https://github.com/iitianpushkar updated https://github.com/llvm/llvm-project/pull/206337
>From 666b03577b08a11d6c0c5df8a4daab2c4080cf2c Mon Sep 17 00:00:00 2001 From: iitianpushkar <[email protected]> Date: Tue, 7 Jul 2026 02:01:05 +0530 Subject: [PATCH 1/2] rebased branch on latest main and updated patch accordingly --- .../Analysis/Analyses/LifetimeSafety/Facts.h | 19 +++ .../Analyses/LifetimeSafety/LifetimeSafety.h | 9 +- .../Analyses/LifetimeSafety/LoanPropagation.h | 9 ++ .../clang/Basic/DiagnosticSemaKinds.td | 2 + clang/lib/Analysis/LifetimeSafety/Checker.cpp | 20 +-- .../LifetimeSafety/FactsGenerator.cpp | 57 ++++++-- .../LifetimeSafety/LoanPropagation.cpp | 62 +++++++- clang/lib/Sema/SemaLifetimeSafety.h | 49 +++++-- .../LifetimeSafety/annotation-suggestions.cpp | 12 +- clang/test/Sema/LifetimeSafety/nocfg.cpp | 26 ++-- clang/test/Sema/LifetimeSafety/safety-c.c | 6 +- clang/test/Sema/LifetimeSafety/safety.cpp | 137 +++++++++--------- 12 files changed, 273 insertions(+), 135 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index 94db2a7f311ae..2e426105d1c84 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -20,6 +20,7 @@ #include "clang/Analysis/Analyses/LifetimeSafety/Utils.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Debug.h" @@ -33,6 +34,11 @@ class LoanPropagationAnalysis; using FactID = utils::ID<struct FactTag>; +struct LifetimeBoundParamInfo { + const ParmVarDecl *Param = nullptr; + bool IsImplicitObject = false; +}; + /// An abstract base class for a single, atomic lifetime-relevant event. class Fact { @@ -393,6 +399,17 @@ class FactManager { const LoanManager &getLoanMgr() const { return LoanMgr; } OriginManager &getOriginMgr() { return OriginMgr; } const OriginManager &getOriginMgr() const { return OriginMgr; } + void setLifetimeBoundParamInfo(const OriginFlowFact *F, + LifetimeBoundParamInfo Info) { + LifetimeBoundParamInfos.try_emplace(F, Info); + } + std::optional<LifetimeBoundParamInfo> + getLifetimeBoundParamInfo(const OriginFlowFact *F) const { + auto It = LifetimeBoundParamInfos.find(F); + if (It == LifetimeBoundParamInfos.end()) + return std::nullopt; + return It->second; + } private: FactID NextFactID{0}; @@ -400,6 +417,8 @@ class FactManager { OriginManager OriginMgr; /// Facts for each CFG block, indexed by block ID. llvm::SmallVector<llvm::SmallVector<const Fact *>> BlockToFacts; + llvm::DenseMap<const OriginFlowFact *, LifetimeBoundParamInfo> + LifetimeBoundParamInfos; llvm::BumpPtrAllocator FactAllocator; }; } // namespace clang::lifetimes::internal diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 80a23f18baebd..6204381a873ee 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -31,6 +31,7 @@ #include "llvm/ADT/PointerUnion.h" #include <cstddef> #include <memory> +#include <optional> namespace clang::lifetimes { @@ -49,6 +50,11 @@ enum class WarningScope { IntraTU // For warnings on functions local to a Translation Unit. }; +struct AliasChainEntry { + const Expr *E = nullptr; + std::optional<internal::LifetimeBoundParamInfo> LifetimeBound; +}; + /// Abstract interface for operations requiring Sema access. /// /// This class exists to break a circular dependency: the LifetimeSafety @@ -67,7 +73,8 @@ class LifetimeSafetySemaHelper { virtual void reportUseAfterScope(const Expr *IssueExpr, const Expr *UseExpr, const Expr *MovedExpr, SourceLocation FreeLoc, - llvm::ArrayRef<const Expr *> ExprChain) {} + llvm::ArrayRef<AliasChainEntry> AliasChain) { + } virtual void reportUseAfterReturn(const Expr *IssueExpr, const Expr *ReturnExpr, diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h index 838daa024c953..616394314056f 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h @@ -56,6 +56,15 @@ class LoanPropagationAnalysis { const LoanID TargetLoan, const CFG *Cfg) const; + llvm::SmallVector<const OriginFlowFact *> + buildOriginFlowChainWithFacts(ProgramPoint StartPoint, + const OriginID StartOID, + const LoanID TargetLoan, const CFG *Cfg) const; + + llvm::SmallVector<const OriginFlowFact *> + buildOriginFlowChainWithFacts(const UseFact *UF, const LoanID TargetLoan, + const CFG *Cfg) const; + private: class Impl; std::unique_ptr<Impl> PImpl; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 05f49a4dbd1ca..ed1e4350890b5 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11078,6 +11078,8 @@ def note_lifetime_safety_escapes_to_global_here: Note<"escapes to this global st def note_lifetime_safety_escapes_to_static_storage_here: Note<"escapes to this static storage">; def note_lifetime_safety_lifetimebound_here: Note<"'lifetimebound' attribute appears here on the definition">; def note_lifetime_safety_aliases_storage : Note<"%0 aliases the storage of %1">; +def note_lifetime_safety_aliases_storage_lifetimebound : Note< + "%0 aliases the storage of %1 because %2 is marked 'lifetimebound'">; def warn_lifetime_safety_intra_tu_param_suggestion : Warning<"parameter in intra-TU function should be marked " diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index f72f7f80abbc0..2a3acdeb4b1ca 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -274,7 +274,8 @@ class LifetimeChecker { // Scope-based expiry (use-after-scope). SemaHelper->reportUseAfterScope( IssueExpr, UF->getUseExpr(), MovedExpr, ExpiryLoc, - getExprChain(LoanPropagation.buildOriginFlowChain(UF, LID, Cfg))); + getAliasChain( + LoanPropagation.buildOriginFlowChainWithFacts(UF, LID, Cfg))); } else if (const auto *OEF = CausingFact.dyn_cast<const OriginEscapesFact *>()) { @@ -526,14 +527,15 @@ class LifetimeChecker { /// Given a chain of origins that shows how a loan propagates, this function /// extracts the corresponding expressions for each origin. Origins that refer /// to declarations (rather than expressions) are skipped. - llvm::SmallVector<const Expr *> - getExprChain(llvm::ArrayRef<OriginID> OriginFlowChain) { - llvm::SmallVector<const Expr *> rs; - for (const OriginID CurrOID : OriginFlowChain) - if (const Expr *CurrExpr = - FactMgr.getOriginMgr().getOrigin(CurrOID).getExpr()) - rs.push_back(CurrExpr); - return rs; + llvm::SmallVector<AliasChainEntry> + getAliasChain(llvm::ArrayRef<const OriginFlowFact *> OriginFlowChain) { + llvm::SmallVector<AliasChainEntry> Result; + for (const OriginFlowFact *Flow : OriginFlowChain) + if (const Expr *CurrExpr = FactMgr.getOriginMgr() + .getOrigin(Flow->getSrcOriginID()) + .getExpr()) + Result.push_back({CurrExpr, FactMgr.getLifetimeBoundParamInfo(Flow)}); + return Result; } }; } // namespace diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 095bee8135c20..83ff0d1265658 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -1096,28 +1096,45 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, flow(CallList, getOriginsList(*Args[0]), /*Kill=*/true); return; } - auto IsArgLifetimeBound = [FD, &Args](unsigned I) -> bool { + auto GetLifetimeBoundParamInfo = + [FD](unsigned I) -> std::optional<LifetimeBoundParamInfo> { const ParmVarDecl *PVD = nullptr; if (const auto *Method = dyn_cast<CXXMethodDecl>(FD); Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD)) { if (I == 0) // For the 'this' argument, the attribute is on the method itself. - return implicitObjectParamIsLifetimeBound(Method) || - shouldTrackImplicitObjectArg( - *Args[0], Method, /*RunningUnderLifetimeSafety=*/true); + return implicitObjectParamIsLifetimeBound(Method) + ? std::optional<LifetimeBoundParamInfo>( + LifetimeBoundParamInfo{/*Param=*/nullptr, + /*IsImplicitObject=*/true}) + : std::nullopt; if ((I - 1) < Method->getNumParams()) // For explicit arguments, find the corresponding parameter // declaration. PVD = Method->getParamDecl(I - 1); - } else if (I == 0 && shouldTrackFirstArgument(FD)) { - return true; - } else if (I == 1 && shouldTrackSecondArgument(FD)) { - return true; } else if (I < FD->getNumParams()) { // For free functions or static methods. PVD = FD->getParamDecl(I); } - return PVD ? PVD->hasAttr<clang::LifetimeBoundAttr>() : false; + if (PVD && PVD->hasAttr<clang::LifetimeBoundAttr>()) + return LifetimeBoundParamInfo{PVD, /*IsImplicitObject=*/false}; + return std::nullopt; + }; + auto ShouldTrackArg = + [FD, &Args](unsigned I, + std::optional<LifetimeBoundParamInfo> Info) -> bool { + if (Info) + return true; + if (const auto *Method = dyn_cast<CXXMethodDecl>(FD); + Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD)) + if (I == 0) + return shouldTrackImplicitObjectArg( + *Args[0], Method, /*RunningUnderLifetimeSafety=*/true); + if (I == 0 && shouldTrackFirstArgument(FD)) + return true; + if (I == 1 && shouldTrackSecondArgument(FD)) + return true; + return false; }; auto shouldTrackPointerImplicitObjectArg = [FD, &Args](unsigned I) -> bool { const auto *Method = dyn_cast<CXXMethodDecl>(FD); @@ -1135,6 +1152,9 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, OriginList *ArgList = getOriginsList(*Args[I]); if (!ArgList) continue; + std::optional<LifetimeBoundParamInfo> LifetimeBoundInfo = + GetLifetimeBoundParamInfo(I); + bool TrackArg = ShouldTrackArg(I, LifetimeBoundInfo); if (IsGslConstruction) { // TODO: document with code example. // std::string_view(const std::string_view& from) @@ -1150,16 +1170,18 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc)); KillSrc = false; - } else if (IsArgLifetimeBound(I)) { + } else if (TrackArg) { // Only flow the outer origin here. For lifetimebound args in // gsl::Pointer construction, we do not have enough information to // safely match inner origins, so the source and // destination origin lists may have different lengths. // FIXME: Handle origin-shape mismatches gracefully so we can also flow // inner origins. - CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( - CallList->getOuterOriginID(), ArgList->getOuterOriginID(), - KillSrc)); + auto *F = FactMgr.createFact<OriginFlowFact>( + CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc); + if (LifetimeBoundInfo) + FactMgr.setLifetimeBoundParamInfo(F, *LifetimeBoundInfo); + CurrentBlockFacts.push_back(F); KillSrc = false; } } else if (shouldTrackPointerImplicitObjectArg(I)) { @@ -1170,12 +1192,15 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, CallList->getOuterOriginID(), ArgList->peelOuterOrigin()->getOuterOriginID(), KillSrc)); KillSrc = false; - } else if (IsArgLifetimeBound(I)) { + } else if (TrackArg) { // Lifetimebound on a non-GSL-ctor function means the returned // pointer/reference itself must not outlive the arguments. This // only constrains the top-level origin. - CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( - CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc)); + auto *F = FactMgr.createFact<OriginFlowFact>( + CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc); + if (LifetimeBoundInfo) + FactMgr.setLifetimeBoundParamInfo(F, *LifetimeBoundInfo); + CurrentBlockFacts.push_back(F); KillSrc = false; } } diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index 078892bd48c10..5744479737b0a 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -207,6 +207,17 @@ class AnalysisImpl const OriginID StartOID, const LoanID TargetLoan, const CFG *Cfg) const { + llvm::SmallVector<OriginID> OriginFlowChain; + for (const OriginFlowFact *Flow : + buildOriginFlowChainWithFacts(StartPoint, StartOID, TargetLoan, Cfg)) + OriginFlowChain.push_back(Flow->getSrcOriginID()); + return OriginFlowChain; + } + + llvm::SmallVector<const OriginFlowFact *> + buildOriginFlowChainWithFacts(ProgramPoint StartPoint, + const OriginID StartOID, + const LoanID TargetLoan, const CFG *Cfg) const { assert(getLoans(StartOID, StartPoint).contains(TargetLoan) && "TargetLoan must be present in the StartOID at the StartPoint"); @@ -225,7 +236,7 @@ class AnalysisImpl using SearchState = std::pair<const CFGBlock *, OriginID>; struct DFSNode { SearchState CurrState; - llvm::SmallVector<OriginID> OriginFlowChain; + llvm::SmallVector<const OriginFlowFact *> OriginFlowChain; }; llvm::SmallVector<DFSNode> PendingStates; @@ -239,10 +250,10 @@ class AnalysisImpl // Trace origins within the current block const auto [BuildResult, Complete] = - buildOriginFlowChain(CurrBlock, CurrOID, TargetLoan); + buildOriginFlowChainWithFacts(CurrBlock, CurrOID, TargetLoan); if (!BuildResult.empty()) { CurrNode.OriginFlowChain.append(BuildResult); - CurrOID = BuildResult.back(); + CurrOID = BuildResult.back()->getSrcOriginID(); } // If we found the IssueFact, we're done @@ -266,11 +277,21 @@ class AnalysisImpl llvm::SmallVector<OriginID> buildOriginFlowChain(const UseFact *UF, const LoanID TargetLoan, const CFG *Cfg) const { + llvm::SmallVector<OriginID> OriginFlowChain; + for (const OriginFlowFact *Flow : + buildOriginFlowChainWithFacts(UF, TargetLoan, Cfg)) + OriginFlowChain.push_back(Flow->getSrcOriginID()); + return OriginFlowChain; + } + + llvm::SmallVector<const OriginFlowFact *> + buildOriginFlowChainWithFacts(const UseFact *UF, const LoanID TargetLoan, + const CFG *Cfg) const { for (const OriginList *Cur = UF->getUsedOrigins(); Cur; Cur = Cur->peelOuterOrigin()) if (getLoans(Cur->getOuterOriginID(), UF).contains(TargetLoan)) - return buildOriginFlowChain(UF, Cur->getOuterOriginID(), TargetLoan, - Cfg); + return buildOriginFlowChainWithFacts(UF, Cur->getOuterOriginID(), + TargetLoan, Cfg); return {}; } @@ -308,8 +329,19 @@ class AnalysisImpl std::pair<llvm::SmallVector<OriginID>, bool> buildOriginFlowChain(const CFGBlock *Block, const OriginID StartOID, const LoanID TargetLoan) const { - OriginID CurrOID = StartOID; llvm::SmallVector<OriginID> OriginFlowChain; + auto [FlowChain, Complete] = + buildOriginFlowChainWithFacts(Block, StartOID, TargetLoan); + for (const OriginFlowFact *Flow : FlowChain) + OriginFlowChain.push_back(Flow->getSrcOriginID()); + return {OriginFlowChain, Complete}; + } + + std::pair<llvm::SmallVector<const OriginFlowFact *>, bool> + buildOriginFlowChainWithFacts(const CFGBlock *Block, const OriginID StartOID, + const LoanID TargetLoan) const { + OriginID CurrOID = StartOID; + llvm::SmallVector<const OriginFlowFact *> OriginFlowChain; for (const Fact *F : llvm::reverse(FactMgr.getFacts(Block))) { if (const auto *IF = F->getAs<IssueFact>()) @@ -324,7 +356,7 @@ class AnalysisImpl if (!getLoans(SrcOriginID, OFF).contains(TargetLoan)) continue; - OriginFlowChain.push_back(SrcOriginID); + OriginFlowChain.push_back(OFF); CurrOID = SrcOriginID; } @@ -369,4 +401,20 @@ llvm::SmallVector<OriginID> LoanPropagationAnalysis::buildOriginFlowChain( const UseFact *UF, const LoanID TargetLoan, const CFG *Cfg) const { return PImpl->buildOriginFlowChain(UF, TargetLoan, Cfg); } + +llvm::SmallVector<const OriginFlowFact *> +LoanPropagationAnalysis::buildOriginFlowChainWithFacts(ProgramPoint StartPoint, + const OriginID StartOID, + const LoanID TargetLoan, + const CFG *Cfg) const { + return PImpl->buildOriginFlowChainWithFacts(StartPoint, StartOID, TargetLoan, + Cfg); +} + +llvm::SmallVector<const OriginFlowFact *> +LoanPropagationAnalysis::buildOriginFlowChainWithFacts(const UseFact *UF, + const LoanID TargetLoan, + const CFG *Cfg) const { + return PImpl->buildOriginFlowChainWithFacts(UF, TargetLoan, Cfg); +} } // namespace clang::lifetimes::internal diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 48edf5f3f070f..7b628669ed0cf 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -105,9 +105,10 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { public: LifetimeSafetySemaHelperImpl(Sema &S) : S(S) {} - void reportUseAfterScope(const Expr *IssueExpr, const Expr *UseExpr, - const Expr *MovedExpr, SourceLocation FreeLoc, - llvm::ArrayRef<const Expr *> ExprChain) override { + void + reportUseAfterScope(const Expr *IssueExpr, const Expr *UseExpr, + const Expr *MovedExpr, SourceLocation FreeLoc, + llvm::ArrayRef<AliasChainEntry> AliasChain) override { unsigned DiagID = MovedExpr ? diag::warn_lifetime_safety_use_after_scope_moved : diag::warn_lifetime_safety_use_after_scope; @@ -121,7 +122,7 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { S.Diag(FreeLoc, diag::note_lifetime_safety_destroyed_here) << DestroyedSubject; - reportAliasingChain(ExprChain); + reportAliasingChain(AliasChain); S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here) << UseExpr->getSourceRange(); @@ -664,20 +665,44 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { return CurrExpr->getSourceRange() != LastExpr->getSourceRange(); } - void reportAliasingChain(llvm::ArrayRef<const Expr *> OriginExprChain) { + void reportAliasingChain(llvm::ArrayRef<AliasChainEntry> OriginExprChain) { if (OriginExprChain.empty()) return; - const Expr *LastExpr = OriginExprChain.back(); + AliasChainEntry Last = OriginExprChain.back(); + const Expr *LastExpr = Last.E; std::string IssueStr = getDiagSubjectDescription(LastExpr); - for (const Expr *CurrExpr : reverse(OriginExprChain.drop_back())) { - if (!shouldShowInAliasChain(CurrExpr, LastExpr)) + for (AliasChainEntry Curr : reverse(OriginExprChain.drop_back())) { + const Expr *CurrExpr = Curr.E; + if (!shouldShowInAliasChain(CurrExpr, LastExpr)) { + if (!Last.LifetimeBound && Curr.LifetimeBound) + Last.LifetimeBound = Curr.LifetimeBound; continue; - S.Diag(CurrExpr->getBeginLoc(), - diag::note_lifetime_safety_aliases_storage) - << CurrExpr->getSourceRange() << getDiagSubjectDescription(CurrExpr) - << IssueStr; + } + if (Last.LifetimeBound) { + std::string LifetimeBoundSubject; + if (Last.LifetimeBound->IsImplicitObject) { + LifetimeBoundSubject = "the implicit object parameter"; + } else { + LifetimeBoundSubject = "parameter "; + if (Last.LifetimeBound->Param && + Last.LifetimeBound->Param->getIdentifier()) + LifetimeBoundSubject += + "'" + Last.LifetimeBound->Param->getNameAsString() + "'"; + else + LifetimeBoundSubject += "'<unnamed>'"; + } + S.Diag(CurrExpr->getBeginLoc(), + diag::note_lifetime_safety_aliases_storage_lifetimebound) + << CurrExpr->getSourceRange() << getDiagSubjectDescription(CurrExpr) + << IssueStr << LifetimeBoundSubject; + } else + S.Diag(CurrExpr->getBeginLoc(), + diag::note_lifetime_safety_aliases_storage) + << CurrExpr->getSourceRange() << getDiagSubjectDescription(CurrExpr) + << IssueStr; + Last = Curr; LastExpr = CurrExpr; } } diff --git a/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp b/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp index 56ed4a6dc7106..f744e958c4d0b 100644 --- a/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp +++ b/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp @@ -278,21 +278,21 @@ View return_view_field(const ViewProvider& v) { // expected-warning {{paramet void test_get_on_temporary_pointer() { const ReturnsSelf* s_ref = &ReturnsSelf().get(); // expected-warning {{temporary object does not live long enough}}. // expected-note@-1 {{temporary object is destroyed here}} - // expected-note@-2 {{result of call to 'get' aliases the storage of temporary object}} + // expected-note@-2 {{result of call to 'get' aliases the storage of temporary object because the implicit object parameter is marked 'lifetimebound'}} (void)s_ref; // expected-note {{later used here}} } void test_get_on_temporary_ref() { const ReturnsSelf& s_ref = ReturnsSelf().get(); // expected-warning {{temporary object does not live long enough}}. // expected-note@-1 {{temporary object is destroyed here}} - // expected-note@-2 {{result of call to 'get' aliases the storage of temporary object}} + // expected-note@-2 {{result of call to 'get' aliases the storage of temporary object because the implicit object parameter is marked 'lifetimebound'}} (void)s_ref; // expected-note {{later used here}} } void test_getView_on_temporary() { View sv = ViewProvider{1}.getView(); // expected-warning {{temporary object does not live long enough}}. // expected-note@-1 {{temporary object is destroyed here}} - // expected-note@-2 {{result of call to 'getView' aliases the storage of temporary object}} + // expected-note@-2 {{result of call to 'getView' aliases the storage of temporary object because the implicit object parameter is marked 'lifetimebound'}} (void)sv; // expected-note {{later used here}} } @@ -603,7 +603,7 @@ void uaf_via_inferred_lifetimebound() { { int local; f = return_lambda_capturing_param(local); // expected-warning {{local variable 'local' does not live long enough}} \ - // expected-note {{result of call to 'return_lambda_capturing_param' aliases the storage of local variable 'local'}} + // expected-note {{result of call to 'return_lambda_capturing_param' aliases the storage of local variable 'local' because parameter 'x' is marked 'lifetimebound'}} } // expected-note {{local variable 'local' is destroyed here}} (void)f; // expected-note {{later used here}} } @@ -627,7 +627,7 @@ void test_inference() { { MyObj obj; ptr = create_target(obj); // expected-warning {{local variable 'obj' does not live long enough}} \ - // expected-note {{result of call to 'create_target' aliases the storage of local variable 'obj'}} + // expected-note {{result of call to 'create_target' aliases the storage of local variable 'obj' because parameter 'obj' is marked 'lifetimebound'}} } // expected-note {{local variable 'obj' is destroyed here}} (void)ptr; // expected-note {{later used here}} } @@ -642,7 +642,7 @@ View* MakeView(const MyObj& in) { // expected-warning {{parameter in intra-TU fu void test_new_allocation() { View* v = MakeView(MyObj{}); // expected-warning {{temporary object does not live long enough}} \ // expected-note {{temporary object is destroyed here}} \ - // expected-note {{result of call to 'MakeView' aliases the storage of temporary object}} + // expected-note {{result of call to 'MakeView' aliases the storage of temporary object because parameter 'in' is marked 'lifetimebound'}} (void)v; // expected-note {{later used here}} } diff --git a/clang/test/Sema/LifetimeSafety/nocfg.cpp b/clang/test/Sema/LifetimeSafety/nocfg.cpp index b9a6150daf712..d7f3b52460fe9 100644 --- a/clang/test/Sema/LifetimeSafety/nocfg.cpp +++ b/clang/test/Sema/LifetimeSafety/nocfg.cpp @@ -621,7 +621,7 @@ std::string_view ReturnStringView(std::string_view abc [[clang::lifetimebound]]) void test() { std::string_view svjkk1 = ReturnStringView(StrCat("bar", "x")); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \ // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \ - // cfg-note {{result of call to 'ReturnStringView' aliases the storage of temporary object}} + // cfg-note {{result of call to 'ReturnStringView' aliases the storage of temporary object because parameter 'abc' is marked 'lifetimebound'}} use(svjkk1); // cfg-note {{later used here}} } } // namespace GH100549 @@ -872,11 +872,11 @@ std::string_view TakeStr(std::string abc [[clang::lifetimebound]]); std::string_view test1_1() { std::string_view t1 = Ref(std::string()); // expected-warning {{object backing}} \ // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \ - // cfg-note {{result of call to 'Ref' aliases the storage of temporary object}} + // cfg-note {{result of call to 'Ref' aliases the storage of temporary object because parameter 'abc' is marked 'lifetimebound'}} use(t1); // cfg-note {{later used here}} t1 = Ref(std::string()); // expected-warning {{object backing}} \ // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \ - // cfg-note {{result of call to 'Ref' aliases the storage of temporary object}} + // cfg-note {{result of call to 'Ref' aliases the storage of temporary object because parameter 'abc' is marked 'lifetimebound'}} use(t1); // cfg-note {{later used here}} return Ref(std::string()); // expected-warning {{returning address}} \ // cfg-warning {{stack memory associated with temporary object is returned}} cfg-note {{returned here}} @@ -885,11 +885,11 @@ std::string_view test1_1() { std::string_view test1_2() { std::string_view t2 = TakeSv(std::string()); // expected-warning {{object backing}} \ // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \ - // cfg-note {{result of call to 'TakeSv' aliases the storage of temporary object}} + // cfg-note {{result of call to 'TakeSv' aliases the storage of temporary object because parameter 'abc' is marked 'lifetimebound'}} use(t2); // cfg-note {{later used here}} t2 = TakeSv(std::string()); // expected-warning {{object backing}} \ // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \ - // cfg-note {{result of call to 'TakeSv' aliases the storage of temporary object}} + // cfg-note {{result of call to 'TakeSv' aliases the storage of temporary object because parameter 'abc' is marked 'lifetimebound'}} use(t2); // cfg-note {{later used here}} return TakeSv(std::string()); // expected-warning {{returning address}} \ @@ -899,11 +899,11 @@ std::string_view test1_2() { std::string_view test1_3() { std::string_view t3 = TakeStrRef(std::string()); // expected-warning {{temporary}} \ // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \ - // cfg-note {{result of call to 'TakeStrRef' aliases the storage of temporary object}} + // cfg-note {{result of call to 'TakeStrRef' aliases the storage of temporary object because parameter 'abc' is marked 'lifetimebound'}} use(t3); // cfg-note {{later used here}} t3 = TakeStrRef(std::string()); // expected-warning {{object backing}} \ // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \ - // cfg-note {{result of call to 'TakeStrRef' aliases the storage of temporary object}} + // cfg-note {{result of call to 'TakeStrRef' aliases the storage of temporary object because parameter 'abc' is marked 'lifetimebound'}} use(t3); // cfg-note {{later used here}} return TakeStrRef(std::string()); // expected-warning {{returning address}} \ // cfg-warning {{stack memory associated with temporary object is returned}} cfg-note {{returned here}} @@ -926,11 +926,11 @@ struct Foo { std::string_view test2_1(Foo<std::string> r1, Foo<std::string_view> r2) { std::string_view t1 = Foo<std::string>().get(); // expected-warning {{object backing}} \ // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \ - // cfg-note {{result of call to 'get' aliases the storage of temporary object}} + // cfg-note {{result of call to 'get' aliases the storage of temporary object because the implicit object parameter is marked 'lifetimebound'}} use(t1); // cfg-note {{later used here}} t1 = Foo<std::string>().get(); // expected-warning {{object backing}} \ // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \ - // cfg-note {{result of call to 'get' aliases the storage of temporary object}} + // cfg-note {{result of call to 'get' aliases the storage of temporary object because the implicit object parameter is marked 'lifetimebound'}} use(t1); // cfg-note {{later used here}} return r1.get(); // expected-warning {{address of stack}} \ // cfg-warning {{stack memory associated with parameter 'r1' is returned}} cfg-note {{returned here}} @@ -1136,19 +1136,19 @@ void test1() { std::string_view k3 = Q().get()->sv; // OK std::string_view k4 = Q().get()->s; // expected-warning {{object backing the pointer will}} \ // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \ - // cfg-note {{result of call to 'get' aliases the storage of temporary object}} \ + // cfg-note {{result of call to 'get' aliases the storage of temporary object because the implicit object parameter is marked 'lifetimebound'}} \ // cfg-note {{expression aliases the storage of temporary object}} std::string_view lb1 = foo(S().s); // expected-warning {{object backing the pointer will}} \ // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \ // cfg-note {{expression aliases the storage of temporary object}} \ - // cfg-note {{result of call to 'foo' aliases the storage of temporary object}} + // cfg-note {{result of call to 'foo' aliases the storage of temporary object because parameter 'sv' is marked 'lifetimebound'}} std::string_view lb2 = foo(Q().get()->s); // expected-warning {{object backing the pointer will}} \ // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \ - // cfg-note {{result of call to 'get' aliases the storage of temporary object}} \ + // cfg-note {{result of call to 'get' aliases the storage of temporary object because the implicit object parameter is marked 'lifetimebound'}} \ // cfg-note {{expression aliases the storage of temporary object}} \ - // cfg-note {{result of call to 'foo' aliases the storage of temporary object}} + // cfg-note {{result of call to 'foo' aliases the storage of temporary object because parameter 'sv' is marked 'lifetimebound'}} use(k1, k2, k3, k4, lb1, lb2); // cfg-note 4 {{later used here}} } diff --git a/clang/test/Sema/LifetimeSafety/safety-c.c b/clang/test/Sema/LifetimeSafety/safety-c.c index 42adbbb3f0628..a5a98170a2c9e 100644 --- a/clang/test/Sema/LifetimeSafety/safety-c.c +++ b/clang/test/Sema/LifetimeSafety/safety-c.c @@ -63,7 +63,7 @@ void lifetimebound_call(void) { { int i; p = identity(&i); // expected-warning {{local variable 'i' does not live long enough}} \ - // expected-note {{result of call to 'identity' aliases the storage of local variable 'i'}} + // expected-note {{result of call to 'identity' aliases the storage of local variable 'i' because parameter 'p' is marked 'lifetimebound'}} } // expected-note {{local variable 'i' is destroyed here}} (void)*p; // expected-note {{later used here}} } @@ -94,8 +94,8 @@ void conditional_operator_lifetimebound(int cond) { { int a, b; p = identity(cond ? &a // expected-warning {{local variable 'a' does not live long enough}} \ - // expected-note {{result of call to 'identity' aliases the storage of local variable 'a'}} \ - // expected-note {{result of call to 'identity' aliases the storage of local variable 'b'}} + // expected-note {{result of call to 'identity' aliases the storage of local variable 'a' because parameter 'p' is marked 'lifetimebound'}} \ + // expected-note {{result of call to 'identity' aliases the storage of local variable 'b' because parameter 'p' is marked 'lifetimebound'}} : &b); // expected-warning {{local variable 'b' does not live long enough}} } // expected-note {{local variable 'a' is destroyed here}} \ // expected-note {{local variable 'b' is destroyed here}} diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp index 3c9b58c4e4957..3b626cb234796 100644 --- a/clang/test/Sema/LifetimeSafety/safety.cpp +++ b/clang/test/Sema/LifetimeSafety/safety.cpp @@ -697,7 +697,7 @@ void test_lifetimebound_multi_level() { int** pp = &p; int*** ppp = &pp; // expected-warning {{local variable 'pp' does not live long enough}} result = return_inner_ptr_addr(ppp); // expected-note {{local variable 'ppp' aliases the storage of local variable 'pp'}} \ - // expected-note {{result of call to 'return_inner_ptr_addr' aliases the storage of local variable 'pp'}} + // expected-note {{result of call to 'return_inner_ptr_addr' aliases the storage of local variable 'pp' because parameter 'ppp' is marked 'lifetimebound'}} } // expected-note {{local variable 'pp' is destroyed here}} (void)**result; // expected-note {{used here}} } @@ -821,7 +821,7 @@ void lifetimebound_simple_function() { { MyObj obj; v = Identity(obj); // expected-warning {{local variable 'obj' does not live long enough}} \ - // expected-note {{result of call to 'Identity' aliases the storage of local variable 'obj'}} + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'obj' because parameter 'v' is marked 'lifetimebound'}} } // expected-note {{local variable 'obj' is destroyed here}} v.use(); // expected-note {{later used here}} } @@ -830,8 +830,8 @@ void lifetimebound_multiple_args_definite() { View v; { MyObj obj1, obj2; - v = Choose(true, // expected-note {{result of call to 'Choose' aliases the storage of local variable 'obj1'}} \ - // expected-note {{result of call to 'Choose' aliases the storage of local variable 'obj2'}} + v = Choose(true, // expected-note {{result of call to 'Choose' aliases the storage of local variable 'obj1' because parameter 'a' is marked 'lifetimebound'}} \ + // expected-note {{result of call to 'Choose' aliases the storage of local variable 'obj2' because parameter 'b' is marked 'lifetimebound'}} obj1, // expected-warning {{local variable 'obj1' does not live long enough}} obj2); // expected-warning {{local variable 'obj2' does not live long enough}} } // expected-note {{local variable 'obj1' is destroyed here}} \ @@ -846,8 +846,8 @@ void lifetimebound_multiple_args_potential(bool cond) { MyObj obj1; if (cond) { MyObj obj2; - v = Choose(true, // expected-note {{result of call to 'Choose' aliases the storage of local variable 'obj1'}} \ - // expected-note {{result of call to 'Choose' aliases the storage of local variable 'obj2'}} + v = Choose(true, // expected-note {{result of call to 'Choose' aliases the storage of local variable 'obj1' because parameter 'a' is marked 'lifetimebound'}} \ + // expected-note {{result of call to 'Choose' aliases the storage of local variable 'obj2' because parameter 'b' is marked 'lifetimebound'}} obj1, // expected-warning {{local variable 'obj1' does not live long enough}} obj2); // expected-warning {{local variable 'obj2' does not live long enough}} } // expected-note {{local variable 'obj2' is destroyed here}} @@ -873,7 +873,7 @@ void lifetimebound_mixed_args() { { MyObj obj1, obj2; v = SelectFirst(obj1, // expected-warning {{local variable 'obj1' does not live long enough}} \ - // expected-note {{result of call to 'SelectFirst' aliases the storage of local variable 'obj1'}} + // expected-note {{result of call to 'SelectFirst' aliases the storage of local variable 'obj1' because parameter 'a' is marked 'lifetimebound'}} obj2); } // expected-note {{local variable 'obj1' is destroyed here}} v.use(); // expected-note {{later used here}} @@ -890,7 +890,7 @@ void lifetimebound_member_function() { { MyObj obj; v = obj.getView(); // expected-warning {{local variable 'obj' does not live long enough}} \ - // expected-note {{result of call to 'getView' aliases the storage of local variable 'obj'}} + // expected-note {{result of call to 'getView' aliases the storage of local variable 'obj' because the implicit object parameter is marked 'lifetimebound'}} } // expected-note {{local variable 'obj' is destroyed here}} v.use(); // expected-note {{later used here}} } @@ -915,7 +915,7 @@ void lifetimebound_chained_calls() { { MyObj obj; v = Identity(Identity(Identity(obj))); // expected-warning {{local variable 'obj' does not live long enough}} \ - // expected-note 3 {{result of call to 'Identity' aliases the storage of local variable 'obj'}} + // expected-note 3 {{result of call to 'Identity' aliases the storage of local variable 'obj' because parameter 'v' is marked 'lifetimebound'}} } // expected-note {{local variable 'obj' is destroyed here}} v.use(); // expected-note {{later used here}} } @@ -925,7 +925,7 @@ void lifetimebound_with_pointers() { { MyObj obj; ptr = GetPointer(obj); // expected-warning {{local variable 'obj' does not live long enough}} \ - // expected-note {{result of call to 'GetPointer' aliases the storage of local variable 'obj'}} + // expected-note {{result of call to 'GetPointer' aliases the storage of local variable 'obj' because parameter 'obj' is marked 'lifetimebound'}} } // expected-note {{local variable 'obj' is destroyed here}} (void)*ptr; // expected-note {{later used here}} } @@ -935,7 +935,7 @@ void chained_assignment_lifetimebound_call() { { MyObj s; p = Identity(obj = &s); // expected-warning {{does not live long enough}} \ - // expected-note {{result of call to 'Identity' aliases the storage of local variable 's'}} + // expected-note {{result of call to 'Identity' aliases the storage of local variable 's' because parameter 'v' is marked 'lifetimebound'}} } // expected-note {{local variable 's' is destroyed here}} (void)*p; // expected-note {{later used here}} } @@ -953,7 +953,7 @@ void lifetimebound_partial_safety(bool cond) { if (cond) { MyObj temp_obj; - v = Choose(true, // expected-note {{result of call to 'Choose' aliases the storage of local variable 'temp_obj'}} + v = Choose(true, // expected-note {{result of call to 'Choose' aliases the storage of local variable 'temp_obj' because parameter 'b' is marked 'lifetimebound'}} safe_obj, temp_obj); // expected-warning {{local variable 'temp_obj' does not live long enough}} } // expected-note {{local variable 'temp_obj' is destroyed here}} @@ -968,7 +968,7 @@ void lifetimebound_return_reference() { MyObj obj; View temp_v = obj; // expected-warning {{local variable 'obj' does not live long enough}} const MyObj& ref = GetObject(temp_v); // expected-note {{local variable 'temp_v' aliases the storage of local variable 'obj'}} \ - // expected-note {{result of call to 'GetObject' aliases the storage of local variable 'obj'}} + // expected-note {{result of call to 'GetObject' aliases the storage of local variable 'obj' because parameter 'v' is marked 'lifetimebound'}} ptr = &ref; } // expected-note {{local variable 'obj' is destroyed here}} (void)*ptr; // expected-note {{later used here}} @@ -1023,7 +1023,7 @@ void lifetimebound_make_unique() { { MyObj obj; ptr = std::make_unique<LifetimeBoundCtor>(obj); // tu-warning {{local variable 'obj' does not live long enough}} \ - // tu-note {{result of call to 'make_unique<LifetimeBoundCtor, MyObj &>' aliases the storage of local variable 'obj'}} + // tu-note {{result of call to 'make_unique<LifetimeBoundCtor, MyObj &>' aliases the storage of local variable 'obj' because parameter 'args' is marked 'lifetimebound'}} } // tu-note {{local variable 'obj' is destroyed here}} (void)ptr; // tu-note {{later used here}} } @@ -1041,7 +1041,7 @@ void non_lifetimebound_make_unique() { void lifetimebound_make_unique_temp() { std::unique_ptr<LifetimeBoundCtor> ptr = std::make_unique<LifetimeBoundCtor>(MyObj()); // tu-warning {{temporary object does not live long enough}} \ // tu-note {{temporary object is destroyed here}} \ - // tu-note {{result of call to 'make_unique<LifetimeBoundCtor, MyObj>' aliases the storage of temporary object}} + // tu-note {{result of call to 'make_unique<LifetimeBoundCtor, MyObj>' aliases the storage of temporary object because parameter 'args' is marked 'lifetimebound'}} (void)ptr; // tu-note {{later used here}} } @@ -1079,7 +1079,7 @@ void lifetimebound_make_unique_multi_params() { { MyObj obj_short; ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long); // tu-warning {{local variable 'obj_short' does not live long enough}} \ - // tu-note {{result of call to 'make_unique<MultiLifetimeBoundCtor, MyObj &, MyObj &>' aliases the storage of local variable 'obj_short'}} + // tu-note {{result of call to 'make_unique<MultiLifetimeBoundCtor, MyObj &, MyObj &>' aliases the storage of local variable 'obj_short' because parameter 'args' is marked 'lifetimebound'}} } // tu-note {{local variable 'obj_short' is destroyed here}} (void)ptr; // tu-note {{later used here}} } @@ -1090,7 +1090,7 @@ void lifetimebound_make_unique_multi_params2() { { MyObj obj_short; ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1); // tu-warning {{local variable 'obj_short' does not live long enough}} \ - // tu-note {{result of call to 'make_unique<MultiLifetimeBoundCtor, MyObj &, MyObj &, int>' aliases the storage of local variable 'obj_short'}} + // tu-note {{result of call to 'make_unique<MultiLifetimeBoundCtor, MyObj &, MyObj &, int>' aliases the storage of local variable 'obj_short' because parameter 'args' is marked 'lifetimebound'}} } // tu-note {{local variable 'obj_short' is destroyed here}} (void)ptr; // tu-note {{later used here}} } @@ -1111,7 +1111,7 @@ void lifetimebound_make_unique_multi_params3_1() { { MyObj obj_short; ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long, 1.0); // tu-warning {{local variable 'obj_short' does not live long enough}} \ - // tu-note {{result of call to 'make_unique<MultiLifetimeBoundCtor, MyObj &, MyObj &, double>' aliases the storage of local variable 'obj_short'}} + // tu-note {{result of call to 'make_unique<MultiLifetimeBoundCtor, MyObj &, MyObj &, double>' aliases the storage of local variable 'obj_short' because parameter 'args' is marked 'lifetimebound'}} } // tu-note {{local variable 'obj_short' is destroyed here}} (void)ptr; // tu-note {{later used here}} } @@ -1122,7 +1122,7 @@ void lifetimebound_make_unique_multi_params3_2() { { MyObj obj_short; ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1.0); // tu-warning {{local variable 'obj_short' does not live long enough}} \ - // tu-note {{result of call to 'make_unique<MultiLifetimeBoundCtor, MyObj &, MyObj &, double>' aliases the storage of local variable 'obj_short'}} + // tu-note {{result of call to 'make_unique<MultiLifetimeBoundCtor, MyObj &, MyObj &, double>' aliases the storage of local variable 'obj_short' because parameter 'args' is marked 'lifetimebound'}} } // tu-note {{local variable 'obj_short' is destroyed here}} (void)ptr; // tu-note {{later used here}} } @@ -1237,8 +1237,8 @@ void conditional_operator_lifetimebound(bool cond) { { MyObj a, b; p = Identity(cond ? &a // expected-warning {{local variable 'a' does not live long enough}} \ - // expected-note {{result of call to 'Identity' aliases the storage of local variable 'a'}} \ - // expected-note {{result of call to 'Identity' aliases the storage of local variable 'b'}} + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'a' because parameter 'v' is marked 'lifetimebound'}} \ + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'b' because parameter 'v' is marked 'lifetimebound'}} : &b); // expected-warning {{local variable 'b' does not live long enough}} } // expected-note {{local variable 'b' is destroyed here}} expected-note {{local variable 'a' is destroyed here}} (void)*p; // expected-note 2 {{later used here}} @@ -1249,10 +1249,10 @@ void conditional_operator_lifetimebound_nested(bool cond) { { MyObj a, b; p = Identity(cond ? Identity(&a) // expected-warning {{local variable 'a' does not live long enough}} \ - // expected-note 2 {{result of call to 'Identity' aliases the storage of local variable 'a'}} \ - // expected-note {{result of call to 'Identity' aliases the storage of local variable 'b'}} + // expected-note 2 {{result of call to 'Identity' aliases the storage of local variable 'a' because parameter 'v' is marked 'lifetimebound'}} \ + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'b' because parameter 'v' is marked 'lifetimebound'}} : Identity(&b)); // expected-warning {{local variable 'b' does not live long enough}} \ - // expected-note {{result of call to 'Identity' aliases the storage of local variable 'b'}} + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'b' because parameter 'v' is marked 'lifetimebound'}} } // expected-note {{local variable 'b' is destroyed here}} expected-note {{local variable 'a' is destroyed here}} (void)*p; // expected-note 2 {{later used here}} } @@ -1262,14 +1262,14 @@ void conditional_operator_lifetimebound_nested_deep(bool cond) { { MyObj a, b, c, d; p = Identity(cond ? Identity(cond ? &a // expected-warning {{local variable 'a' does not live long enough}} \ - // expected-note 2 {{result of call to 'Identity' aliases the storage of local variable 'a'}} \ - // expected-note 2 {{result of call to 'Identity' aliases the storage of local variable 'b'}} \ - // expected-note {{result of call to 'Identity' aliases the storage of local variable 'c'}} \ - // expected-note {{result of call to 'Identity' aliases the storage of local variable 'd'}} + // expected-note 2 {{result of call to 'Identity' aliases the storage of local variable 'a' because parameter 'v' is marked 'lifetimebound'}} \ + // expected-note 2 {{result of call to 'Identity' aliases the storage of local variable 'b' because parameter 'v' is marked 'lifetimebound'}} \ + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'c' because parameter 'v' is marked 'lifetimebound'}} \ + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'd' because parameter 'v' is marked 'lifetimebound'}} : &b) // expected-warning {{local variable 'b' does not live long enough}} : Identity(cond ? &c // expected-warning {{local variable 'c' does not live long enough}} \ - // expected-note {{result of call to 'Identity' aliases the storage of local variable 'c'}} \ - // expected-note {{result of call to 'Identity' aliases the storage of local variable 'd'}} + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'c' because parameter 'v' is marked 'lifetimebound'}} \ + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'd' because parameter 'v' is marked 'lifetimebound'}} : &d)); // expected-warning {{local variable 'd' does not live long enough}} } // expected-note {{local variable 'a' is destroyed here}} expected-note {{local variable 'd' is destroyed here}} expected-note {{local variable 'b' is destroyed here}} expected-note {{local variable 'c' is destroyed here}} (void)*p; // expected-note 4 {{later used here}} @@ -1464,7 +1464,7 @@ void parentheses(bool cond) { { MyObj a; p = ((GetPointer((a)))); // expected-warning {{local variable 'a' does not live long enough}} \ - // expected-note {{result of call to 'GetPointer' aliases the storage of local variable 'a'}} + // expected-note {{result of call to 'GetPointer' aliases the storage of local variable 'a' because parameter 'obj' is marked 'lifetimebound'}} } // expected-note {{local variable 'a' is destroyed here}} (void)*p; // expected-note {{later used here}} @@ -1496,7 +1496,7 @@ void use_temporary_after_destruction() { void passing_temporary_to_lifetime_bound_function() { View a = construct_view(non_trivially_destructed_temporary()); // expected-warning {{temporary object does not live long enough}} \ expected-note {{temporary object is destroyed here}} \ - expected-note {{result of call to 'construct_view' aliases the storage of temporary object}} + expected-note {{result of call to 'construct_view' aliases the storage of temporary object because parameter 'obj' is marked 'lifetimebound'}} use(a); // expected-note {{later used here}} } @@ -1538,7 +1538,7 @@ int **bit_cast_multilevel() { namespace FullExprCleanupLoc { void var_initializer() { View v = non_trivially_destructed_temporary() // expected-warning {{temporary object does not live long enough}} \ - // expected-note {{result of call to 'getView' aliases the storage of temporary object}} + // expected-note {{result of call to 'getView' aliases the storage of temporary object because the implicit object parameter is marked 'lifetimebound'}} .getView(); // expected-note {{temporary object is destroyed here}} v.use(); // expected-note {{later used here}} } @@ -1546,7 +1546,7 @@ void var_initializer() { void expr_statement() { View v; v = non_trivially_destructed_temporary() // expected-warning {{temporary object does not live long enough}} \ - // expected-note {{result of call to 'getView' aliases the storage of temporary object}} + // expected-note {{result of call to 'getView' aliases the storage of temporary object because the implicit object parameter is marked 'lifetimebound'}} .getView(); // expected-note {{temporary object is destroyed here}} v.use(); // expected-note {{later used here}} } @@ -1590,7 +1590,7 @@ void foobar() { { StatusOr<MyObj> string_or = getStringOr(); view = string_or. // expected-warning {{local variable 'string_or' does not live long enough}} \ - // expected-note {{result of call to 'value' aliases the storage of local variable 'string_or'}} + // expected-note {{result of call to 'value' aliases the storage of local variable 'string_or' because the implicit object parameter is marked 'lifetimebound'}} value(); } // expected-note {{local variable 'string_or' is destroyed here}} (void)view; // expected-note {{later used here}} @@ -1666,7 +1666,7 @@ void test_user_defined_deref_uaf() { MyObj obj; SmartPtr<MyObj> smart_ptr(&obj); p = &(*smart_ptr); // expected-warning {{local variable 'smart_ptr' does not live long enough}} \ - // expected-note {{expression aliases the storage of local variable 'smart_ptr'}} + // expected-note {{expression aliases the storage of local variable 'smart_ptr' because the implicit object parameter is marked 'lifetimebound'}} } // expected-note {{local variable 'smart_ptr' is destroyed here}} (void)*p; // expected-note {{later used here}} } @@ -1684,7 +1684,7 @@ void test_user_defined_deref_with_view() { MyObj obj; SmartPtr<MyObj> smart_ptr(&obj); v = *smart_ptr; // expected-warning {{local variable 'smart_ptr' does not live long enough}} \ - // expected-note {{expression aliases the storage of local variable 'smart_ptr'}} + // expected-note {{expression aliases the storage of local variable 'smart_ptr' because the implicit object parameter is marked 'lifetimebound'}} } // expected-note {{local variable 'smart_ptr' is destroyed here}} v.use(); // expected-note {{later used here}} } @@ -1695,7 +1695,7 @@ void test_user_defined_deref_arrow() { MyObj obj; SmartPtr<MyObj> smart_ptr(&obj); p = smart_ptr.operator->(); // expected-warning {{local variable 'smart_ptr' does not live long enough}} \ - // expected-note {{expression aliases the storage of local variable 'smart_ptr'}} + // expected-note {{expression aliases the storage of local variable 'smart_ptr' because the implicit object parameter is marked 'lifetimebound'}} } // expected-note {{local variable 'smart_ptr' is destroyed here}} (void)*p; // expected-note {{later used here}} } @@ -1706,7 +1706,7 @@ void test_user_defined_deref_chained() { MyObj obj; SmartPtr<SmartPtr<MyObj>> double_ptr; p = &(**double_ptr); // expected-warning {{local variable 'double_ptr' does not live long enough}} \ - // expected-note 2 {{expression aliases the storage of local variable 'double_ptr'}} + // expected-note 2 {{expression aliases the storage of local variable 'double_ptr' because the implicit object parameter is marked 'lifetimebound'}} } // expected-note {{local variable 'double_ptr' is destroyed here}} (void)*p; // expected-note {{later used here}} } @@ -1901,7 +1901,7 @@ void wrong_use_of_move_is_permissive() { MyObj a; p = std::move(a).getData(); // expected-warning {{local variable 'a' does not live long enough}} \ // expected-note {{result of call to 'move<MyObj &>' aliases the storage of local variable 'a'}} \ - // expected-note {{result of call to 'getData' aliases the storage of local variable 'a'}} + // expected-note {{result of call to 'getData' aliases the storage of local variable 'a' because the implicit object parameter is marked 'lifetimebound'}} } // expected-note {{local variable 'a' is destroyed here}} (void)p; // expected-note {{later used here}} } @@ -1936,10 +1936,10 @@ void bar() { { S s; x = s.x(); // expected-warning {{local variable 's' does not live long enough}} \ - // expected-note {{result of call to 'x' aliases the storage of local variable 's'}} + // expected-note {{result of call to 'x' aliases the storage of local variable 's' because the implicit object parameter is marked 'lifetimebound'}} View y = S().x(); // expected-warning {{temporary object does not live long enough}} \ expected-note {{temporary object is destroyed here}} \ - expected-note {{result of call to 'x' aliases the storage of temporary object}} + expected-note {{result of call to 'x' aliases the storage of temporary object because the implicit object parameter is marked 'lifetimebound'}} (void)y; // expected-note {{used here}} } // expected-note {{local variable 's' is destroyed here}} (void)x; // expected-note {{used here}} @@ -2028,19 +2028,19 @@ const S& identity(const S& in [[clang::lifetimebound]]); void test_temporary() { const std::string& x = S().x(); // expected-warning {{temporary object does not live long enough}} expected-note {{temporary object is destroyed here}} \ - // expected-note {{result of call to 'x' aliases the storage of temporary object}} + // expected-note {{result of call to 'x' aliases the storage of temporary object because the implicit object parameter is marked 'lifetimebound'}} (void)x; // expected-note {{later used here}} const std::string& y = identity(S().x()); // expected-warning {{temporary object does not live long enough}} expected-note {{temporary object is destroyed here}} \ - // expected-note {{result of call to 'x' aliases the storage of temporary object}} \ - // expected-note {{result of call to 'identity' aliases the storage of temporary object}} + // expected-note {{result of call to 'x' aliases the storage of temporary object because the implicit object parameter is marked 'lifetimebound'}} \ + // expected-note {{result of call to 'identity' aliases the storage of temporary object because parameter 'in' is marked 'lifetimebound'}} (void)y; // expected-note {{later used here}} std::string_view z; { S s; const std::string& zz = s.x(); // expected-warning {{local variable 's' does not live long enough}} \ - // expected-note {{result of call to 'x' aliases the storage of local variable 's'}} + // expected-note {{result of call to 'x' aliases the storage of local variable 's' because the implicit object parameter is marked 'lifetimebound'}} z = zz; // expected-note {{expression aliases the storage of local variable 's'}} } // expected-note {{local variable 's' is destroyed here}} (void)z; // expected-note {{later used here}} @@ -2050,13 +2050,13 @@ void test_lifetime_extension_ok() { const S& x = S(); (void)x; const S& y = identity(S()); // expected-warning {{temporary object does not live long enough}} expected-note {{temporary object is destroyed here}} \ - // expected-note {{result of call to 'identity' aliases the storage of temporary object}} + // expected-note {{result of call to 'identity' aliases the storage of temporary object because parameter 'in' is marked 'lifetimebound'}} (void)y; // expected-note {{later used here}} } const std::string& test_return() { const std::string& x = S().x(); // expected-warning {{temporary object does not live long enough}} expected-note {{temporary object is destroyed here}} \ - // expected-note {{result of call to 'x' aliases the storage of temporary object}} + // expected-note {{result of call to 'x' aliases the storage of temporary object because the implicit object parameter is marked 'lifetimebound'}} return x; // expected-note {{later used here}} } } // namespace reference_type_decl_ref_expr @@ -2237,11 +2237,11 @@ const T* MemberFuncsTpl<T>::memberC(const T& x [[clang::lifetimebound]]) { void test() { MemberFuncsTpl<MyObj> mtf; const MyObj* pTMA = mtf.memberA(MyObj()); // expected-warning {{temporary object does not live long enough}} // expected-note {{temporary object is destroyed here}} \ - // expected-note {{result of call to 'memberA' aliases the storage of temporary object}} + // expected-note {{result of call to 'memberA' aliases the storage of temporary object because parameter 'x' is marked 'lifetimebound'}} const MyObj* pTMB = mtf.memberB(MyObj()); // tu-warning {{temporary object does not live long enough}} // tu-note {{temporary object is destroyed here}} \ - // tu-note {{result of call to 'memberB' aliases the storage of temporary object}} + // tu-note {{result of call to 'memberB' aliases the storage of temporary object because parameter 'x' is marked 'lifetimebound'}} const MyObj* pTMC = mtf.memberC(MyObj()); // expected-warning {{temporary object does not live long enough}} // expected-note {{temporary object is destroyed here}} \ - // expected-note {{result of call to 'memberC' aliases the storage of temporary object}} + // expected-note {{result of call to 'memberC' aliases the storage of temporary object because parameter 'x' is marked 'lifetimebound'}} (void)pTMA; // expected-note {{later used here}} (void)pTMB; // tu-note {{later used here}} (void)pTMC; // expected-note {{later used here}} @@ -2289,7 +2289,7 @@ void test_optional_arrow_lifetimebound() { std::optional<MyObj> opt; v = opt->getView(); // expected-warning {{local variable 'opt' does not live long enough}} \ // expected-note {{expression aliases the storage of local variable 'opt'}} \ - // expected-note {{result of call to 'getView' aliases the storage of local variable 'opt'}} + // expected-note {{result of call to 'getView' aliases the storage of local variable 'opt' because the implicit object parameter is marked 'lifetimebound'}} } // expected-note {{local variable 'opt' is destroyed here}} v.use(); // expected-note {{later used here}} } @@ -2690,7 +2690,8 @@ struct S { void indexing_with_static_operator() { S()(1, 2); - S& x = S()("1", // expected-note 2 {{expression aliases the storage of temporary object}} + S& x = S()("1", // expected-note {{expression aliases the storage of temporary object because parameter 'a' is marked 'lifetimebound'}} \ + // expected-note {{expression aliases the storage of temporary object because parameter 'b' is marked 'lifetimebound'}} 2, // expected-warning {{temporary object does not live long enough}} 3); // expected-warning {{temporary object does not live long enough}} expected-note 2 {{temporary object is destroyed here}} @@ -2716,7 +2717,7 @@ S getS(const std::string &s [[clang::lifetimebound]]); void from_free_function() { S s = getS(std::string("temp")); // expected-warning {{temporary object does not live long enough}} \ // expected-note {{temporary object is destroyed here}} \ - // expected-note {{result of call to 'getS' aliases the storage of temporary object}} + // expected-note {{result of call to 'getS' aliases the storage of temporary object because parameter 's' is marked 'lifetimebound'}} use(s); // expected-note {{later used here}} } @@ -2736,14 +2737,14 @@ void from_method() { Factory f; S s = f.make(std::string("temp")); // expected-warning {{temporary object does not live long enough}} \ // expected-note {{temporary object is destroyed here}} \ - // expected-note {{result of call to 'make' aliases the storage of temporary object}} + // expected-note {{result of call to 'make' aliases the storage of temporary object because parameter 's' is marked 'lifetimebound'}} use(s); // expected-note {{later used here}} } void from_static_method() { S s = Factory::create(std::string("temp")); // expected-warning {{temporary object does not live long enough}} \ // expected-note {{temporary object is destroyed here}} \ - // expected-note {{result of call to 'create' aliases the storage of temporary object}} + // expected-note {{result of call to 'create' aliases the storage of temporary object because parameter 's' is marked 'lifetimebound'}} use(s); // expected-note {{later used here}} } @@ -2752,7 +2753,7 @@ void from_lifetimebound_this_method() { { Factory f; value = f.makeThis(); // expected-warning {{local variable 'f' does not live long enough}} \ - // expected-note {{result of call to 'makeThis' aliases the storage of local variable 'f'}} + // expected-note {{result of call to 'makeThis' aliases the storage of local variable 'f' because the implicit object parameter is marked 'lifetimebound'}} } // expected-note {{local variable 'f' is destroyed here}} use(value); // expected-note {{later used here}} } @@ -2762,7 +2763,7 @@ void across_scope() { { std::string str{"abc"}; s = getS(str); // expected-warning {{local variable 'str' does not live long enough}} \ - // expected-note {{result of call to 'getS' aliases the storage of local variable 'str'}} + // expected-note {{result of call to 'getS' aliases the storage of local variable 'str' because parameter 's' is marked 'lifetimebound'}} } // expected-note {{local variable 'str' is destroyed here}} use(s); // expected-note {{later used here}} } @@ -2785,7 +2786,7 @@ void assignment_propagation() { { std::string str{"abc"}; a = getS(str); // expected-warning {{local variable 'str' does not live long enough}} \ - // expected-note {{result of call to 'getS' aliases the storage of local variable 'str'}} + // expected-note {{result of call to 'getS' aliases the storage of local variable 'str' because parameter 's' is marked 'lifetimebound'}} b = a; // expected-note {{local variable 'a' aliases the storage of local variable 'str'}} } // expected-note {{local variable 'str' is destroyed here}} use(b); // expected-note {{later used here}} @@ -2796,7 +2797,7 @@ void chained_defaulted_assignment_propagation() { { std::string str{"abc"}; S a = getS(str); // expected-warning {{local variable 'str' does not live long enough}} \ - // expected-note {{result of call to 'getS' aliases the storage of local variable 'str'}} + // expected-note {{result of call to 'getS' aliases the storage of local variable 'str' because parameter 's' is marked 'lifetimebound'}} c = b = a; // expected-note {{local variable 'a' aliases the storage of local variable 'str'}} \ // expected-note {{expression aliases the storage of local variable 'str'}} } // expected-note {{local variable 'str' is destroyed here}} @@ -2813,7 +2814,7 @@ void no_annotation() { void mix_annotated_and_not() { S s1 = getS(std::string("temp")); // expected-warning {{temporary object does not live long enough}} \ // expected-note {{temporary object is destroyed here}} \ - // expected-note {{result of call to 'getS' aliases the storage of temporary object}} + // expected-note {{result of call to 'getS' aliases the storage of temporary object because parameter 's' is marked 'lifetimebound'}} S s2 = getSNoAnnotation(std::string("temp")); use(s1); // expected-note {{later used here}} use(s2); @@ -2825,7 +2826,7 @@ S multiple_lifetimebound_params() { std::string str{"abc"}; S s = getS2(str, std::string("temp")); // expected-warning {{stack memory associated with local variable 'str' is returned}} \ // expected-warning {{temporary object does not live long enough}} \ - // expected-note {{result of call to 'getS2' aliases the storage of temporary object}} \ + // expected-note {{result of call to 'getS2' aliases the storage of temporary object because parameter 'b' is marked 'lifetimebound'}} \ // expected-note {{temporary object is destroyed here}} return s; // expected-note {{returned here}} \ // expected-note {{later used here}} @@ -2846,7 +2847,7 @@ T make(const std::string &s [[clang::lifetimebound]]); void from_template_instantiation() { S s = make<S>(std::string("temp")); // expected-warning {{temporary object does not live long enough}} \ // expected-note {{temporary object is destroyed here}} \ - // expected-note {{result of call to 'make<track_origins_for_lifetimebound_record_type::S>' aliases the storage of temporary object}} + // expected-note {{result of call to 'make<track_origins_for_lifetimebound_record_type::S>' aliases the storage of temporary object because parameter 's' is marked 'lifetimebound'}} use(s); // expected-note {{later used here}} } @@ -2910,7 +2911,7 @@ SAlias getSAlias(const std::string &s [[clang::lifetimebound]]); void from_typedef_return() { SAlias s = getSAlias(std::string("temp")); // expected-warning {{temporary object does not live long enough}} \ // expected-note {{temporary object is destroyed here}} \ - // expected-note {{result of call to 'getSAlias' aliases the storage of temporary object}} + // expected-note {{result of call to 'getSAlias' aliases the storage of temporary object because parameter 's' is marked 'lifetimebound'}} use(s); // expected-note {{later used here}} } @@ -2985,7 +2986,7 @@ std::unique_ptr<S> getUniqueS(const std::string &s [[clang::lifetimebound]]); void owner_return_unique_ptr_s() { auto ptr = getUniqueS(std::string("temp")); // expected-warning {{temporary object does not live long enough}} \ // expected-note {{temporary object is destroyed here}} \ - // expected-note {{result of call to 'getUniqueS' aliases the storage of temporary object}} + // expected-note {{result of call to 'getUniqueS' aliases the storage of temporary object because parameter 's' is marked 'lifetimebound'}} (void)ptr; // expected-note {{later used here}} } @@ -3002,7 +3003,7 @@ void owner_outlives_lifetimebound_source() { { std::string local; ups = getUniqueS(local); // expected-warning {{local variable 'local' does not live long enough}} \ - // expected-note {{result of call to 'getUniqueS' aliases the storage of local variable 'local'}} + // expected-note {{result of call to 'getUniqueS' aliases the storage of local variable 'local' because parameter 's' is marked 'lifetimebound'}} } // expected-note {{local variable 'local' is destroyed here}} (void)ups; // expected-note {{later used here}} } @@ -3037,7 +3038,7 @@ void nested_local_pointer() { { Bar v; p = Pointer(v); // expected-warning {{local variable 'v' does not live long enough}} - pp = Pointer(p); // expected-note {{local variable 'p' aliases the storage of local variable 'v'}} + pp = Pointer(p); // expected-note {{local variable 'p' aliases the storage of local variable 'v' because parameter 'bar' is marked 'lifetimebound'}} ppp = Pointer(pp); // expected-note {{local variable 'pp' aliases the storage of local variable 'v'}} } // expected-note {{local variable 'v' is destroyed here}} use(***ppp); // expected-note {{later used here}} @@ -3795,7 +3796,7 @@ void uaf_via_lifetimebound() { { int local; f = capture_lifetimebound_param(local); // expected-warning {{local variable 'local' does not live long enough}} \ - // expected-note {{result of call to 'capture_lifetimebound_param' aliases the storage of local variable 'local'}} + // expected-note {{result of call to 'capture_lifetimebound_param' aliases the storage of local variable 'local' because parameter 'x' is marked 'lifetimebound'}} } // expected-note {{local variable 'local' is destroyed here}} (void)f; // expected-note {{later used here}} } >From ad1a2be77ddb923c3c1020985224e9b0b6ff3c5c Mon Sep 17 00:00:00 2001 From: iitianpushkar <[email protected]> Date: Wed, 8 Jul 2026 20:50:04 +0530 Subject: [PATCH 2/2] implemented re-derive design flow using PointerUnion --- .../Analysis/Analyses/LifetimeSafety/Facts.h | 19 ---- .../Analyses/LifetimeSafety/LifetimeSafety.h | 6 +- .../Analyses/LifetimeSafety/LoanPropagation.h | 9 -- .../clang/Basic/DiagnosticSemaKinds.td | 3 +- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 97 +++++++++++++++++-- .../LifetimeSafety/FactsGenerator.cpp | 38 +++----- .../LifetimeSafety/LoanPropagation.cpp | 62 ++---------- clang/lib/Sema/SemaLifetimeSafety.h | 23 +++-- 8 files changed, 126 insertions(+), 131 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index 2e426105d1c84..94db2a7f311ae 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -20,7 +20,6 @@ #include "clang/Analysis/Analyses/LifetimeSafety/Utils.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" -#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Debug.h" @@ -34,11 +33,6 @@ class LoanPropagationAnalysis; using FactID = utils::ID<struct FactTag>; -struct LifetimeBoundParamInfo { - const ParmVarDecl *Param = nullptr; - bool IsImplicitObject = false; -}; - /// An abstract base class for a single, atomic lifetime-relevant event. class Fact { @@ -399,17 +393,6 @@ class FactManager { const LoanManager &getLoanMgr() const { return LoanMgr; } OriginManager &getOriginMgr() { return OriginMgr; } const OriginManager &getOriginMgr() const { return OriginMgr; } - void setLifetimeBoundParamInfo(const OriginFlowFact *F, - LifetimeBoundParamInfo Info) { - LifetimeBoundParamInfos.try_emplace(F, Info); - } - std::optional<LifetimeBoundParamInfo> - getLifetimeBoundParamInfo(const OriginFlowFact *F) const { - auto It = LifetimeBoundParamInfos.find(F); - if (It == LifetimeBoundParamInfos.end()) - return std::nullopt; - return It->second; - } private: FactID NextFactID{0}; @@ -417,8 +400,6 @@ class FactManager { OriginManager OriginMgr; /// Facts for each CFG block, indexed by block ID. llvm::SmallVector<llvm::SmallVector<const Fact *>> BlockToFacts; - llvm::DenseMap<const OriginFlowFact *, LifetimeBoundParamInfo> - LifetimeBoundParamInfos; llvm::BumpPtrAllocator FactAllocator; }; } // namespace clang::lifetimes::internal diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 6204381a873ee..7a4577e890b2f 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -21,6 +21,7 @@ #define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H #include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" #include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeStats.h" #include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h" @@ -50,9 +51,12 @@ enum class WarningScope { IntraTU // For warnings on functions local to a Translation Unit. }; +using LifetimeBoundParamInfo = + llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>; + struct AliasChainEntry { const Expr *E = nullptr; - std::optional<internal::LifetimeBoundParamInfo> LifetimeBound; + std::optional<LifetimeBoundParamInfo> LifetimeBound; }; /// Abstract interface for operations requiring Sema access. diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h index 616394314056f..838daa024c953 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h @@ -56,15 +56,6 @@ class LoanPropagationAnalysis { const LoanID TargetLoan, const CFG *Cfg) const; - llvm::SmallVector<const OriginFlowFact *> - buildOriginFlowChainWithFacts(ProgramPoint StartPoint, - const OriginID StartOID, - const LoanID TargetLoan, const CFG *Cfg) const; - - llvm::SmallVector<const OriginFlowFact *> - buildOriginFlowChainWithFacts(const UseFact *UF, const LoanID TargetLoan, - const CFG *Cfg) const; - private: class Impl; std::unique_ptr<Impl> PImpl; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index ed1e4350890b5..e55e6ee1aa939 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11079,7 +11079,8 @@ def note_lifetime_safety_escapes_to_static_storage_here: Note<"escapes to this s def note_lifetime_safety_lifetimebound_here: Note<"'lifetimebound' attribute appears here on the definition">; def note_lifetime_safety_aliases_storage : Note<"%0 aliases the storage of %1">; def note_lifetime_safety_aliases_storage_lifetimebound : Note< - "%0 aliases the storage of %1 because %2 is marked 'lifetimebound'">; + "%0 aliases the storage of %1 because %select{parameter %3|the implicit " + "object parameter}2 is marked 'lifetimebound'">; def warn_lifetime_safety_intra_tu_param_suggestion : Warning<"parameter in intra-TU function should be marked " diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 2a3acdeb4b1ca..93dbdd602df92 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -14,6 +14,7 @@ #include "clang/Analysis/Analyses/LifetimeSafety/Checker.h" #include "clang/AST/Decl.h" #include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" #include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" #include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h" @@ -40,6 +41,12 @@ static bool causingFactDominatesExpiry(LivenessKind K) { llvm_unreachable("unknown liveness kind"); } +static OriginList *getRValueOrigins(const Expr *E, OriginList *List) { + if (!List) + return nullptr; + return E->isGLValue() ? List->peelOuterOrigin() : List; +} + namespace { /// Struct to store the complete context for a potential lifetime violation. @@ -275,7 +282,7 @@ class LifetimeChecker { SemaHelper->reportUseAfterScope( IssueExpr, UF->getUseExpr(), MovedExpr, ExpiryLoc, getAliasChain( - LoanPropagation.buildOriginFlowChainWithFacts(UF, LID, Cfg))); + LoanPropagation.buildOriginFlowChain(UF, LID, Cfg))); } else if (const auto *OEF = CausingFact.dyn_cast<const OriginEscapesFact *>()) { @@ -522,19 +529,95 @@ class LifetimeChecker { } } + std::optional<LifetimeBoundParamInfo> + getLifetimeBoundParamInfo(const FunctionDecl *FD, unsigned I) { + const ParmVarDecl *PVD = nullptr; + if (const auto *Method = dyn_cast<CXXMethodDecl>(FD); + Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD)) { + if (I == 0) + return implicitObjectParamIsLifetimeBound(Method) + ? std::optional<LifetimeBoundParamInfo>( + LifetimeBoundParamInfo(Method)) + : std::nullopt; + if ((I - 1) < Method->getNumParams()) + PVD = Method->getParamDecl(I - 1); + } else if (I < FD->getNumParams()) { + PVD = FD->getParamDecl(I); + } + + if (PVD && PVD->hasAttr<clang::LifetimeBoundAttr>()) + return LifetimeBoundParamInfo(PVD); + return std::nullopt; + } + + std::optional<LifetimeBoundParamInfo> + getLifetimeBoundParamInfo(const Expr *Call, OriginID SrcOriginID) { + Call = Call->IgnoreParenImpCasts(); + + const FunctionDecl *FD = nullptr; + llvm::SmallVector<const Expr *, 4> Args; + + if (const auto *CCE = dyn_cast<CXXConstructExpr>(Call)) { + FD = CCE->getConstructor(); + Args.append(CCE->getArgs(), CCE->getArgs() + CCE->getNumArgs()); + } else if (const auto *MCE = dyn_cast<CXXMemberCallExpr>(Call)) { + FD = MCE->getMethodDecl(); + Args.push_back(MCE->getImplicitObjectArgument()); + Args.append(MCE->getArgs(), MCE->getArgs() + MCE->getNumArgs()); + } else if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(Call)) { + FD = OCE->getDirectCallee(); + Args.append(OCE->getArgs(), OCE->getArgs() + OCE->getNumArgs()); + if (FD && OCE->getOperator() == OO_Call && FD->isStatic()) + Args.erase(Args.begin()); + } else if (const auto *CE = dyn_cast<CallExpr>(Call)) { + FD = CE->getDirectCallee(); + Args.append(CE->getArgs(), CE->getArgs() + CE->getNumArgs()); + } + + FD = getDeclWithMergedLifetimeBoundAttrs(FD); + if (!FD) + return std::nullopt; + + for (unsigned I = 0; I < Args.size(); ++I) { + std::optional<LifetimeBoundParamInfo> Info = + getLifetimeBoundParamInfo(FD, I); + if (!Info) + continue; + + OriginList *ArgList = FactMgr.getOriginMgr().getOrCreateList(Args[I]); + if (ArgList && ArgList->getOuterOriginID() == SrcOriginID) + return Info; + + OriginList *RValueList = getRValueOrigins(Args[I], ArgList); + if (RValueList && RValueList->getOuterOriginID() == SrcOriginID) + return Info; + } + + return std::nullopt; + } + /// Extract expressions from the origin flow chain for diagnostic purposes. /// /// Given a chain of origins that shows how a loan propagates, this function /// extracts the corresponding expressions for each origin. Origins that refer /// to declarations (rather than expressions) are skipped. llvm::SmallVector<AliasChainEntry> - getAliasChain(llvm::ArrayRef<const OriginFlowFact *> OriginFlowChain) { + getAliasChain(llvm::ArrayRef<OriginID> OriginFlowChain) { + llvm::SmallVector<std::pair<OriginID, AliasChainEntry>> Entries; + for (OriginID OID : OriginFlowChain) + if (const Expr *CurrExpr = + FactMgr.getOriginMgr().getOrigin(OID).getExpr()) + Entries.push_back({OID, {CurrExpr, std::nullopt}}); + + for (unsigned I = 0; I + 1 < Entries.size(); ++I) + if (std::optional<LifetimeBoundParamInfo> Info = + getLifetimeBoundParamInfo(Entries[I].second.E, + Entries[I + 1].first)) + Entries[I + 1].second.LifetimeBound = Info; + llvm::SmallVector<AliasChainEntry> Result; - for (const OriginFlowFact *Flow : OriginFlowChain) - if (const Expr *CurrExpr = FactMgr.getOriginMgr() - .getOrigin(Flow->getSrcOriginID()) - .getExpr()) - Result.push_back({CurrExpr, FactMgr.getLifetimeBoundParamInfo(Flow)}); + for (const auto &Entry : Entries) + Result.push_back(Entry.second); return Result; } }; diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 83ff0d1265658..ca88986975299 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -1096,18 +1096,13 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, flow(CallList, getOriginsList(*Args[0]), /*Kill=*/true); return; } - auto GetLifetimeBoundParamInfo = - [FD](unsigned I) -> std::optional<LifetimeBoundParamInfo> { + auto IsArgLifetimeBound = [FD](unsigned I) -> bool { const ParmVarDecl *PVD = nullptr; if (const auto *Method = dyn_cast<CXXMethodDecl>(FD); Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD)) { if (I == 0) // For the 'this' argument, the attribute is on the method itself. - return implicitObjectParamIsLifetimeBound(Method) - ? std::optional<LifetimeBoundParamInfo>( - LifetimeBoundParamInfo{/*Param=*/nullptr, - /*IsImplicitObject=*/true}) - : std::nullopt; + return implicitObjectParamIsLifetimeBound(Method); if ((I - 1) < Method->getNumParams()) // For explicit arguments, find the corresponding parameter // declaration. @@ -1116,14 +1111,10 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, // For free functions or static methods. PVD = FD->getParamDecl(I); } - if (PVD && PVD->hasAttr<clang::LifetimeBoundAttr>()) - return LifetimeBoundParamInfo{PVD, /*IsImplicitObject=*/false}; - return std::nullopt; + return PVD ? PVD->hasAttr<clang::LifetimeBoundAttr>() : false; }; - auto ShouldTrackArg = - [FD, &Args](unsigned I, - std::optional<LifetimeBoundParamInfo> Info) -> bool { - if (Info) + auto ShouldTrackArg = [FD, &Args, IsArgLifetimeBound](unsigned I) -> bool { + if (IsArgLifetimeBound(I)) return true; if (const auto *Method = dyn_cast<CXXMethodDecl>(FD); Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD)) @@ -1152,9 +1143,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, OriginList *ArgList = getOriginsList(*Args[I]); if (!ArgList) continue; - std::optional<LifetimeBoundParamInfo> LifetimeBoundInfo = - GetLifetimeBoundParamInfo(I); - bool TrackArg = ShouldTrackArg(I, LifetimeBoundInfo); + bool TrackArg = ShouldTrackArg(I); if (IsGslConstruction) { // TODO: document with code example. // std::string_view(const std::string_view& from) @@ -1177,11 +1166,9 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, // destination origin lists may have different lengths. // FIXME: Handle origin-shape mismatches gracefully so we can also flow // inner origins. - auto *F = FactMgr.createFact<OriginFlowFact>( - CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc); - if (LifetimeBoundInfo) - FactMgr.setLifetimeBoundParamInfo(F, *LifetimeBoundInfo); - CurrentBlockFacts.push_back(F); + CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( + CallList->getOuterOriginID(), ArgList->getOuterOriginID(), + KillSrc)); KillSrc = false; } } else if (shouldTrackPointerImplicitObjectArg(I)) { @@ -1196,11 +1183,8 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, // Lifetimebound on a non-GSL-ctor function means the returned // pointer/reference itself must not outlive the arguments. This // only constrains the top-level origin. - auto *F = FactMgr.createFact<OriginFlowFact>( - CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc); - if (LifetimeBoundInfo) - FactMgr.setLifetimeBoundParamInfo(F, *LifetimeBoundInfo); - CurrentBlockFacts.push_back(F); + CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( + CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc)); KillSrc = false; } } diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index 5744479737b0a..078892bd48c10 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -207,17 +207,6 @@ class AnalysisImpl const OriginID StartOID, const LoanID TargetLoan, const CFG *Cfg) const { - llvm::SmallVector<OriginID> OriginFlowChain; - for (const OriginFlowFact *Flow : - buildOriginFlowChainWithFacts(StartPoint, StartOID, TargetLoan, Cfg)) - OriginFlowChain.push_back(Flow->getSrcOriginID()); - return OriginFlowChain; - } - - llvm::SmallVector<const OriginFlowFact *> - buildOriginFlowChainWithFacts(ProgramPoint StartPoint, - const OriginID StartOID, - const LoanID TargetLoan, const CFG *Cfg) const { assert(getLoans(StartOID, StartPoint).contains(TargetLoan) && "TargetLoan must be present in the StartOID at the StartPoint"); @@ -236,7 +225,7 @@ class AnalysisImpl using SearchState = std::pair<const CFGBlock *, OriginID>; struct DFSNode { SearchState CurrState; - llvm::SmallVector<const OriginFlowFact *> OriginFlowChain; + llvm::SmallVector<OriginID> OriginFlowChain; }; llvm::SmallVector<DFSNode> PendingStates; @@ -250,10 +239,10 @@ class AnalysisImpl // Trace origins within the current block const auto [BuildResult, Complete] = - buildOriginFlowChainWithFacts(CurrBlock, CurrOID, TargetLoan); + buildOriginFlowChain(CurrBlock, CurrOID, TargetLoan); if (!BuildResult.empty()) { CurrNode.OriginFlowChain.append(BuildResult); - CurrOID = BuildResult.back()->getSrcOriginID(); + CurrOID = BuildResult.back(); } // If we found the IssueFact, we're done @@ -277,21 +266,11 @@ class AnalysisImpl llvm::SmallVector<OriginID> buildOriginFlowChain(const UseFact *UF, const LoanID TargetLoan, const CFG *Cfg) const { - llvm::SmallVector<OriginID> OriginFlowChain; - for (const OriginFlowFact *Flow : - buildOriginFlowChainWithFacts(UF, TargetLoan, Cfg)) - OriginFlowChain.push_back(Flow->getSrcOriginID()); - return OriginFlowChain; - } - - llvm::SmallVector<const OriginFlowFact *> - buildOriginFlowChainWithFacts(const UseFact *UF, const LoanID TargetLoan, - const CFG *Cfg) const { for (const OriginList *Cur = UF->getUsedOrigins(); Cur; Cur = Cur->peelOuterOrigin()) if (getLoans(Cur->getOuterOriginID(), UF).contains(TargetLoan)) - return buildOriginFlowChainWithFacts(UF, Cur->getOuterOriginID(), - TargetLoan, Cfg); + return buildOriginFlowChain(UF, Cur->getOuterOriginID(), TargetLoan, + Cfg); return {}; } @@ -329,19 +308,8 @@ class AnalysisImpl std::pair<llvm::SmallVector<OriginID>, bool> buildOriginFlowChain(const CFGBlock *Block, const OriginID StartOID, const LoanID TargetLoan) const { - llvm::SmallVector<OriginID> OriginFlowChain; - auto [FlowChain, Complete] = - buildOriginFlowChainWithFacts(Block, StartOID, TargetLoan); - for (const OriginFlowFact *Flow : FlowChain) - OriginFlowChain.push_back(Flow->getSrcOriginID()); - return {OriginFlowChain, Complete}; - } - - std::pair<llvm::SmallVector<const OriginFlowFact *>, bool> - buildOriginFlowChainWithFacts(const CFGBlock *Block, const OriginID StartOID, - const LoanID TargetLoan) const { OriginID CurrOID = StartOID; - llvm::SmallVector<const OriginFlowFact *> OriginFlowChain; + llvm::SmallVector<OriginID> OriginFlowChain; for (const Fact *F : llvm::reverse(FactMgr.getFacts(Block))) { if (const auto *IF = F->getAs<IssueFact>()) @@ -356,7 +324,7 @@ class AnalysisImpl if (!getLoans(SrcOriginID, OFF).contains(TargetLoan)) continue; - OriginFlowChain.push_back(OFF); + OriginFlowChain.push_back(SrcOriginID); CurrOID = SrcOriginID; } @@ -401,20 +369,4 @@ llvm::SmallVector<OriginID> LoanPropagationAnalysis::buildOriginFlowChain( const UseFact *UF, const LoanID TargetLoan, const CFG *Cfg) const { return PImpl->buildOriginFlowChain(UF, TargetLoan, Cfg); } - -llvm::SmallVector<const OriginFlowFact *> -LoanPropagationAnalysis::buildOriginFlowChainWithFacts(ProgramPoint StartPoint, - const OriginID StartOID, - const LoanID TargetLoan, - const CFG *Cfg) const { - return PImpl->buildOriginFlowChainWithFacts(StartPoint, StartOID, TargetLoan, - Cfg); -} - -llvm::SmallVector<const OriginFlowFact *> -LoanPropagationAnalysis::buildOriginFlowChainWithFacts(const UseFact *UF, - const LoanID TargetLoan, - const CFG *Cfg) const { - return PImpl->buildOriginFlowChainWithFacts(UF, TargetLoan, Cfg); -} } // namespace clang::lifetimes::internal diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 7b628669ed0cf..205ff2f2ae054 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -21,6 +21,7 @@ #include "clang/Lex/Lexer.h" #include "clang/Lex/Preprocessor.h" #include "clang/Sema/Sema.h" +#include <cassert> #include <string> namespace clang::lifetimes { @@ -681,22 +682,20 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { continue; } if (Last.LifetimeBound) { - std::string LifetimeBoundSubject; - if (Last.LifetimeBound->IsImplicitObject) { - LifetimeBoundSubject = "the implicit object parameter"; - } else { - LifetimeBoundSubject = "parameter "; - if (Last.LifetimeBound->Param && - Last.LifetimeBound->Param->getIdentifier()) - LifetimeBoundSubject += - "'" + Last.LifetimeBound->Param->getNameAsString() + "'"; - else - LifetimeBoundSubject += "'<unnamed>'"; + bool IsImplicitObject = isa<const CXXMethodDecl *>(*Last.LifetimeBound); + std::string ParamName; + if (!IsImplicitObject) { + const auto *Param = cast<const ParmVarDecl *>(*Last.LifetimeBound); + assert(Param && + "lifetimebound parameter info should identify a parameter"); + ParamName = Param->getIdentifier() + ? "'" + Param->getNameAsString() + "'" + : "'<unnamed>'"; } S.Diag(CurrExpr->getBeginLoc(), diag::note_lifetime_safety_aliases_storage_lifetimebound) << CurrExpr->getSourceRange() << getDiagSubjectDescription(CurrExpr) - << IssueStr << LifetimeBoundSubject; + << IssueStr << IsImplicitObject << ParamName; } else S.Diag(CurrExpr->getBeginLoc(), diag::note_lifetime_safety_aliases_storage) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
