Skip to content

Commit cda8759

Browse files
committed
Reapply "[clang] Diagnose dangling issues for the "Container<GSLPointer>" case. (llvm#107213)"
This reverts commit 0683c4e.
1 parent f4eeae1 commit cda8759

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
@@ -333,6 +333,8 @@ Improvements to Clang's diagnostics
333333
local variables passed to function calls using the ``[[clang::musttail]]``
334334
attribute.
335335

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

clang/include/clang/Basic/AttrDocs.td

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

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

clang/lib/Sema/CheckExprLifetime.cpp

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

274+
// Returns true if the given Record decl is a form of `GSLOwner<Pointer>`
275+
// type, e.g. std::vector<string_view>, std::optional<string_view>.
276+
static bool isContainerOfPointer(const RecordDecl *Container) {
277+
if (const auto *CTSD =
278+
dyn_cast_if_present<ClassTemplateSpecializationDecl>(Container)) {
279+
if (!CTSD->hasAttr<OwnerAttr>()) // Container must be a GSL owner type.
280+
return false;
281+
const auto &TAs = CTSD->getTemplateArgs();
282+
return TAs.size() > 0 && TAs[0].getKind() == TemplateArgument::Type &&
283+
(isRecordWithAttr<PointerAttr>(TAs[0].getAsType()) ||
284+
TAs[0].getAsType()->isPointerType());
285+
}
286+
return false;
287+
}
288+
289+
static bool isGSLOwner(QualType T) {
290+
return isRecordWithAttr<OwnerAttr>(T) &&
291+
!isContainerOfPointer(T->getAsRecordDecl());
292+
}
293+
274294
static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
275295
if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
276296
if (isRecordWithAttr<PointerAttr>(Conv->getConversionType()) &&
@@ -280,7 +300,7 @@ static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
280300
return false;
281301
if (!isRecordWithAttr<PointerAttr>(
282302
Callee->getFunctionObjectParameterType()) &&
283-
!isRecordWithAttr<OwnerAttr>(Callee->getFunctionObjectParameterType()))
303+
!isGSLOwner(Callee->getFunctionObjectParameterType()))
284304
return false;
285305
if (Callee->getReturnType()->isPointerType() ||
286306
isRecordWithAttr<PointerAttr>(Callee->getReturnType())) {
@@ -418,7 +438,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
418438
// Once we initialized a value with a non gsl-owner reference, it can no
419439
// longer dangle.
420440
if (ReturnType->isReferenceType() &&
421-
!isRecordWithAttr<OwnerAttr>(ReturnType->getPointeeType())) {
441+
!isGSLOwner(ReturnType->getPointeeType())) {
422442
for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
423443
if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit ||
424444
PE.Kind == IndirectLocalPathEntry::LifetimeBoundCall)
@@ -473,12 +493,17 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
473493
if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
474494
VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]);
475495
else if (EnableGSLAnalysis && I == 0) {
496+
// Perform GSL analysis for the first argument
476497
if (shouldTrackFirstArgument(Callee)) {
477498
VisitGSLPointerArg(Callee, Args[0]);
478-
} else if (auto *CCE = dyn_cast<CXXConstructExpr>(Call);
479-
CCE &&
480-
CCE->getConstructor()->getParent()->hasAttr<PointerAttr>()) {
481-
VisitGSLPointerArg(CCE->getConstructor(), Args[0]);
499+
} else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call)) {
500+
const auto *ClassD = Ctor->getConstructor()->getParent();
501+
// Two cases:
502+
// a GSL pointer, e.g. std::string_view
503+
// a container of GSL pointer, e.g. std::vector<string_view>
504+
if (ClassD->hasAttr<PointerAttr>() ||
505+
(isContainerOfPointer(ClassD) && Callee->getNumParams() == 1))
506+
VisitGSLPointerArg(Ctor->getConstructor(), Args[0]);
482507
}
483508
}
484509
}
@@ -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 {
@@ -587,3 +610,57 @@ std::string_view test2() {
587610
return k.value(); // expected-warning {{address of stack memory associated}}
588611
}
589612
} // namespace GH108272
613+
614+
namespace GH100526 {
615+
void test() {
616+
std::vector<std::string_view> v1({std::string()}); // expected-warning {{object backing the pointer will be destroyed at the end}}
617+
std::vector<std::string_view> v2({
618+
std::string(), // expected-warning {{object backing the pointer will be destroyed at the end}}
619+
std::string_view()
620+
});
621+
std::vector<std::string_view> v3({
622+
std::string_view(),
623+
std::string() // expected-warning {{object backing the pointer will be destroyed at the end}}
624+
});
625+
626+
std::optional<std::string_view> o1 = std::string(); // expected-warning {{object backing the pointer}}
627+
628+
std::string s;
629+
// This is a tricky use-after-free case, what it does:
630+
// 1. make_optional creates a temporary "optional<string>"" object
631+
// 2. the temporary object owns the underlying string which is copied from s.
632+
// 3. the t3 object holds the view to the underlying string of the temporary object.
633+
std::optional<std::string_view> o2 = std::make_optional(s); // expected-warning {{object backing the pointer}}
634+
std::optional<std::string_view> o3 = std::optional<std::string>(s); // expected-warning {{object backing the pointer}}
635+
std::optional<std::string_view> o4 = std::optional<std::string_view>(s);
636+
637+
// FIXME: should work for assignment cases
638+
v1 = {std::string()};
639+
o1 = std::string();
640+
641+
// no warning on copying pointers.
642+
std::vector<std::string_view> n1 = {std::string_view()};
643+
std::optional<std::string_view> n2 = {std::string_view()};
644+
std::optional<std::string_view> n3 = std::string_view();
645+
std::optional<std::string_view> n4 = std::make_optional(std::string_view());
646+
const char* b = "";
647+
std::optional<std::string_view> n5 = std::make_optional(b);
648+
std::optional<std::string_view> n6 = std::make_optional("test");
649+
}
650+
651+
std::vector<std::string_view> test2(int i) {
652+
std::vector<std::string_view> t;
653+
if (i)
654+
return t; // this is fine, no dangling
655+
return std::vector<std::string_view>(t.begin(), t.end());
656+
}
657+
658+
std::optional<std::string_view> test3(int i) {
659+
std::string s;
660+
std::string_view sv;
661+
if (i)
662+
return s; // expected-warning {{address of stack memory associated}}
663+
return sv; // fine
664+
}
665+
666+
} // namespace GH100526

0 commit comments

Comments
 (0)