Skip to content

Commit 6e110ae

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 bfcfd1e commit 6e110ae

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
@@ -302,11 +302,6 @@ static bool isStdInitializerListOfPointer(const RecordDecl *RD) {
302302
return false;
303303
}
304304

305-
static bool isGSLOwner(QualType T) {
306-
return isRecordWithAttr<OwnerAttr>(T) &&
307-
!isContainerOfPointer(T->getAsRecordDecl());
308-
}
309-
310305
static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
311306
if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
312307
if (isRecordWithAttr<PointerAttr>(Conv->getConversionType()) &&
@@ -316,7 +311,7 @@ static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
316311
return false;
317312
if (!isRecordWithAttr<PointerAttr>(
318313
Callee->getFunctionObjectParameterType()) &&
319-
!isGSLOwner(Callee->getFunctionObjectParameterType()))
314+
!isRecordWithAttr<OwnerAttr>(Callee->getFunctionObjectParameterType()))
320315
return false;
321316
if (isPointerLikeType(Callee->getReturnType())) {
322317
if (!Callee->getIdentifier())
@@ -372,22 +367,29 @@ static bool
372367
shouldTrackFirstArgumentForConstructor(const CXXConstructExpr *Ctor) {
373368
const auto *ClassD = Ctor->getConstructor()->getParent();
374369

375-
auto FirstArgType = Ctor->getArg(0)->getType();
376370
// Case 1, construct a GSL pointer, e.g. std::string_view
377371
if (ClassD->hasAttr<PointerAttr>())
378372
return true;
379373

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

393395
// Return true if this is an "normal" assignment operator.
@@ -477,7 +479,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
477479
// Once we initialized a value with a non gsl-owner reference, it can no
478480
// longer dangle.
479481
if (ReturnType->isReferenceType() &&
480-
!isGSLOwner(ReturnType->getPointeeType())) {
482+
!isRecordWithAttr<OwnerAttr>(ReturnType->getPointeeType())) {
481483
for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
482484
if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit ||
483485
PE.Kind == IndirectLocalPathEntry::LifetimeBoundCall)
@@ -1049,12 +1051,13 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
10491051
// int &p = *localUniquePtr;
10501052
// someContainer.add(std::move(localUniquePtr));
10511053
// return p;
1052-
IsLocalGslOwner = isGSLOwner(L->getType());
1054+
IsLocalGslOwner = isRecordWithAttr<OwnerAttr>(L->getType());
10531055
if (pathContainsInit(Path) || !IsLocalGslOwner)
10541056
return false;
10551057
} else {
10561058
IsGslPtrValueFromGslTempOwner =
1057-
MTE && !MTE->getExtendingDecl() && isGSLOwner(MTE->getType());
1059+
MTE && !MTE->getExtendingDecl() &&
1060+
isRecordWithAttr<OwnerAttr>(MTE->getType());
10581061
// Skipping a chain of initializing gsl::Pointer annotated objects.
10591062
// We are looking only for the final source to find out if it was
10601063
// 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)