[clang] [Lifetime Safety] Highlight lifetimebound calls in alias chain diagnostics (PR #206337)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jul 10 08:25:35 PDT 2026
https://github.com/iitianpushkar updated https://github.com/llvm/llvm-project/pull/206337
>From 666b03577b08a11d6c0c5df8a4daab2c4080cf2c Mon Sep 17 00:00:00 2001
From: iitianpushkar <pushkarsingh0587 at gmail.com>
Date: Tue, 7 Jul 2026 02:01:05 +0530
Subject: [PATCH 1/4] 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 at -1 {{temporary object is destroyed here}}
- // expected-note at -2 {{result of call to 'get' aliases the storage of temporary object}}
+ // expected-note at -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 at -1 {{temporary object is destroyed here}}
- // expected-note at -2 {{result of call to 'get' aliases the storage of temporary object}}
+ // expected-note at -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 at -1 {{temporary object is destroyed here}}
- // expected-note at -2 {{result of call to 'getView' aliases the storage of temporary object}}
+ // expected-note at -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 <pushkarsingh0587 at gmail.com>
Date: Wed, 8 Jul 2026 20:50:04 +0530
Subject: [PATCH 2/4] 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)
>From 10dc54a3575b8dcad63f5050b754928b730808be Mon Sep 17 00:00:00 2001
From: iitianpushkar <pushkarsingh0587 at gmail.com>
Date: Fri, 10 Jul 2026 20:40:29 +0530
Subject: [PATCH 3/4] created a shared helper flow for rederive design
---
.../LifetimeSafety/LifetimeAnnotations.h | 17 +++-
.../Analyses/LifetimeSafety/LifetimeSafety.h | 12 +--
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 95 +------------------
.../LifetimeSafety/FactsGenerator.cpp | 21 +---
.../LifetimeSafety/LifetimeAnnotations.cpp | 83 ++++++++++++++++
clang/lib/Sema/SemaLifetimeSafety.h | 39 ++++----
6 files changed, 127 insertions(+), 140 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 47fcd5dbfd569..85ff329774c7b 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -12,8 +12,10 @@
#include "clang/AST/Attr.h"
#include "clang/AST/DeclCXX.h"
+#include "llvm/ADT/PointerUnion.h"
+#include <optional>
-namespace clang ::lifetimes {
+namespace clang::lifetimes {
// This function is needed because Decl::isInStdNamespace will return false for
// iterators in some STL implementations due to them being defined in a
@@ -56,6 +58,19 @@ getImplicitObjectParamLifetimeBoundAttr(const FunctionDecl *FD);
/// method or because it's a normal assignment operator.
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD);
+using LifetimeBoundParamInfo =
+ llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>;
+
+/// Returns the lifetimebound parameter corresponding to argument I.
+/// For instance methods, argument 0 is the implicit object argument.
+std::optional<LifetimeBoundParamInfo>
+getLifetimeBoundParamInfo(const FunctionDecl *FD, unsigned I);
+
+/// Returns the lifetimebound parameter in Call whose argument corresponds to
+/// Source, if any.
+std::optional<LifetimeBoundParamInfo>
+getLifetimeBoundParamInfoForCallArg(const Expr *Call, const Expr *Source);
+
// Returns true if the implicit object argument (this) of a method call should
// be tracked for GSL lifetime analysis. This applies to STL methods that return
// pointers or references that depend on the lifetime of the object, such as
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 7a4577e890b2f..52d199c51ae24 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -32,7 +32,6 @@
#include "llvm/ADT/PointerUnion.h"
#include <cstddef>
#include <memory>
-#include <optional>
namespace clang::lifetimes {
@@ -51,14 +50,6 @@ 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<LifetimeBoundParamInfo> LifetimeBound;
-};
-
/// Abstract interface for operations requiring Sema access.
///
/// This class exists to break a circular dependency: the LifetimeSafety
@@ -77,8 +68,7 @@ class LifetimeSafetySemaHelper {
virtual void reportUseAfterScope(const Expr *IssueExpr, const Expr *UseExpr,
const Expr *MovedExpr,
SourceLocation FreeLoc,
- llvm::ArrayRef<AliasChainEntry> AliasChain) {
- }
+ llvm::ArrayRef<const Expr *> ExprChain) {}
virtual void reportUseAfterReturn(const Expr *IssueExpr,
const Expr *ReturnExpr,
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 93dbdd602df92..1276b4824d4b1 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -14,7 +14,6 @@
#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"
@@ -41,12 +40,6 @@ 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.
@@ -281,8 +274,7 @@ class LifetimeChecker {
// Scope-based expiry (use-after-scope).
SemaHelper->reportUseAfterScope(
IssueExpr, UF->getUseExpr(), MovedExpr, ExpiryLoc,
- getAliasChain(
- LoanPropagation.buildOriginFlowChain(UF, LID, Cfg)));
+ getExprChain(LoanPropagation.buildOriginFlowChain(UF, LID, Cfg)));
} else if (const auto *OEF =
CausingFact.dyn_cast<const OriginEscapesFact *>()) {
@@ -529,95 +521,18 @@ 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<OriginID> OriginFlowChain) {
- llvm::SmallVector<std::pair<OriginID, AliasChainEntry>> Entries;
+ llvm::SmallVector<const Expr *>
+ getExprChain(llvm::ArrayRef<OriginID> OriginFlowChain) {
+ llvm::SmallVector<const Expr *> Result;
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 auto &Entry : Entries)
- Result.push_back(Entry.second);
+ Result.push_back(CurrExpr);
return Result;
}
};
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index ca88986975299..475c57cf38eb4 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -1096,25 +1096,8 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
flow(CallList, getOriginsList(*Args[0]), /*Kill=*/true);
return;
}
- 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);
- if ((I - 1) < Method->getNumParams())
- // For explicit arguments, find the corresponding parameter
- // declaration.
- PVD = Method->getParamDecl(I - 1);
- } else if (I < FD->getNumParams()) {
- // For free functions or static methods.
- PVD = FD->getParamDecl(I);
- }
- return PVD ? PVD->hasAttr<clang::LifetimeBoundAttr>() : false;
- };
- auto ShouldTrackArg = [FD, &Args, IsArgLifetimeBound](unsigned I) -> bool {
- if (IsArgLifetimeBound(I))
+ auto ShouldTrackArg = [FD, &Args](unsigned I) -> bool {
+ if (getLifetimeBoundParamInfo(FD, I))
return true;
if (const auto *Method = dyn_cast<CXXMethodDecl>(FD);
Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD))
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 6a52616c5d590..6543009cd38b0 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -11,6 +11,8 @@
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/OperatorKinds.h"
@@ -105,6 +107,87 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
return isNormalAssignmentOperator(FD);
}
+std::optional<LifetimeBoundParamInfo>
+getLifetimeBoundParamInfo(const FunctionDecl *FD, unsigned I) {
+ FD = getDeclWithMergedLifetimeBoundAttrs(FD);
+ if (!FD)
+ return std::nullopt;
+
+ 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;
+}
+
+static bool isExprInCallArg(const Expr *Arg, const Expr *Source) {
+ if (!Arg || !Source)
+ return false;
+
+ Arg = Arg->IgnoreParenImpCasts();
+ Source = Source->IgnoreParenImpCasts();
+ if (Arg == Source || Arg->getSourceRange() == Source->getSourceRange())
+ return true;
+
+ for (const Stmt *Child : Arg->children())
+ if (const auto *ChildExpr = dyn_cast_or_null<Expr>(Child))
+ if (isExprInCallArg(ChildExpr, Source))
+ return true;
+
+ return false;
+}
+
+std::optional<LifetimeBoundParamInfo>
+getLifetimeBoundParamInfoForCallArg(const Expr *Call, const Expr *Source) {
+ if (!Call || !Source)
+ return std::nullopt;
+
+ 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());
+ }
+
+ if (!FD)
+ return std::nullopt;
+
+ for (unsigned I = 0; I < Args.size(); ++I)
+ if (isExprInCallArg(Args[I], Source))
+ if (std::optional<LifetimeBoundParamInfo> Info =
+ getLifetimeBoundParamInfo(FD, I))
+ return Info;
+
+ return std::nullopt;
+}
+
bool isInStlNamespace(const Decl *D) {
for (const DeclContext *DC = D->getDeclContext(); DC; DC = DC->getParent()) {
if (DC->isStdNamespace())
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index 205ff2f2ae054..f916bcc4db372 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -21,7 +21,6 @@
#include "clang/Lex/Lexer.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Sema/Sema.h"
-#include <cassert>
#include <string>
namespace clang::lifetimes {
@@ -106,10 +105,9 @@ 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<AliasChainEntry> AliasChain) override {
+ void reportUseAfterScope(const Expr *IssueExpr, const Expr *UseExpr,
+ const Expr *MovedExpr, SourceLocation FreeLoc,
+ llvm::ArrayRef<const Expr *> ExprChain) override {
unsigned DiagID = MovedExpr
? diag::warn_lifetime_safety_use_after_scope_moved
: diag::warn_lifetime_safety_use_after_scope;
@@ -123,7 +121,7 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
S.Diag(FreeLoc, diag::note_lifetime_safety_destroyed_here)
<< DestroyedSubject;
- reportAliasingChain(AliasChain);
+ reportAliasingChain(ExprChain);
S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here)
<< UseExpr->getSourceRange();
@@ -666,28 +664,31 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
return CurrExpr->getSourceRange() != LastExpr->getSourceRange();
}
- void reportAliasingChain(llvm::ArrayRef<AliasChainEntry> OriginExprChain) {
+ void reportAliasingChain(llvm::ArrayRef<const Expr *> OriginExprChain) {
if (OriginExprChain.empty())
return;
- AliasChainEntry Last = OriginExprChain.back();
- const Expr *LastExpr = Last.E;
+ const Expr *LastExpr = OriginExprChain.back();
std::string IssueStr = getDiagSubjectDescription(LastExpr);
+ std::optional<LifetimeBoundParamInfo> HiddenLifetimeBound;
- for (AliasChainEntry Curr : reverse(OriginExprChain.drop_back())) {
- const Expr *CurrExpr = Curr.E;
+ for (const Expr *CurrExpr : reverse(OriginExprChain.drop_back())) {
+ std::optional<LifetimeBoundParamInfo> LifetimeBound =
+ getLifetimeBoundParamInfoForCallArg(CurrExpr, LastExpr);
if (!shouldShowInAliasChain(CurrExpr, LastExpr)) {
- if (!Last.LifetimeBound && Curr.LifetimeBound)
- Last.LifetimeBound = Curr.LifetimeBound;
+ if (!HiddenLifetimeBound && LifetimeBound)
+ HiddenLifetimeBound = LifetimeBound;
continue;
}
- if (Last.LifetimeBound) {
- bool IsImplicitObject = isa<const CXXMethodDecl *>(*Last.LifetimeBound);
+
+ if (!LifetimeBound)
+ LifetimeBound = HiddenLifetimeBound;
+
+ if (LifetimeBound) {
+ bool IsImplicitObject = isa<const CXXMethodDecl *>(*LifetimeBound);
std::string ParamName;
if (!IsImplicitObject) {
- const auto *Param = cast<const ParmVarDecl *>(*Last.LifetimeBound);
- assert(Param &&
- "lifetimebound parameter info should identify a parameter");
+ const auto *Param = cast<const ParmVarDecl *>(*LifetimeBound);
ParamName = Param->getIdentifier()
? "'" + Param->getNameAsString() + "'"
: "'<unnamed>'";
@@ -701,7 +702,7 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
diag::note_lifetime_safety_aliases_storage)
<< CurrExpr->getSourceRange() << getDiagSubjectDescription(CurrExpr)
<< IssueStr;
- Last = Curr;
+ HiddenLifetimeBound.reset();
LastExpr = CurrExpr;
}
}
>From a8aaa978b0154c25cba5d20081afd981d7f3be4b Mon Sep 17 00:00:00 2001
From: iitianpushkar <pushkarsingh0587 at gmail.com>
Date: Fri, 10 Jul 2026 20:55:03 +0530
Subject: [PATCH 4/4] diff cleanup
---
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 1276b4824d4b1..f72f7f80abbc0 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -528,12 +528,12 @@ class LifetimeChecker {
/// to declarations (rather than expressions) are skipped.
llvm::SmallVector<const Expr *>
getExprChain(llvm::ArrayRef<OriginID> OriginFlowChain) {
- llvm::SmallVector<const Expr *> Result;
- for (OriginID OID : OriginFlowChain)
+ llvm::SmallVector<const Expr *> rs;
+ for (const OriginID CurrOID : OriginFlowChain)
if (const Expr *CurrExpr =
- FactMgr.getOriginMgr().getOrigin(OID).getExpr())
- Result.push_back(CurrExpr);
- return Result;
+ FactMgr.getOriginMgr().getOrigin(CurrOID).getExpr())
+ rs.push_back(CurrExpr);
+ return rs;
}
};
} // namespace
More information about the cfe-commits
mailing list