Skip to content

Commit b2756de

Browse files
committed
Refine the heuristic on inferring the nested pointer onwnership.
Fix another new false positive
1 parent 6e110ae commit b2756de

File tree

2 files changed

+117
-14
lines changed

2 files changed

+117
-14
lines changed

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,18 @@ static bool isContainerOfPointer(const RecordDecl *Container) {
289289
}
290290
return false;
291291
}
292+
static bool isContainerOfOwner(const RecordDecl *Container) {
293+
if (const auto *CTSD =
294+
dyn_cast_if_present<ClassTemplateSpecializationDecl>(Container)) {
295+
if (!CTSD->hasAttr<OwnerAttr>()) // Container must be a GSL owner type.
296+
return false;
297+
const auto &TAs = CTSD->getTemplateArgs();
298+
return TAs.size() > 0 && TAs[0].getKind() == TemplateArgument::Type &&
299+
isRecordWithAttr<OwnerAttr>(TAs[0].getAsType());
300+
}
301+
return false;
302+
}
303+
292304
// Returns true if the given Record is `std::initializer_list<pointer>`.
293305
static bool isStdInitializerListOfPointer(const RecordDecl *RD) {
294306
if (const auto *CTSD =
@@ -361,35 +373,101 @@ static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
361373
return false;
362374
}
363375

376+
// Returns true if the given constructor is a copy-like constructor, such as
377+
// `Ctor(Owner<U>&&)` or `Ctor(const Owner<U>&)`.
378+
static bool isCopyLikeConstructor(const CXXConstructorDecl *Ctor) {
379+
if (!Ctor || Ctor->param_size() != 1)
380+
return false;
381+
const auto *ParamRefType =
382+
Ctor->getParamDecl(0)->getType()->getAs<ReferenceType>();
383+
if (!ParamRefType)
384+
return false;
385+
386+
// Check if the first parameter type "Owner<U>".
387+
if (const auto *TST =
388+
ParamRefType->getPointeeType()->getAs<TemplateSpecializationType>())
389+
return TST->getTemplateName()
390+
.getAsTemplateDecl()
391+
->getTemplatedDecl()
392+
->hasAttr<OwnerAttr>();
393+
return false;
394+
}
395+
364396
// Returns true if we should perform the GSL analysis on the first argument for
365397
// the given constructor.
366398
static bool
367399
shouldTrackFirstArgumentForConstructor(const CXXConstructExpr *Ctor) {
368-
const auto *ClassD = Ctor->getConstructor()->getParent();
400+
const auto *LHSRecordDecl = Ctor->getConstructor()->getParent();
369401

370402
// Case 1, construct a GSL pointer, e.g. std::string_view
371-
if (ClassD->hasAttr<PointerAttr>())
403+
// Always inspect when LHS is a pointer.
404+
if (LHSRecordDecl->hasAttr<PointerAttr>())
372405
return true;
373406

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
377407
if (Ctor->getConstructor()->getNumParams() != 1 ||
378-
!isContainerOfPointer(ClassD))
408+
!isContainerOfPointer(LHSRecordDecl))
379409
return false;
380410

381-
// For the typical case: `std::vector<std::string_view> abc = {std::string()};`
411+
// Now, the LHS is an Owner<Pointer> type, e.g., std::vector<string_view>.
412+
//
413+
// At a high level, we cannot precisely determine what the nested pointer
414+
// owns. However, by analyzing the RHS owner type, we can use heuristics to
415+
// infer ownership information. These heuristics are designed to be
416+
// conservative, minimizing false positives while still providing meaningful
417+
// diagnostics.
418+
//
419+
// While this inference isn't perfect, it helps catch common use-after-free
420+
// patterns.
421+
auto RHSArgType = Ctor->getArg(0)->getType();
422+
const auto *RHSRD = RHSArgType->getAsRecordDecl();
423+
// LHS is constructed from an intializer_list.
424+
//
382425
// std::initializer_list is a proxy object that provides access to the backing
383426
// array. We perform analysis on it to determine if there are any dangling
384427
// temporaries in the backing array.
385-
if (isStdInitializerListOfPointer(FirstArgType->getAsRecordDecl()))
428+
// E.g. std::vector<string_view> abc = {string()};
429+
if (isStdInitializerListOfPointer(RHSRD))
430+
return true;
431+
432+
// RHS must be an owner.
433+
if (!isRecordWithAttr<OwnerAttr>(RHSArgType))
434+
return false;
435+
436+
// Bail out if the RHS is Owner<Pointer>.
437+
//
438+
// We cannot reliably determine what the LHS nested pointer owns -- it could
439+
// be the entire RHS or the nested pointer in RHS. To avoid false positives,
440+
// we skip this case, such as:
441+
// std::stack<std::string_view> s(std::deque<std::string_view>{});
442+
//
443+
// TODO: this also has a false negative, it doesn't catch the case like:
444+
// std::optional<span<int*>> os = std::vector<int*>{}
445+
if (isContainerOfPointer(RHSRD))
446+
return false;
447+
448+
// Assume that the nested Pointer is constructed from the nested Owner.
449+
// E.g. std::optional<string_view> sv = std::optional<string>(s);
450+
if (isContainerOfOwner(RHSRD))
386451
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());
452+
453+
// Now, the LHS is an Owner<Pointer> and the RHS is an Owner<X>, where X is
454+
// neither an `Owner` nor a `Pointer`.
455+
//
456+
// Use the constructor's signature as a hint. If it is a copy-like constructor
457+
// `Owner1<Pointer>(Owner2<X>&&)`, we assume that the nested pointer is
458+
// constructed from X. In such cases, we do not diagnose, as `X` is not an
459+
// owner, e.g.
460+
// std::optional<string_view> sv = std::optional<Foo>();
461+
if (const auto *PrimaryCtorTemplate =
462+
Ctor->getConstructor()->getPrimaryTemplate();
463+
PrimaryCtorTemplate &&
464+
isCopyLikeConstructor(dyn_cast_if_present<CXXConstructorDecl>(
465+
PrimaryCtorTemplate->getTemplatedDecl()))) {
466+
return false;
467+
}
468+
// Assume that the nested pointer is constructed from the whole RHS.
469+
// E.g. optional<string_view> s = std::string();
470+
return true;
393471
}
394472

