Skip to content

Commit f6a9125

Browse files
committed
[clang] Diagnose dangling issues for "Container<GSLPointer>" case.
We teach the GSL lifetime analysis to handle this code path, particuly when constructing the container<GSLPointer> object from a GSLOwner.
1 parent b11a703 commit f6a9125

File tree

4 files changed

+104
-6
lines changed

4 files changed

+104
-6
lines changed

clang/docs/ReleaseNotes.rst

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

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

295+
- Clang now diagnoses cases where a dangling `GSLOwner<GSLPointer>`` object is constructed, e.g. `std::vector<string_view> v = {std::string}` (#GH100526).
296+
295297
Improvements to Clang's time-trace
296298
----------------------------------
297299

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 instantiated
6694+
template argument is a pointer type (raw pointer, or [[gsl::Pointer]]), the
6695+
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: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,21 @@ static bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
367367
return isNormalAssignmentOperator(FD);
368368
}
369369

370+
// Returns true if the given Record decl is a form of `GSLOwner<Pointer>`
371+
// type, e.g. std::vector<string_view>, std::optional<string_view>.
372+
static bool isContainerOfPointer(const RecordDecl *Container) {
373+
if (const auto *CTSD =
374+
dyn_cast_if_present<ClassTemplateSpecializationDecl>(Container)) {
375+
if (!CTSD->hasAttr<OwnerAttr>()) // Container must be a GSL owner type.
376+
return false;
377+
const auto &TAs = CTSD->getTemplateArgs();
378+
return TAs.size() > 0 && TAs[0].getKind() == TemplateArgument::Type &&
379+
(isRecordWithAttr<PointerAttr>(TAs[0].getAsType()) ||
380+
TAs[0].getAsType()->isPointerType());
381+
}
382+
return false;
383+
}
384+
370385
// Visit lifetimebound or gsl-pointer arguments.
371386
static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
372387
LocalVisitor Visit) {
@@ -470,10 +485,14 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
470485
else if (EnableGSLAnalysis && I == 0) {
471486
if (shouldTrackFirstArgument(Callee)) {
472487
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]);
488+
} else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call)) {
489+
const auto *ClassD = Ctor->getConstructor()->getParent();
490+
// Two cases:
491+
// a GSL pointer, e.g. std::string_view
492+
// a container of GSL pointer, e.g. std::vector<string_view>
493+
if (ClassD->hasAttr<PointerAttr>() ||
494+
(isContainerOfPointer(ClassD) && Callee->getNumParams() == 1))
495+
VisitGSLPointerArg(Ctor->getConstructor(), Args[0]);
477496
}
478497
}
479498
}
@@ -990,13 +1009,16 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
9901009
// int &p = *localUniquePtr;
9911010
// someContainer.add(std::move(localUniquePtr));
9921011
// return p;
993-
IsLocalGslOwner = isRecordWithAttr<OwnerAttr>(L->getType());
1012+
IsLocalGslOwner =
1013+
isRecordWithAttr<OwnerAttr>(L->getType()) &&
1014+
!isContainerOfPointer(L->getType()->getAsRecordDecl());
9941015
if (pathContainsInit(Path) || !IsLocalGslOwner)
9951016
return false;
9961017
} else {
9971018
IsGslPtrValueFromGslTempOwner =
9981019
MTE && !MTE->getExtendingDecl() &&
999-
isRecordWithAttr<OwnerAttr>(MTE->getType());
1020+
isRecordWithAttr<OwnerAttr>(MTE->getType()) &&
1021+
!isContainerOfPointer(MTE->getType()->getAsRecordDecl());
10001022
// Skipping a chain of initializing gsl::Pointer annotated objects.
10011023
// We are looking only for the final source to find out if it was
10021024
// a local or temporary owner or the address of a local variable/param.

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

Lines changed: 60 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,40 @@ 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({std::string(), std::string_view()}); // expected-warning {{object backing the pointer will be destroyed at the end}}
584+
std::vector<std::string_view> v3({std::string_view(), std::string()}); // expected-warning {{object backing the pointer will be destroyed at the end}}
585+
586+
std::optional<std::string_view> o1 = std::string(); // expected-warning {{object backing the pointer}}
587+
588+
std::string s;
589+
// This is a tricky use-after-free case, what it does:
590+
// 1. make_optional creates a temporary "optional<string>"" object
591+
// 2. the temporary object owns the underlying string which is copied from s.
592+
// 3. the t3 object holds the view to the underlying string of the temporary object.
593+
std::optional<std::string_view> o2 = std::make_optional(s); // expected-warning {{object backing the pointer}}
594+
595+
// FIXME: should work for assignment cases
596+
v1 = {std::string()};
597+
o1 = std::string();
598+
599+
// no warning on copying pointers.
600+
std::vector<std::string_view> n1 = {std::string_view()};
601+
std::optional<std::string_view> n2 = {std::string_view()};
602+
std::optional<std::string_view> n3 = std::make_optional(std::string_view());
603+
const char* b = "";
604+
std::optional<std::string_view> n4 = std::make_optional(b);
605+
std::optional<std::string_view> n5 = std::make_optional("test");
606+
}
607+
608+
std::vector<std::string_view> test2(int i) {
609+
std::vector<std::string_view> t;
610+
if (i)
611+
return t; // this is fine, no dangling
612+
return std::vector<std::string_view>(t.begin(), t.end());
613+
}
614+
615+
} // namespace GH100526

0 commit comments

Comments
 (0)