Skip to content

Commit ca0b4ba

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 ea6fffa commit ca0b4ba

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()))
@@ -311,7 +306,7 @@ static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
311306
return false;
312307
if (!isRecordWithAttr<PointerAttr>(
313308
Callee->getFunctionObjectParameterType()) &&
314-
!isGSLOwner(Callee->getFunctionObjectParameterType()))
309+
!isRecordWithAttr<OwnerAttr>(Callee->getFunctionObjectParameterType()))
315310
return false;
316311
if (isPointerLikeType(Callee->getReturnType())) {
317312
if (!Callee->getIdentifier())
@@ -367,22 +362,29 @@ static bool
367362
shouldTrackFirstArgumentForConstructor(const CXXConstructExpr *Ctor) {
368363
const auto *ClassD = Ctor->getConstructor()->getParent();
369364

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

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

388390
// Return true if this is an "normal" assignment operator.
@@ -472,7 +474,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
472474
// Once we initialized a value with a non gsl-owner reference, it can no
473475
// longer dangle.
474476
if (ReturnType->isReferenceType() &&
475-
!isGSLOwner(ReturnType->getPointeeType())) {
477+
!isRecordWithAttr<OwnerAttr>(ReturnType->getPointeeType())) {
476478
for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
477479
if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit ||
478480
PE.Kind == IndirectLocalPathEntry::LifetimeBoundCall)
@@ -1044,12 +1046,13 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
10441046
// int &p = *localUniquePtr;
10451047
// someContainer.add(std::move(localUniquePtr));
10461048
// return p;
1047-
IsLocalGslOwner = isGSLOwner(L->getType());
1049+
IsLocalGslOwner = isRecordWithAttr<OwnerAttr>(L->getType());
10481050
if (pathContainsInit(Path) || !IsLocalGslOwner)
10491051
return false;
10501052
} else {
10511053
IsGslPtrValueFromGslTempOwner =
1052-
MTE && !MTE->getExtendingDecl() && isGSLOwner(MTE->getType());
1054+
MTE && !MTE->getExtendingDecl() &&
1055+
isRecordWithAttr<OwnerAttr>(MTE->getType());
10531056
// Skipping a chain of initializing gsl::Pointer annotated objects.
10541057
// We are looking only for the final source to find out if it was
10551058
// 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*() &&;
@@ -598,7 +598,7 @@ void test() {
598598
// 3. the t3 object holds the view to the underlying string of the temporary object.
599599
std::optional<std::string_view> o2 = std::make_optional(s); // expected-warning {{object backing the pointer}}
600600
std::optional<std::string_view> o3 = std::optional<std::string>(s); // expected-warning {{object backing the pointer}}
601-
std::optional<std::string_view> o4 = std::optional<std::string_view>(s);
601+
std::optional<std::string_view> o4 = std::optional<std::string_view>(s);
602602

603603
// FIXME: should work for assignment cases
604604
v1 = {std::string()};
@@ -633,12 +633,81 @@ std::optional<int*> test4(int a) {
633633
return std::make_optional(nullptr); // fine
634634
}
635635

636+
636637
template <typename T>
637638
struct [[gsl::Owner]] StatusOr {
638-
const T &value() [[clang::lifetimebound]];
639+
const T &valueLB() const [[clang::lifetimebound]];
640+
const T &valueNoLB() const;
639641
};
640-
std::vector<int*> test5(StatusOr<std::vector<int*>> aa) {
641-
return aa.value(); // fine
642+
643+
template<typename T>
644+
struct [[gsl::Pointer]] Span {
645+
Span(const std::vector<T> &V);
646+
647+
const int& getFieldLB() const [[clang::lifetimebound]];
648+
const int& getFieldNoLB() const;
649+
};
650+
651+
652+
/////// From Owner<Pointer> ///////
653+
654+
// Pointer from Owner<Pointer>
655+
std::string_view test5() {
656+
std::string_view a = StatusOr<std::string_view>().valueLB(); // expected-warning {{object backing the pointer will be dest}}
657+
return StatusOr<std::string_view>().valueLB(); // expected-warning {{returning address of local temporary}}
658+
659+
// No dangling diagnostics on non-lifetimebound methods.
660+
std::string_view b = StatusOr<std::string_view>().valueNoLB();
661+
return StatusOr<std::string_view>().valueNoLB();
662+
}
663+
664+
// Pointer<Pointer> from Owner<Pointer>
665+
// Prevent regression GH108463
666+
Span<int*> test6(std::vector<int*> v) {
667+
Span<int *> dangling = std::vector<int*>(); // expected-warning {{object backing the pointer}}
668+
return v; // expected-warning {{address of stack memory}}
669+
}
670+
671+
/////// From Owner<Owner<Pointer>> ///////
672+
673+
// Pointer from Owner<Owner<Pointer>>
674+
int* test7(StatusOr<StatusOr<int*>> aa) {
675+
// No dangling diagnostic on pointer.
676+
return aa.valueLB().valueLB(); // OK.
677+
}
678+
679+
// Owner<Pointer> from Owner<Owner<Pointer>>
680+
std::vector<int*> test8(StatusOr<std::vector<int*>> aa) {
681+
return aa.valueLB(); // OK, no pointer being construct on this case.
682+
return aa.valueNoLB();
683+
}
684+
685+
// Pointer<Pointer> from Owner<Owner<Pointer>>
686+
Span<int*> test9(StatusOr<std::vector<int*>> aa) {
687+
return aa.valueLB(); // expected-warning {{address of stack memory associated}}
688+
return aa.valueNoLB(); // OK.
689+
}
690+
691+
/////// From Owner<Owner> ///////
692+
693+
// Pointer<Owner>> from Owner<Owner>
694+
Span<std::string> test10(StatusOr<std::vector<std::string>> aa) {
695+
return aa.valueLB(); // expected-warning {{address of stack memory}}
696+
return aa.valueNoLB(); // OK.
697+
}
698+
699+
/////// From Owner<Pointer<Owner>> ///////
700+
701+
// Pointer<Owner>> from Owner<Pointer<Owner>>
702+
Span<std::string> test11(StatusOr<Span<std::string>> aa) {
703+
return aa.valueLB(); // expected-warning {{address of stack memory}}
704+
return aa.valueNoLB(); // OK.
705+
}
706+
707+
// Lifetimebound and gsl::Pointer.
708+
const int& test12(Span<int> a) {
709+
return a.getFieldLB(); // expected-warning {{reference to stack memory associated}}
710+
return a.getFieldNoLB(); // OK.
642711
}
643712

644713
} // namespace GH100526

0 commit comments

Comments
 (0)