https://github.com/aeft created https://github.com/llvm/llvm-project/pull/187917
- Move `hasOrigins` from free function to `OriginManager` method - Add pre-scan (`collectLifetimeboundOriginTypes`) to register return types of `[[clang::lifetimebound]]` calls before fact generation - Generalize copy/move constructor origin propagation from lambda-only to all types with `isDefaulted()` and `hasOrigins()` guard - Guard `operator=` origin propagation: pointer-like types always propagate; other tracked types only when defaulted - Defer `ThisOrigins` construction until after the pre-scan to avoid origin list depth mismatch - Fix `IsArgLifetimeBound` to exclude constructors from the instance-method branch (latent bug exposed by this change) Fixes #163600 >From 04a5108a652fe4c247832088783a20dc3db409a1 Mon Sep 17 00:00:00 2001 From: Alex Wang <[email protected]> Date: Sat, 21 Mar 2026 23:53:45 -0700 Subject: [PATCH] [LifetimeSafety] Track origins for lifetimebound calls returning record types --- .../Analysis/Analyses/LifetimeSafety/Facts.h | 2 +- .../Analyses/LifetimeSafety/FactsGenerator.h | 3 + .../Analyses/LifetimeSafety/Origins.h | 22 +- .../LifetimeSafety/FactsGenerator.cpp | 41 ++- .../LifetimeSafety/LifetimeSafety.cpp | 3 + clang/lib/Analysis/LifetimeSafety/Origins.cpp | 88 ++++++- clang/test/Sema/warn-lifetime-safety.cpp | 249 +++++++++++++++++- 7 files changed, 380 insertions(+), 28 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index fdcf317c69cbf..93bfae4079e47 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -313,7 +313,7 @@ class TestPointFact : public Fact { class FactManager { public: FactManager(const AnalysisDeclContext &AC, const CFG &Cfg) - : OriginMgr(AC.getASTContext(), AC.getDecl()) { + : OriginMgr(AC.getASTContext()) { BlockToFacts.resize(Cfg.getNumBlockIDs()); } diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index dfcbdc7d73007..775b51afa0237 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -57,6 +57,9 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { OriginList *getOriginsList(const ValueDecl &D); OriginList *getOriginsList(const Expr &E); + bool hasOrigins(QualType QT) const; + bool hasOrigins(const Expr *E) const; + void flow(OriginList *Dst, OriginList *Src, bool Kill); void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr); diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h index 8c638bdcace3f..12148d9b29c9c 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h @@ -20,6 +20,7 @@ #include "clang/AST/TypeBase.h" #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeStats.h" #include "clang/Analysis/Analyses/LifetimeSafety/Utils.h" +#include "clang/Analysis/AnalysisDeclContext.h" #include "llvm/Support/raw_ostream.h" namespace clang::lifetimes::internal { @@ -117,15 +118,17 @@ class OriginList { OriginList *InnerList = nullptr; }; -bool hasOrigins(QualType QT); -bool hasOrigins(const Expr *E); bool doesDeclHaveStorage(const ValueDecl *D); /// Manages the creation, storage, and retrieval of origins for pointer-like /// variables and expressions. class OriginManager { public: - explicit OriginManager(ASTContext &AST, const Decl *D); + explicit OriginManager(ASTContext &AST); + + /// Must be called after collectLifetimeboundOriginTypes() to ensure + /// ThisOrigins reflects the complete set of tracked types. + void initializeThisOrigins(const Decl *D); /// Gets or creates the OriginList for a given ValueDecl. /// @@ -155,11 +158,20 @@ class OriginManager { unsigned getNumOrigins() const { return NextOriginID.Value; } + bool hasOrigins(QualType QT) const; + bool hasOrigins(const Expr *E) const; + void dump(OriginID OID, llvm::raw_ostream &OS) const; /// Collects statistics about expressions that lack associated origins. void collectMissingOrigins(Stmt &FunctionBody, LifetimeSafetyStats &LSStats); + /// Pre-scans the function body (and constructor init lists) to discover + /// return types of [[clang::lifetimebound]] calls, registering them for + /// origin tracking. + void collectLifetimeboundOriginTypes(AnalysisDeclContext &AC); + void registerLifetimeboundOriginType(QualType QT); + private: OriginID getNextOriginID() { return NextOriginID++; } @@ -178,6 +190,10 @@ class OriginManager { llvm::DenseMap<const clang::ValueDecl *, OriginList *> DeclToList; llvm::DenseMap<const clang::Expr *, OriginList *> ExprToList; std::optional<OriginList *> ThisOrigins; + /// Types that are not inherently pointer-like but require origin tracking + /// because they are returned from functions with [[clang::lifetimebound]] + /// parameters. + llvm::DenseSet<const Type *> LifetimeboundOriginTypes; }; } // namespace clang::lifetimes::internal diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 3259505584c9f..890f1d2ba3a7b 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -36,6 +36,14 @@ OriginList *FactsGenerator::getOriginsList(const Expr &E) { return FactMgr.getOriginMgr().getOrCreateList(&E); } +bool FactsGenerator::hasOrigins(QualType QT) const { + return FactMgr.getOriginMgr().hasOrigins(QT); +} + +bool FactsGenerator::hasOrigins(const Expr *E) const { + return FactMgr.getOriginMgr().hasOrigins(E); +} + /// Propagates origin information from Src to Dst through all levels of /// indirection, creating OriginFlowFacts at each level. /// @@ -182,14 +190,13 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) { handleGSLPointerConstruction(CCE); return; } - // Implicit copy/move constructors of lambda closures lack - // [[clang::lifetimebound]], so `handleFunctionCall` cannot propagate origins. - // Handle them directly to keep the origin chain intact (e.g., `return - // lambda;` copies the closure). - if (const auto *RD = CCE->getType()->getAsCXXRecordDecl(); - RD && RD->isLambda() && - CCE->getConstructor()->isCopyOrMoveConstructor() && - CCE->getNumArgs() == 1) { + // For defaulted (implicit or `= default`) copy/move constructors, propagate + // origins directly. User-defined copy/move constructors have opaque semantics + // and fall through to `handleFunctionCall`, where [[clang::lifetimebound]] is + // needed to propagate origins. + if (CCE->getConstructor()->isCopyOrMoveConstructor() && + CCE->getConstructor()->isDefaulted() && CCE->getNumArgs() == 1 && + hasOrigins(CCE->getType())) { const Expr *Arg = CCE->getArg(0); if (OriginList *ArgList = getRValueOrigins(Arg, getOriginsList(*Arg))) { flow(getOriginsList(*CCE), ArgList, /*Kill=*/true); @@ -398,8 +405,20 @@ void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) { // and are handled separately. if (OCE->getOperator() == OO_Equal && OCE->getNumArgs() == 2 && hasOrigins(OCE->getArg(0)->getType())) { - handleAssignment(OCE->getArg(0), OCE->getArg(1)); - return; + // Pointer-like types: assignment inherently propagates origins. + QualType LHSTy = OCE->getArg(0)->getType(); + if (LHSTy->isPointerOrReferenceType() || isGslPointerType(LHSTy)) { + handleAssignment(OCE->getArg(0), OCE->getArg(1)); + return; + } + // Other tracked types: only defaulted operator= propagates origins. + // User-defined operator= has opaque semantics, so don't handle them now. + if (const auto *MD = + dyn_cast_or_null<CXXMethodDecl>(OCE->getDirectCallee()); + MD && MD->isDefaulted()) { + handleAssignment(OCE->getArg(0), OCE->getArg(1)); + return; + } } VisitCallExpr(OCE); } @@ -662,7 +681,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, auto IsArgLifetimeBound = [FD](unsigned I) -> bool { const ParmVarDecl *PVD = nullptr; if (const auto *Method = dyn_cast<CXXMethodDecl>(FD); - Method && Method->isInstance()) { + Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD)) { if (I == 0) // For the 'this' argument, the attribute is on the method itself. return implicitObjectParamIsLifetimeBound(Method) || diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp index 714f979fa5ee7..56a187202d8fa 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp @@ -71,6 +71,9 @@ void LifetimeSafetyAnalysis::run() { FactMgr = std::make_unique<FactManager>(AC, Cfg); + FactMgr->getOriginMgr().collectLifetimeboundOriginTypes(AC); + FactMgr->getOriginMgr().initializeThisOrigins(AC.getDecl()); + FactsGenerator FactGen(*FactMgr, AC); FactGen.run(); diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp index 0122f7a734541..2655835317d9b 100644 --- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp @@ -18,6 +18,7 @@ #include "clang/AST/TypeBase.h" #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeStats.h" +#include "clang/Analysis/AnalysisDeclContext.h" #include "llvm/ADT/StringMap.h" namespace clang::lifetimes::internal { @@ -29,10 +30,10 @@ class MissingOriginCollector public: MissingOriginCollector( const llvm::DenseMap<const clang::Expr *, OriginList *> &ExprToOriginList, - LifetimeSafetyStats &LSStats) - : ExprToOriginList(ExprToOriginList), LSStats(LSStats) {} + const OriginManager &OM, LifetimeSafetyStats &LSStats) + : ExprToOriginList(ExprToOriginList), OM(OM), LSStats(LSStats) {} bool VisitExpr(Expr *E) { - if (!hasOrigins(E)) + if (!OM.hasOrigins(E)) return true; // Check if we have an origin for this expression. if (!ExprToOriginList.contains(E)) { @@ -46,13 +47,60 @@ class MissingOriginCollector private: const llvm::DenseMap<const clang::Expr *, OriginList *> &ExprToOriginList; + const OriginManager &OM; LifetimeSafetyStats &LSStats; }; + +class LifetimeboundOriginTypeCollector + : public RecursiveASTVisitor<LifetimeboundOriginTypeCollector> { +public: + LifetimeboundOriginTypeCollector(OriginManager &OM) : OM(OM) {} + + bool VisitCallExpr(const CallExpr *CE) { + if (const auto *FD = CE->getDirectCallee()) + collect(FD, FD->getReturnType()); + return true; + } + + bool VisitCXXConstructExpr(const CXXConstructExpr *CCE) { + collect(CCE->getConstructor(), CCE->getType()); + return true; + } + + bool shouldVisitLambdaBody() const { return false; } + +private: + OriginManager &OM; + + void collect(const FunctionDecl *FD, QualType RetType) { + if (!FD) + return; + FD = getDeclWithMergedLifetimeBoundAttrs(FD); + + if (const auto *MD = dyn_cast<CXXMethodDecl>(FD); + MD && MD->isInstance() && !isa<CXXConstructorDecl>(MD) && + implicitObjectParamIsLifetimeBound(MD)) { + OM.registerLifetimeboundOriginType(RetType); + return; + } + + for (const auto *Param : FD->parameters()) { + if (Param->hasAttr<LifetimeBoundAttr>()) { + OM.registerLifetimeboundOriginType(RetType); + return; + } + } + } +}; + } // namespace -bool hasOrigins(QualType QT) { +bool OriginManager::hasOrigins(QualType QT) const { if (QT->isPointerOrReferenceType() || isGslPointerType(QT)) return true; + if (LifetimeboundOriginTypes.contains( + QT->getCanonicalTypeUnqualified().getTypePtr())) + return true; const auto *RD = QT->getAsCXXRecordDecl(); if (!RD) return false; @@ -70,7 +118,9 @@ bool hasOrigins(QualType QT) { /// /// An expression has origins if: /// - It's a glvalue (has addressable storage), OR -/// - Its type is pointer-like (pointer, reference, or gsl::Pointer) +/// - Its type is pointer-like (pointer, reference, or gsl::Pointer), OR +/// - Its type is registered for origin tracking (e.g., return type of a +/// [[clang::lifetimebound]] function) /// /// Examples: /// - `int x; x` : has origin (glvalue) @@ -78,7 +128,7 @@ bool hasOrigins(QualType QT) { /// - `std::string_view{}` : has 1 origin (prvalue of pointer type) /// - `42` : no origin (prvalue of non-pointer type) /// - `x + y` : (where x, y are int) → no origin (prvalue of non-pointer type) -bool hasOrigins(const Expr *E) { +bool OriginManager::hasOrigins(const Expr *E) const { return E->isGLValue() || hasOrigins(E->getType()); } @@ -99,8 +149,9 @@ bool doesDeclHaveStorage(const ValueDecl *D) { return !D->getType()->isReferenceType(); } -OriginManager::OriginManager(ASTContext &AST, const Decl *D) : AST(AST) { - // Create OriginList for 'this' expr. +OriginManager::OriginManager(ASTContext &AST) : AST(AST) {} + +void OriginManager::initializeThisOrigins(const Decl *D) { const auto *MD = llvm::dyn_cast_or_null<CXXMethodDecl>(D); if (!MD || !MD->isInstance()) return; @@ -232,8 +283,27 @@ const Origin &OriginManager::getOrigin(OriginID ID) const { void OriginManager::collectMissingOrigins(Stmt &FunctionBody, LifetimeSafetyStats &LSStats) { - MissingOriginCollector Collector(this->ExprToList, LSStats); + MissingOriginCollector Collector(this->ExprToList, *this, LSStats); Collector.TraverseStmt(const_cast<Stmt *>(&FunctionBody)); } +void OriginManager::collectLifetimeboundOriginTypes(AnalysisDeclContext &AC) { + LifetimeboundOriginTypeCollector Collector(*this); + if (Stmt *Body = AC.getBody()) + Collector.TraverseStmt(Body); + if (const auto *CD = dyn_cast<CXXConstructorDecl>(AC.getDecl())) + for (const auto *Init : CD->inits()) + Collector.TraverseStmt(Init->getInit()); +} + +void OriginManager::registerLifetimeboundOriginType(QualType QT) { + // TODO: Support [[gsl::Owner]] return types. For now, skip them because they + // change owner origin-list shape and can break GSL construction flow. + if (!QT->getAsCXXRecordDecl() || isGslOwnerType(QT) || hasOrigins(QT)) + return; + + LifetimeboundOriginTypes.insert( + QT->getCanonicalTypeUnqualified().getTypePtr()); +} + } // namespace clang::lifetimes::internal diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index bd09bb70e9a11..a8c0efeb875c0 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -909,7 +909,6 @@ void lifetimebound_return_reference() { (void)*ptr; // expected-note {{later used here}} } -// FIXME: No warning for non gsl::Pointer types. Origin tracking is only supported for pointer types. struct LifetimeBoundCtor { LifetimeBoundCtor(); LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]); @@ -919,9 +918,9 @@ void lifetimebound_ctor() { LifetimeBoundCtor v; { MyObj obj; - v = obj; - } - (void)v; + v = obj; // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)v; // expected-note {{later used here}} } View lifetimebound_return_of_local() { @@ -2102,3 +2101,245 @@ void pointer_in_array_use_after_scope() { } } // namespace array + +namespace track_origins_for_lifetimebound_record_type { + +template <class T> void use(T); + +struct S { + S(); + S(const std::string &s [[clang::lifetimebound]]); + + S return_self_after_registration() const; +}; + +S getS(const std::string &s [[clang::lifetimebound]]); + +void from_free_function() { + S s = getS(std::string("temp")); // expected-warning {{object whose reference is captured does not live long enough}} \ + // expected-note {{destroyed here}} + use(s); // expected-note {{later used here}} +} + +void from_constructor() { + S s(std::string("temp")); // expected-warning {{object whose reference is captured does not live long enough}} \ + // expected-note {{destroyed here}} + use(s); // expected-note {{later used here}} +} + +struct Factory { + S make(const std::string &s [[clang::lifetimebound]]); + static S create(const std::string &s [[clang::lifetimebound]]); + S makeThis() const [[clang::lifetimebound]]; +}; + +void from_method() { + Factory f; + S s = f.make(std::string("temp")); // expected-warning {{object whose reference is captured does not live long enough}} \ + // expected-note {{destroyed here}} + use(s); // expected-note {{later used here}} +} + +void from_static_method() { + S s = Factory::create(std::string("temp")); // expected-warning {{object whose reference is captured does not live long enough}} \ + // expected-note {{destroyed here}} + use(s); // expected-note {{later used here}} +} + +void from_lifetimebound_this_method() { + S value; + { + Factory f; + value = f.makeThis(); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + use(value); // expected-note {{later used here}} +} + +void across_scope() { + S s{}; + { + std::string str{"abc"}; + s = getS(str); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + use(s); // expected-note {{later used here}} +} + +void same_scope() { + std::string str{"abc"}; + S s = getS(str); + use(s); +} + +S copy_propagation() { + std::string str{"abc"}; + S a = getS(str); // expected-warning {{address of stack memory is returned later}} + S b = a; + return b; // expected-note {{returned here}} +} + +void assignment_propagation() { + S a, b; + { + std::string str{"abc"}; + a = getS(str); // expected-warning {{object whose reference is captured does not live long enough}} + b = a; + } // expected-note {{destroyed here}} + use(b); // expected-note {{later used here}} +} + +S getSNoAnnotation(const std::string &s); + +void no_annotation() { + S s = getSNoAnnotation(std::string("temp")); + use(s); +} + +void mix_annotated_and_not() { + S s1 = getS(std::string("temp")); // expected-warning {{object whose reference is captured does not live long enough}} \ + // expected-note {{destroyed here}} + S s2 = getSNoAnnotation(std::string("temp")); + use(s1); // expected-note {{later used here}} + use(s2); +} + +S getS2(const std::string &a [[clang::lifetimebound]], const std::string &b [[clang::lifetimebound]]); + +S multiple_lifetimebound_params() { + std::string str{"abc"}; + S s = getS2(str, std::string("temp")); // expected-warning {{address of stack memory is returned later}} \ + // expected-warning {{object whose reference is captured does not live long enough}} \ + // expected-note {{destroyed here}} + return s; // expected-note {{returned here}} \ + // expected-note {{later used here}} +} + +int getInt(const std::string &s [[clang::lifetimebound]]); + +void primitive_return() { + int i = getInt(std::string("temp")); + use(i); +} + +template <class T> +T make(const std::string &s [[clang::lifetimebound]]); + +void from_template_instantiation() { + S s = make<S>(std::string("temp")); // expected-warning {{object whose reference is captured does not live long enough}} \ + // expected-note {{destroyed here}} + use(s); // expected-note {{later used here}} +} + +struct FieldInitFromLifetimebound { + S value; // function-note {{this field dangles}} + FieldInitFromLifetimebound() : value(getS(std::string("temp"))) {} // function-warning {{address of stack memory escapes to a field}} +}; + +S S::return_self_after_registration() const { + std::string s{"abc"}; + getS(s); + return *this; +} + +struct SWithUserDefinedCopyLikeOps { + SWithUserDefinedCopyLikeOps(); + SWithUserDefinedCopyLikeOps(const std::string &s [[clang::lifetimebound]]) : owned(s), data(s) {} + + SWithUserDefinedCopyLikeOps(const SWithUserDefinedCopyLikeOps &other) : owned("copy"), data(owned) {} + + SWithUserDefinedCopyLikeOps &operator=(const SWithUserDefinedCopyLikeOps &) { + owned = "copy"; + data = owned; + return *this; + } + + std::string owned; + std::string_view data; +}; + +SWithUserDefinedCopyLikeOps getSWithUserDefinedCopyLikeOps(const std::string &s [[clang::lifetimebound]]); + +SWithUserDefinedCopyLikeOps user_defined_copy_ctor_should_not_assume_origin_propagation() { + std::string str{"abc"}; + SWithUserDefinedCopyLikeOps s = getSWithUserDefinedCopyLikeOps(str); + SWithUserDefinedCopyLikeOps copy = s; // Copy is rescued by user-defined copy constructor, so should not warn. + return copy; +} + +void user_defined_assignment_should_not_assume_origin_propagation() { + SWithUserDefinedCopyLikeOps dst; + { + std::string str{"abc"}; + SWithUserDefinedCopyLikeOps src = getSWithUserDefinedCopyLikeOps(str); + dst = src; + } + use(dst); +} + +struct SWithOriginPropagatingCopy { + SWithOriginPropagatingCopy(); + SWithOriginPropagatingCopy(const std::string &s [[clang::lifetimebound]]) : data(s) {} + SWithOriginPropagatingCopy(const SWithOriginPropagatingCopy &other) : data(other.data) {} + std::string_view data; +}; + +SWithOriginPropagatingCopy getSWithOriginPropagatingCopy(const std::string &s [[clang::lifetimebound]]); + +// FIXME: False negative. User-defined copy ctor may propagate origins. +SWithOriginPropagatingCopy user_defined_copy_with_origin_propagation() { + std::string str{"abc"}; + SWithOriginPropagatingCopy s = getSWithOriginPropagatingCopy(str); + SWithOriginPropagatingCopy copy = s; + return copy; // Should warn. +} + +struct DefaultedOuter { + DefaultedOuter(); + DefaultedOuter(const std::string &s [[clang::lifetimebound]]) : inner(s) {} + SWithUserDefinedCopyLikeOps inner; +}; + +DefaultedOuter getDefaultedOuter(const std::string &s [[clang::lifetimebound]]); + +// FIXME: False positive. The defaulted outer copy ctor invokes +// SWithUserDefinedCopyLikeOps's user-defined copy ctor, so `copy` should be +// semantically safe. +DefaultedOuter nested_defaulted_outer_with_user_defined_inner() { + std::string str{"abc"}; + DefaultedOuter o = getDefaultedOuter(str); // expected-warning {{address of stack memory is returned later}} + DefaultedOuter copy = o; + return copy; // expected-note {{returned here}} +} + +std::string_view getSV(S s [[clang::lifetimebound]]); + +// FIXME: False negative. Non-pointer/ref/gsl::Pointer parameter types marked +// [[clang::lifetimebound]] are not registered for origin tracking. +void dangling_view_from_non_pointer_param() { + std::string_view sv; + { + S s; + sv = getSV(s); + } + use(sv); // Should warn. +} + +const S &getRef(const std::string &s [[clang::lifetimebound]]); + +// FIXME: False negative. The analysis tracks the returned reference, +// but loses that information when it is copied into a new `S` object. +S from_ref() { + std::string str{"abc"}; + S s = getRef(str); + return s; // Should warn. +} + +MyObj getMyObj(const MyObj &obj [[clang::lifetimebound]]); + +void gsl_owner_return_does_not_crash() { + MyObj obj; + View v = obj; + getMyObj(obj); + use(v); +} + +} // namespace track_origins_for_lifetimebound_record_type _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
