Skip to content

Commit b006370

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

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
@@ -324,6 +324,8 @@ Improvements to Clang's diagnostics
324324

325325
- Don't emit bogus dangling diagnostics when ``[[gsl::Owner]]`` and `[[clang::lifetimebound]]` are used together (#GH108272).
326326

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

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()) &&
@@ -276,7 +296,7 @@ static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
276296
return false;
277297
if (!isRecordWithAttr<PointerAttr>(
278298
Callee->getFunctionObjectParameterType()) &&
279-
!isRecordWithAttr<OwnerAttr>(Callee->getFunctionObjectParameterType()))
299+
!isGSLOwner(Callee->getFunctionObjectParameterType()))
280300
return false;
281301
if (Callee->getReturnType()->isPointerType() ||
282302
isRecordWithAttr<PointerAttr>(Callee->getReturnType())) {
@@ -414,7 +434,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
414434
// Once we initialized a value with a non gsl-owner reference, it can no
415435
// longer dangle.
416436
if (ReturnType->isReferenceType() &&
417-
!isRecordWithAttr<OwnerAttr>(ReturnType->getPointeeType())) {
437+
!isGSLOwner(ReturnType->getPointeeType())) {
418438
for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
419439
if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit ||
420440
PE.Kind == IndirectLocalPathEntry::LifetimeBoundCall)
@@ -469,12 +489,17 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
469489
if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
470490
VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]);
471491
else if (EnableGSLAnalysis && I == 0) {
492+
// Perform GSL analysis for the first argument
472493
if (shouldTrackFirstArgument(Callee)) {
473494
VisitGSLPointerArg(Callee, Args[0]);
474-
} else if (auto *CCE = dyn_cast<CXXConstructExpr>(Call);
475-
CCE &&
476-
CCE->getConstructor()->getParent()->hasAttr<PointerAttr>()) {
477-
VisitGSLPointerArg(CCE->getConstructor(), Args[0]);
495+
} else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call)) {
496+
const auto *ClassD = Ctor->getConstructor()->getParent();
497+
// Two cases:
498+
// a GSL pointer, e.g. std::string_view
499+
// a container of GSL pointer, e.g. std::vector<string_view>
500+
if (ClassD->hasAttr<PointerAttr>() ||
501+
(isContainerOfPointer(ClassD) && Callee->getNumParams() == 1))
502+
VisitGSLPointerArg(Ctor->getConstructor(), Args[0]);
478503
}
479504
}
480505
}
@@ -986,13 +1011,12 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
9861011
// int &p = *localUniquePtr;
9871012
// someContainer.add(std::move(localUniquePtr));
9881013
// return p;
989-
IsLocalGslOwner = isRecordWithAttr<OwnerAttr>(L->getType());
1014+
IsLocalGslOwner = isGSLOwner(L->getType());
9901015
if (pathContainsInit(Path) || !IsLocalGslOwner)
9911016
return false;
9921017
} else {
9931018
IsGslPtrValueFromGslTempOwner =
994-
MTE && !MTE->getExtendingDecl() &&
995-
isRecordWithAttr<OwnerAttr>(MTE->getType());
1019+
MTE && !MTE->getExtendingDecl() && isGSLOwner(MTE->getType());
9961020
// Skipping a chain of initializing gsl::Pointer annotated objects.
9971021
// We are looking only for the final source to find out if it was
9981022
// 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)