Skip to content

Commit e50131a

Browse files
authored
[clang] Diagnose dangling issues for the "Container<GSLPointer>" case. (#107213)
This pull request enhances the GSL lifetime analysis to detect situations where a dangling `Container<GSLPointer>` object is constructed: ```cpp std::vector<std::string_view> bad = {std::string()}; // dangling ``` The assignment case is not yet supported, but they will be addressed in a follow-up. Fixes #100526 (excluding the `push_back` case).
1 parent 2f3d061 commit e50131a

File tree

4 files changed

+126
-9
lines changed

4 files changed

+126
-9
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@ Improvements to Clang's diagnostics
298298

299299
- Clang now warns for u8 character literals used in C23 with ``-Wpre-c23-compat`` instead of ``-Wpre-c++17-compat``.
300300

301+
- Clang now diagnoses cases where a dangling ``GSLOwner<GSLPointer>`` object is constructed, e.g. ``std::vector<string_view> v = {std::string()};`` (#GH100526).
302+
301303
Improvements to Clang's time-trace
302304
----------------------------------
303305

clang/include/clang/Basic/AttrDocs.td

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6690,6 +6690,20 @@ When the Owner's lifetime ends, it will consider the Pointer to be dangling.
66906690
P.getInt(); // P is dangling
66916691
}
66926692

6693+
If a template class is annotated with ``[[gsl::Owner]]``, and the first
6694+
instantiated template argument is a pointer type (raw pointer, or ``[[gsl::Pointer]]``),
6695+
the analysis will consider the instantiated class as a container of the pointer.
6696+
When constructing such an object from a GSL owner object, the analysis will
6697+
assume that the container holds a pointer to the owner object. Consequently,
6698+
when the owner object is destroyed, the pointer will be considered dangling.
6699+
6700+
.. code-block:: c++
6701+
6702+
int f() {
6703+
std::vector<std::string_view> v = {std::string()}; // v holds a dangling pointer.
6704+
std::optional<std::string_view> o = std::string(); // o holds a dangling pointer.
6705+
}
6706+
66936707
}];
66946708
}
66956709

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,26 @@ static bool isInStlNamespace(const Decl *D) {
267267
return DC->isStdNamespace();
268268
}
269269

