Skip to content

Commit 1dbf4a8

Browse files
committed
Fix a newly-introcuded false negative.
And add more tests per the comment. The new false negative was caused by replacing the instances of "!isRecordWithAttr<OwnerAttr>" in `TemporaryVisitor` with "isGSLOwner".
1 parent 50daa23 commit 1dbf4a8

File tree

2 files changed

+97
-25
lines changed

2 files changed

+97
-25
lines changed

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -298,11 +298,6 @@ static bool isStdInitializerListOfPointer(const RecordDecl *RD) {
298298
return false;
299299
}
300300

301-
static bool isGSLOwner(QualType T) {
302-
return isRecordWithAttr<OwnerAttr>(T) &&
303-
!isContainerOfPointer(T->getAsRecordDecl());
304-
}
305-
306301
static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
307302
if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
308303
if (isRecordWithAttr<PointerAttr>(Conv->getConversionType()) &&
@@ -312,7 +307,7 @@ static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
312307
return false;
313308
if (!isRecordWithAttr<PointerAttr>(
314309
Callee->getFunctionObjectParameterType()) &&
315-
!isGSLOwner(Callee->getFunctionObjectParameterType()))
310+
!isRecordWithAttr<OwnerAttr>(Callee->getFunctionObjectParameterType()))
316311
return false;
317312
if (isPointerLikeType(Callee->getReturnType())) {
318313
if (!Callee->getIdentifier())
@@ -368,22 +363,29 @@ static bool
368363
shouldTrackFirstArgumentForConstructor(const CXXConstructExpr *Ctor) {
369364
const auto *ClassD = Ctor->getConstructor()->getParent();
370365

371-
auto FirstArgType = Ctor->getArg(0)->getType();
372366
// Case 1, construct a GSL pointer, e.g. std::string_view
373367
if (ClassD->hasAttr<PointerAttr>())
374368
return true;
375369

376-
// case 2: construct a container of pointer (std::vector<std::string_view>)
377-
// from an owner or a std::initilizer_list.
378-
//
379-
// std::initializer_list is a proxy object that provides access to the backing
380-
// array. We perform analysis on it to determine if there are any dangling
381-
// temporaries in the backing array.
370+
auto FirstArgType = Ctor->getArg(0)->getType();
371+
// Case 2, construct a container of pointer (std::vector<std::string_view>)
372+
// from a std::initilizer_list or an GSL owner
382373
if (Ctor->getConstructor()->getNumParams() != 1 ||
383374
!isContainerOfPointer(ClassD))
384375
return false;
385-
return isGSLOwner(FirstArgType) ||
386-
isStdInitializerListOfPointer(FirstArgType->getAsRecordDecl());
376+
377+
// For the typical case: `std::vector<std::string_view> abc = {std::string()};`
378+
// std::initializer_list is a proxy object that provides access to the backing
379+
// array. We perform analysis on it to determine if there are any dangling
380+
// temporaries in the backing array.
381+
if (isStdInitializerListOfPointer(FirstArgType->getAsRecordDecl()))
382+
return true;
383+
// For the case: `std::optional<std::string_view> abc = std::string();`
384+
// When constructing from a container of pointers, it's less likely to result
385+
// in a dangling pointer. Therefore, we try to be conservative to not track
386+
// the argument futher to avoid false positives.
387+
return isRecordWithAttr<OwnerAttr>(FirstArgType) &&
388+
!isContainerOfPointer(FirstArgType->getAsRecordDecl());
387389
}
388390

389391
// Return true if this is an "normal" assignment operator.
@@ -473,7 +475,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
473475
// Once we initialized a value with a non gsl-owner reference, it can no
474476
// longer dangle.
475477
if (ReturnType->isReferenceType() &&
476-
!isGSLOwner(ReturnType->getPointeeType())) {
478+
!isRecordWithAttr<OwnerAttr>(ReturnType->getPointeeType())) {
477479
for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
478480
if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit ||
479481
PE.Kind == IndirectLocalPathEntry::LifetimeBoundCall)
@@ -1045,12 +1047,13 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
10451047
// int &p = *localUniquePtr;
10461048
// someContainer.add(std::move(localUniquePtr));
10471049
// return p;
1048-
IsLocalGslOwner = isGSLOwner(L->getType());
1050+
IsLocalGslOwner = isRecordWithAttr<OwnerAttr>(L->getType());
10491051
if (pathContainsInit(Path) || !IsLocalGslOwner)
10501052
return false;
10511053
} else {
10521054
IsGslPtrValueFromGslTempOwner =
1053-
MTE && !MTE->getExtendingDecl() && isGSLOwner(MTE->getType());
1055+
MTE && !MTE->getExtendingDecl() &&
1056+
isRecordWithAttr<OwnerAttr>(MTE->getType());
10541057
// Skipping a chain of initializing gsl::Pointer annotated objects.
10551058
// We are looking only for the final source to find out if it was
10561059
// a local or temporary owner or the address of a local variable/param.

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

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ struct vector {
172172
const T *data() const;
173173
vector();
174174
vector(initializer_list<T> __l);
175-
175+
176176
template<typename InputIterator>
177177
vector(InputIterator first, InputIterator __last);
178178

@@ -218,10 +218,10 @@ struct optional {
218218
optional(const T&);
219219

220220
template<typename U = T>
221-
optional(U&& t);
221+
optional(U&& t);
222222

223223
template<typename U>
224-
optional(optional<U>&& __t);
224+
optional(optional<U>&& __t);
225225

226226
T &operator*() &;
227227
T &&operator*() &&;
@@ -632,7 +632,7 @@ void test() {
632632
// 3. the t3 object holds the view to the underlying string of the temporary object.
633633
std::optional<std::string_view> o2 = std::make_optional(s); // expected-warning {{object backing the pointer}}
634634
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);
635+
std::optional<std::string_view> o4 = std::optional<std::string_view>(s);
636636

637637
// FIXME: should work for assignment cases
638638
v1 = {std::string()};
@@ -667,12 +667,81 @@ std::optional<int*> test4(int a) {
667667
return std::make_optional(nullptr); // fine
668668
}
669669

670+
670671
template <typename T>
671672
struct [[gsl::Owner]] StatusOr {
672-
const T &value() [[clang::lifetimebound]];
673+
const T &valueLB() const [[clang::lifetimebound]];
674+
const T &valueNoLB() const;
675+
};
676+
677+
template<typename T>
678+
struct [[gsl::Pointer]] Span {
679+
Span(const std::vector<T> &V);
680+
681+
const int& getFieldLB() const [[clang::lifetimebound]];
682+
const int& getFieldNoLB() const;
673683
};
674-
std::vector<int*> test5(StatusOr<std::vector<int*>> aa) {
675-
return aa.value(); // fine
684+
685+
686+
/////// From Owner<Pointer> ///////
687+
688+
// Pointer from Owner<Pointer>
689+
std::string_view test5() {
690+
std::string_view a = StatusOr<std::string_view>().valueLB(); // expected-warning {{object backing the pointer will be dest}}
691+
return StatusOr<std::string_view>().valueLB(); // expected-warning {{returning address of local temporary}}
692+
693+
// No dangling diagnostics on non-lifetimebound methods.
694+
std::string_view b = StatusOr<std::string_view>().valueNoLB();
695+
return StatusOr<std::string_view>().valueNoLB();
696+
}
697+
698+
// Pointer<Pointer> from Owner<Pointer>
699+
// Prevent regression GH108463
700+
Span<int*> test6(std::vector<int*> v) {
701+
Span<int *> dangling = std::vector<int*>(); // expected-warning {{object backing the pointer}}
702+
return v; // expected-warning {{address of stack memory}}
703+
}
704+
705+
/////// From Owner<Owner<Pointer>> ///////
706+
707+
// Pointer from Owner<Owner<Pointer>>
708+
int* test7(StatusOr<StatusOr<int*>> aa) {
709+
// No dangling diagnostic on pointer.
710+
return aa.valueLB().valueLB(); // OK.
711+
}
712+
713+
// Owner<Pointer> from Owner<Owner<Pointer>>
714+
std::vector<int*> test8(StatusOr<std::vector<int*>> aa) {
715+
return aa.valueLB(); // OK, no pointer being construct on this case.
716+
return aa.valueNoLB();
717+
}
718+
719+
// Pointer<Pointer> from Owner<Owner<Pointer>>
720+
Span<int*> test9(StatusOr<std::vector<int*>> aa) {
721+
return aa.valueLB(); // expected-warning {{address of stack memory associated}}
722+
return aa.valueNoLB(); // OK.
723+
}
724+
725+
/////// From Owner<Owner> ///////
726+
727+
// Pointer<Owner>> from Owner<Owner>
728+
Span<std::string> test10(StatusOr<std::vector<std::string>> aa) {
729+
return aa.valueLB(); // expected-warning {{address of stack memory}}
730+
return aa.valueNoLB(); // OK.
731+
}
732+
733+
/////// From Owner<Pointer<Owner>> ///////
734+
735+
// Pointer<Owner>> from Owner<Pointer<Owner>>
736+
Span<std::string> test11(StatusOr<Span<std::string>> aa) {
737+
return aa.valueLB(); // expected-warning {{address of stack memory}}
738+
return aa.valueNoLB(); // OK.
739+
}
740+
741+
// Lifetimebound and gsl::Pointer.
742+
const int& test12(Span<int> a) {
743+
return a.getFieldLB(); // expected-warning {{reference to stack memory associated}}
744+
return a.getFieldNoLB(); // OK.
676745
}
677746

678747
} // namespace GH100526

0 commit comments

Comments
 (0)