395473
// Return true if this is an "normal" assignment operator.

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,12 +655,37 @@ std::vector<std::string_view> test2(int i) {
655655
return std::vector<std::string_view>(t.begin(), t.end());
656656
}
657657

658+
class Foo {
659+
public:
660+
operator std::string_view() const { return ""; }
661+
};
662+
class [[gsl::Owner]] FooOwner {
663+
public:
664+
operator std::string_view() const { return ""; }
665+
};
666+
std::optional<Foo> GetFoo();
667+
std::optional<FooOwner> GetFooOwner();
668+
669+
template <typename T>
670+
struct [[gsl::Owner]] Container1 {
671+
Container1();
672+
};
673+
template <typename T>
674+
struct [[gsl::Owner]] Container2 {
675+
template<typename U>
676+
Container2(const Container1<U>& C2);
677+
};
678+
658679
std::optional<std::string_view> test3(int i) {
659680
std::string s;
660681
std::string_view sv;
661682
if (i)
662683
return s; // expected-warning {{address of stack memory associated}}
663684
return sv; // fine
685+
Container2<std::string_view> c1 = Container1<Foo>(); // no diagnostic as Foo is not an Owner.
686+
Container2<std::string_view> c2 = Container1<FooOwner>(); // expected-warning {{object backing the pointer will be destroyed}}
687+
return GetFoo(); // fine, we don't know Foo is owner or not, be conservative.
688+
return GetFooOwner(); // expected-warning {{returning address of local temporary object}}
664689
}
665690

666691
std::optional<int*> test4(int a) {

0 commit comments

Comments
 (0)