270+
// Returns true if the given Record decl is a form of `GSLOwner<Pointer>`
271+
// type, e.g. std::vector<string_view>, std::optional<string_view>.
272+
static bool isContainerOfPointer(const RecordDecl *Container) {
273+
if (const auto *CTSD =
274+
dyn_cast_if_present<ClassTemplateSpecializationDecl>(Container)) {
275+
if (!CTSD->hasAttr<OwnerAttr>()) // Container must be a GSL owner type.
276+
return false;
277+
const auto &TAs = CTSD->getTemplateArgs();
278+
return TAs.size() > 0 && TAs[0].getKind() == TemplateArgument::Type &&
279+
(isRecordWithAttr<PointerAttr>(TAs[0].getAsType()) ||
280+
TAs[0].getAsType()->isPointerType());
281+
}
282+
return false;
283+
}
284+
285+
static bool isGSLOwner(QualType T) {
286+
return isRecordWithAttr<OwnerAttr>(T) &&
287+
!isContainerOfPointer(T->getAsRecordDecl());
288+
}
289+
270290
static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
271291
if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
272292
if (isRecordWithAttr<PointerAttr>(Conv->getConversionType()))
@@ -275,7 +295,7 @@ static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
275295
return false;
276296
if (!isRecordWithAttr<PointerAttr>(
277297
Callee->getFunctionObjectParameterType()) &&
278-
!isRecordWithAttr<OwnerAttr>(Callee->getFunctionObjectParameterType()))
298+
!isGSLOwner(Callee->getFunctionObjectParameterType()))
279299
return false;
280300
if (Callee->getReturnType()->isPointerType() ||
281301
isRecordWithAttr<PointerAttr>(Callee->getReturnType())) {
@@ -413,7 +433,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
413433
// Once we initialized a value with a non gsl-owner reference, it can no
414434
// longer dangle.
415435
if (ReturnType->isReferenceType() &&
416-
!isRecordWithAttr<OwnerAttr>(ReturnType->getPointeeType())) {
436+
!isGSLOwner(ReturnType->getPointeeType())) {
417437
for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
418438
if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit ||
419439
PE.Kind == IndirectLocalPathEntry::LifetimeBoundCall)
@@ -468,12 +488,17 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
468488
if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
469489
VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]);
470490
else if (EnableGSLAnalysis && I == 0) {
491+
// Perform GSL analysis for the first argument
471492
if (shouldTrackFirstArgument(Callee)) {
472493
VisitGSLPointerArg(Callee, Args[0]);
473-
} else if (auto *CCE = dyn_cast<CXXConstructExpr>(Call);
474-
CCE &&
475-
CCE->getConstructor()->getParent()->hasAttr<PointerAttr>()) {
476-
VisitGSLPointerArg(CCE->getConstructor(), Args[0]);
494+
} else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call)) {
495+
const auto *ClassD = Ctor->getConstructor()->getParent();
496+
// Two cases:
497+
// a GSL pointer, e.g. std::string_view
498+
// a container of GSL pointer, e.g. std::vector<string_view>
499+
if (ClassD->hasAttr<PointerAttr>() ||
500+
(isContainerOfPointer(ClassD) && Callee->getNumParams() == 1))
501+
VisitGSLPointerArg(Ctor->getConstructor(), Args[0]);
477502
}
478503
}
479504
}
@@ -990,13 +1015,12 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
9901015
// int &p = *localUniquePtr;
9911016
// someContainer.add(std::move(localUniquePtr));
9921017
// return p;
993-
IsLocalGslOwner = isRecordWithAttr<OwnerAttr>(L->getType());
1018+
IsLocalGslOwner = isGSLOwner(L->getType());
9941019
if (pathContainsInit(Path) || !IsLocalGslOwner)
9951020
return false;
9961021
} else {
9971022
IsGslPtrValueFromGslTempOwner =
998-
MTE && !MTE->getExtendingDecl() &&
999-
isRecordWithAttr<OwnerAttr>(MTE->getType());
1023+
MTE && !MTE->getExtendingDecl() && isGSLOwner(MTE->getType());
10001024
// Skipping a chain of initializing gsl::Pointer annotated objects.
10011025
// We are looking only for the final source to find out if it was
10021026
// a local or temporary owner or the address of a local variable/param.

clang/test/Sema/warn-lifetime-analysis-nocfg.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,17 +158,30 @@ auto begin(C &c) -> decltype(c.begin());
158158
template<typename T, int N>
159159
T *begin(T (&array)[N]);
160160

161+
using size_t = decltype(sizeof(0));
162+
163+
template<typename T>
164+
struct initializer_list {
165+
const T* ptr; size_t sz;
166+
};
161167
template <typename T>
162168
struct vector {
163169
typedef __gnu_cxx::basic_iterator<T> iterator;
164170
iterator begin();
165171
iterator end();
166172
const T *data() const;
173+
vector();
174+
vector(initializer_list<T> __l);
175+
176+
template<typename InputIterator>
177+
vector(InputIterator first, InputIterator __last);
178+
167179
T &at(int n);
168180
};
169181

170182
template<typename T>
171183
struct basic_string_view {
184+
basic_string_view();
172185
basic_string_view(const T *);
173186
const T *begin() const;
174187
};
@@ -203,11 +216,21 @@ template<typename T>
203216
struct optional {
204217
optional();
205218
optional(const T&);
219+
220+
template<typename U = T>
221+
optional(U&& t);
222+
223+
template<typename U>
224+
optional(optional<U>&& __t);
225+
206226
T &operator*() &;
207227
T &&operator*() &&;
208228
T &value() &;
209229
T &&value() &&;
210230
};
231+
template<typename T>
232+
optional<__decay(T)> make_optional(T&&);
233+
211234

212235
template<typename T>
213236
struct stack {
@@ -553,3 +576,57 @@ void test() {
553576
std::string_view svjkk1 = ReturnStringView(StrCat("bar", "x")); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
554577
}
555578
} // namespace GH100549
579+
580+
namespace GH100526 {
581+
void test() {
582+
std::vector<std::string_view> v1({std::string()}); // expected-warning {{object backing the pointer will be destroyed at the end}}
583+
std::vector<std::string_view> v2({
584+
std::string(), // expected-warning {{object backing the pointer will be destroyed at the end}}
585+
std::string_view()
586+
});
587+
std::vector<std::string_view> v3({
588+
std::string_view(),
589+
std::string() // expected-warning {{object backing the pointer will be destroyed at the end}}
590+
});
591+
592+
std::optional<std::string_view> o1 = std::string(); // expected-warning {{object backing the pointer}}
593+
594+
std::string s;
595+
// This is a tricky use-after-free case, what it does:
596+
// 1. make_optional creates a temporary "optional<string>"" object
597+
// 2. the temporary object owns the underlying string which is copied from s.
598+
// 3. the t3 object holds the view to the underlying string of the temporary object.
599+
std::optional<std::string_view> o2 = std::make_optional(s); // expected-warning {{object backing the pointer}}
600+
std::optional<std::string_view> o3 = std::optional<std::string>(s); // expected-warning {{object backing the pointer}}
601+
std::optional<std::string_view> o4 = std::optional<std::string_view>(s);
602+
603+
// FIXME: should work for assignment cases
604+
v1 = {std::string()};
605+
o1 = std::string();
606+
607+
// no warning on copying pointers.
608+
std::vector<std::string_view> n1 = {std::string_view()};
609+
std::optional<std::string_view> n2 = {std::string_view()};
610+
std::optional<std::string_view> n3 = std::string_view();
611+
std::optional<std::string_view> n4 = std::make_optional(std::string_view());
612+
const char* b = "";
613+
std::optional<std::string_view> n5 = std::make_optional(b);
614+
std::optional<std::string_view> n6 = std::make_optional("test");
615+
}
616+
617+
std::vector<std::string_view> test2(int i) {
618+
std::vector<std::string_view> t;
619+
if (i)
620+
return t; // this is fine, no dangling
621+
return std::vector<std::string_view>(t.begin(), t.end());
622+
}
623+
624+
std::optional<std::string_view> test3(int i) {
625+
std::string s;
626+
std::string_view sv;
627+
if (i)
628+
return s; // expected-warning {{address of stack memory associated}}
629+
return sv; // fine
630+
}
631+
632+
} // namespace GH100526

0 commit comments

Comments
 (0)