[clang] 7d887bc - [LifetimeSafety] Add suggestion and inference for implicit this (#176703)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Jan 26 02:32:57 PST 2026
Author: Kashika Akhouri
Date: 2026-01-26T11:32:52+01:00
New Revision: 7d887bccc9bc5d493f8aeb558275ce24f520f2c2
URL: https://github.com/llvm/llvm-project/commit/7d887bccc9bc5d493f8aeb558275ce24f520f2c2
DIFF: https://github.com/llvm/llvm-project/commit/7d887bccc9bc5d493f8aeb558275ce24f520f2c2.diff
LOG: [LifetimeSafety] Add suggestion and inference for implicit this (#176703)
This PR implements support for automatically suggesting and inferring
`[[clang::lifetimebound]]` annotations for the implicit `this`.
The analysis now models the implicit this object as an implicit
parameter that carries a "placeholder loan" from the caller's scope. By
tracking this loan through the method body, the analysis can identify if
this reaches an escape point, such as a return statement.
Key Changes:
- Updated `PlaceholderLoan` to hold a union of `ParmVarDecl*` and
`CXXMethodDecl*`
- Extended `OriginManager` to handle `CXXThisExpr` and create a
dedicated origin list for the implicit this parameter
- Modified `FactsGenerator::issuePlaceholderLoans` to create a
placeholder loan for this at the entry of non-static member functions
- Updated `implicitObjectParamIsLifetimeBound` in
`LifetimeAnnotations.cpp` to check for the attribute on the method
declaration itself
- Added logic to skip implicit methods and standard assignment operators
to avoid redundant warnings on boilerplate code
Example:
```cpp
struct ReturnsSelf {
const ReturnsSelf& get() const {
return *this;
}
};
void test_get_on_temporary() {
const ReturnsSelf& s_ref = ReturnsSelf().get();
(void)s_ref;
}
```
Warning:
```
a7.cpp:5:33: warning: implict this in intra-TU function should be marked [[clang::lifetimebound]] [-Wexperimental-lifetime-safety-intra-tu-suggestions]
5 | const ReturnsSelf& get() const {
| ~~~ ^
| [[clang::lifetimebound]]
a7.cpp:6:12: note: param returned here
6 | return *this;
| ^~~~~
a7.cpp:11:30: warning: temporary bound to local reference 's_ref' will be destroyed at the end of the full-expression [-Wdangling]
11 | const ReturnsSelf& s_ref = ReturnsSelf().get();
| ^~~~~~~~~~~~~
```
Fixes https://github.com/llvm/llvm-project/issues/169941
Added:
Modified:
clang/include/clang/Analysis/Analyses/LifetimeSafety/Checker.h
clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/include/clang/Sema/Sema.h
clang/lib/Analysis/LifetimeSafety/Checker.cpp
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
clang/lib/Analysis/LifetimeSafety/Origins.cpp
clang/lib/Sema/AnalysisBasedWarnings.cpp
clang/lib/Sema/SemaDecl.cpp
clang/test/Sema/warn-lifetime-safety-suggestions.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Checker.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Checker.h
index 03636be7d00c3..5c631c92c0167 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Checker.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Checker.h
@@ -28,7 +28,7 @@ namespace clang::lifetimes::internal {
void runLifetimeChecker(const LoanPropagationAnalysis &LoanPropagation,
const LiveOriginsAnalysis &LiveOrigins,
const FactManager &FactMgr, AnalysisDeclContext &ADC,
- LifetimeSafetyReporter *Reporter);
+ LifetimeSafetySemaHelper *SemaHelper);
} // namespace clang::lifetimes::internal
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index a66925b7302ca..1bb34e6986857 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -195,7 +195,7 @@ class TestPointFact : public Fact {
class FactManager {
public:
FactManager(const AnalysisDeclContext &AC, const CFG &Cfg)
- : OriginMgr(AC.getASTContext()) {
+ : OriginMgr(AC.getASTContext(), AC.getDecl()) {
BlockToFacts.resize(Cfg.getNumBlockIDs());
}
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 9c91355355233..25d97b4af1ed5 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -42,10 +42,20 @@ enum class SuggestionScope {
IntraTU // For suggestions on definitions local to a Translation Unit.
};
-class LifetimeSafetyReporter {
+/// Abstract interface for operations requiring Sema access.
+///
+/// This class exists to break a circular dependency: the LifetimeSafety
+/// analysis target cannot directly depend on clangSema (which would create the
+/// cycle: clangSema -> clangAnalysis -> clangAnalysisLifetimeSafety ->
+/// clangSema).
+///
+/// Instead, this interface is implemented in AnalysisBasedWarnings.cpp (part of
+/// clangSema), allowing the analysis to report diagnostics and modify the AST
+/// through Sema without introducing a circular dependency.
+class LifetimeSafetySemaHelper {
public:
- LifetimeSafetyReporter() = default;
- virtual ~LifetimeSafetyReporter() = default;
+ LifetimeSafetySemaHelper() = default;
+ virtual ~LifetimeSafetySemaHelper() = default;
virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
SourceLocation FreeLoc,
@@ -56,15 +66,24 @@ class LifetimeSafetyReporter {
SourceLocation ExpiryLoc,
Confidence Confidence) {}
- // Suggests lifetime bound annotations for function paramters
- virtual void suggestAnnotation(SuggestionScope Scope,
- const ParmVarDecl *ParmToAnnotate,
- const Expr *EscapeExpr) {}
+ // Suggests lifetime bound annotations for function paramters.
+ virtual void suggestLifetimeboundToParmVar(SuggestionScope Scope,
+ const ParmVarDecl *ParmToAnnotate,
+ const Expr *EscapeExpr) {}
+
+ // Suggests lifetime bound annotations for implicit this.
+ virtual void suggestLifetimeboundToImplicitThis(SuggestionScope Scope,
+ const CXXMethodDecl *MD,
+ const Expr *EscapeExpr) {}
+
+ // Adds inferred lifetime bound attribute for implicit this to its
+ // TypeSourceInfo.
+ virtual void addLifetimeBoundToImplicitThis(const CXXMethodDecl *MD) {}
};
/// The main entry point for the analysis.
void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC,
- LifetimeSafetyReporter *Reporter,
+ LifetimeSafetySemaHelper *SemaHelper,
LifetimeSafetyStats &Stats, bool CollectStats);
namespace internal {
@@ -85,7 +104,7 @@ struct LifetimeFactory {
class LifetimeSafetyAnalysis {
public:
LifetimeSafetyAnalysis(AnalysisDeclContext &AC,
- LifetimeSafetyReporter *Reporter);
+ LifetimeSafetySemaHelper *SemaHelper);
void run();
@@ -98,7 +117,7 @@ class LifetimeSafetyAnalysis {
private:
AnalysisDeclContext ∾
- LifetimeSafetyReporter *Reporter;
+ LifetimeSafetySemaHelper *SemaHelper;
LifetimeFactory Factory;
std::unique_ptr<FactManager> FactMgr;
std::unique_ptr<LiveOriginsAnalysis> LiveOrigins;
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
index ee1634a6f5ea2..a366e3e811cbf 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
@@ -15,6 +15,7 @@
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOANS_H
#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
#include "clang/AST/ExprCXX.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Utils.h"
#include "llvm/Support/raw_ostream.h"
@@ -100,10 +101,11 @@ class PathLoan : public Loan {
static bool classof(const Loan *L) { return L->getKind() == Kind::Path; }
};
-/// A placeholder loan held by a function parameter, representing a borrow from
-/// the caller's scope.
+/// A placeholder loan held by a function parameter or an implicit 'this'
+/// object, representing a borrow from the caller's scope.
///
-/// Created at function entry for each pointer or reference parameter with an
+/// Created at function entry for each pointer or reference parameter or for
+/// the implicit 'this' parameter of instance methods, with an
/// origin. Unlike PathLoan, placeholder loans:
/// - Have no IssueExpr (created at function entry, not at a borrow site)
/// - Have no AccessPath (the borrowed object is not visible to the function)
@@ -111,17 +113,27 @@ class PathLoan : public Loan {
/// invalidations (e.g., vector::push_back)
///
/// When a placeholder loan escapes the function (e.g., via return), it
-/// indicates the parameter should be marked [[clang::lifetimebound]], enabling
-/// lifetime annotation suggestions.
+/// indicates the parameter or method should be marked [[clang::lifetimebound]],
+/// enabling lifetime annotation suggestions.
class PlaceholderLoan : public Loan {
- /// The function parameter that holds this placeholder loan.
- const ParmVarDecl *PVD;
+ /// The function parameter or method (representing 'this') that holds this
+ /// placeholder loan.
+ llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *> ParamOrMethod;
public:
PlaceholderLoan(LoanID ID, const ParmVarDecl *PVD)
- : Loan(Kind::Placeholder, ID), PVD(PVD) {}
+ : Loan(Kind::Placeholder, ID), ParamOrMethod(PVD) {}
- const ParmVarDecl *getParmVarDecl() const { return PVD; }
+ PlaceholderLoan(LoanID ID, const CXXMethodDecl *MD)
+ : Loan(Kind::Placeholder, ID), ParamOrMethod(MD) {}
+
+ const ParmVarDecl *getParmVarDecl() const {
+ return ParamOrMethod.dyn_cast<const ParmVarDecl *>();
+ }
+
+ const CXXMethodDecl *getMethodDecl() const {
+ return ParamOrMethod.dyn_cast<const CXXMethodDecl *>();
+ }
void dump(llvm::raw_ostream &OS) const override;
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
index 690faae996f0e..8c638bdcace3f 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
@@ -15,6 +15,7 @@
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_ORIGINS_H
#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/TypeBase.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeStats.h"
@@ -124,7 +125,7 @@ bool doesDeclHaveStorage(const ValueDecl *D);
/// variables and expressions.
class OriginManager {
public:
- explicit OriginManager(ASTContext &AST) : AST(AST) {}
+ explicit OriginManager(ASTContext &AST, const Decl *D);
/// Gets or creates the OriginList for a given ValueDecl.
///
@@ -144,6 +145,10 @@ class OriginManager {
/// \returns The OriginList, or nullptr for non-pointer rvalues.
OriginList *getOrCreateList(const Expr *E);
+ /// Returns the OriginList for the implicit 'this' parameter if the current
+ /// declaration is an instance method.
+ std::optional<OriginList *> getThisOrigins() const { return ThisOrigins; }
+
const Origin &getOrigin(OriginID ID) const;
llvm::ArrayRef<Origin> getOrigins() const { return AllOrigins; }
@@ -172,6 +177,7 @@ class OriginManager {
llvm::BumpPtrAllocator ListAllocator;
llvm::DenseMap<const clang::ValueDecl *, OriginList *> DeclToList;
llvm::DenseMap<const clang::Expr *, OriginList *> ExprToList;
+ std::optional<OriginList *> ThisOrigins;
};
} // namespace clang::lifetimes::internal
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 33461284e11dd..afe37ab88c5c8 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10830,18 +10830,30 @@ def note_lifetime_safety_used_here : Note<"later used here">;
def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
def note_lifetime_safety_returned_here : Note<"returned here">;
-def warn_lifetime_safety_intra_tu_suggestion
+def warn_lifetime_safety_intra_tu_param_suggestion
: Warning<"parameter in intra-TU function should be marked "
"[[clang::lifetimebound]]">,
InGroup<LifetimeSafetyIntraTUSuggestions>,
DefaultIgnore;
-def warn_lifetime_safety_cross_tu_suggestion
+def warn_lifetime_safety_cross_tu_param_suggestion
: Warning<"parameter in cross-TU function should be marked "
"[[clang::lifetimebound]]">,
InGroup<LifetimeSafetyCrossTUSuggestions>,
DefaultIgnore;
+def warn_lifetime_safety_intra_tu_this_suggestion
+ : Warning<"implicit this in intra-TU function should be marked "
+ "[[clang::lifetimebound]]">,
+ InGroup<LifetimeSafetyIntraTUSuggestions>,
+ DefaultIgnore;
+
+def warn_lifetime_safety_cross_tu_this_suggestion
+ : Warning<"implicit this in cross-TU function should be marked "
+ "[[clang::lifetimebound]]">,
+ InGroup<LifetimeSafetyCrossTUSuggestions>,
+ DefaultIgnore;
+
def note_lifetime_safety_suggestion_returned_here : Note<"param returned here">;
// For non-floating point, expressions of the form x == x or x != x
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 48803fae9d837..7b3479bbc3677 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -33,6 +33,7 @@
#include "clang/AST/StmtCXX.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/Basic/AttrSubjectMatchRules.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/CapturedStmt.h"
@@ -1256,6 +1257,10 @@ class Sema final : public SemaBase {
bool ForceComplain = false,
bool (*IsPlausibleResult)(QualType) = nullptr);
+ // Adds implicit lifetime bound attribute for implicit this to its
+ // TypeSourceInfo.
+ void addLifetimeBoundToImplicitThis(CXXMethodDecl *MD);
+
/// Figure out if an expression could be turned into a call.
///
/// Use this when trying to recover from an error where the programmer may
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index f7383126fac38..91fbcc0a98650 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -49,22 +49,26 @@ struct PendingWarning {
Confidence ConfidenceLevel;
};
+using AnnotationTarget =
+ llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>;
+
class LifetimeChecker {
private:
llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
- llvm::DenseMap<const ParmVarDecl *, const Expr *> AnnotationWarningsMap;
+ llvm::DenseMap<AnnotationTarget, const Expr *> AnnotationWarningsMap;
const LoanPropagationAnalysis &LoanPropagation;
const LiveOriginsAnalysis &LiveOrigins;
const FactManager &FactMgr;
- LifetimeSafetyReporter *Reporter;
+ LifetimeSafetySemaHelper *SemaHelper;
ASTContext &AST;
public:
LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation,
const LiveOriginsAnalysis &LiveOrigins, const FactManager &FM,
- AnalysisDeclContext &ADC, LifetimeSafetyReporter *Reporter)
+ AnalysisDeclContext &ADC,
+ LifetimeSafetySemaHelper *SemaHelper)
: LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM),
- Reporter(Reporter), AST(ADC.getASTContext()) {
+ SemaHelper(SemaHelper), AST(ADC.getASTContext()) {
for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
for (const Fact *F : FactMgr.getFacts(B))
if (const auto *EF = F->getAs<ExpireFact>())
@@ -88,10 +92,14 @@ class LifetimeChecker {
for (LoanID LID : EscapedLoans) {
const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
if (const auto *PL = dyn_cast<PlaceholderLoan>(L)) {
- const ParmVarDecl *PVD = PL->getParmVarDecl();
- if (PVD->hasAttr<LifetimeBoundAttr>())
- continue;
- AnnotationWarningsMap.try_emplace(PVD, OEF->getEscapeExpr());
+ if (const auto *PVD = PL->getParmVarDecl()) {
+ if (PVD->hasAttr<LifetimeBoundAttr>())
+ continue;
+ AnnotationWarningsMap.try_emplace(PVD, OEF->getEscapeExpr());
+ } else if (const auto *MD = PL->getMethodDecl()) {
+ if (!implicitObjectParamIsLifetimeBound(MD))
+ AnnotationWarningsMap.try_emplace(MD, OEF->getEscapeExpr());
+ }
}
}
}
@@ -139,7 +147,7 @@ class LifetimeChecker {
}
void issuePendingWarnings() {
- if (!Reporter)
+ if (!SemaHelper)
return;
for (const auto &[LID, Warning] : FinalWarningsMap) {
const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
@@ -151,12 +159,12 @@ class LifetimeChecker {
SourceLocation ExpiryLoc = Warning.ExpiryLoc;
if (const auto *UF = CausingFact.dyn_cast<const UseFact *>())
- Reporter->reportUseAfterFree(IssueExpr, UF->getUseExpr(), ExpiryLoc,
- Confidence);
+ SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(), ExpiryLoc,
+ Confidence);
else if (const auto *OEF =
CausingFact.dyn_cast<const OriginEscapesFact *>())
- Reporter->reportUseAfterReturn(IssueExpr, OEF->getEscapeExpr(),
- ExpiryLoc, Confidence);
+ SemaHelper->reportUseAfterReturn(IssueExpr, OEF->getEscapeExpr(),
+ ExpiryLoc, Confidence);
else
llvm_unreachable("Unhandled CausingFact type");
}
@@ -164,50 +172,81 @@ class LifetimeChecker {
/// Returns the declaration of a function that is visible across translation
/// units, if such a declaration exists and is
diff erent from the definition.
- static const FunctionDecl *getCrossTUDecl(const ParmVarDecl &PVD,
+ static const FunctionDecl *getCrossTUDecl(const FunctionDecl &FD,
SourceManager &SM) {
- const auto *FD = dyn_cast<FunctionDecl>(PVD.getDeclContext());
- if (!FD)
+ if (!FD.isExternallyVisible())
return nullptr;
- if (!FD->isExternallyVisible())
- return nullptr;
- const FileID DefinitionFile = SM.getFileID(FD->getLocation());
- for (const FunctionDecl *Redecl : FD->redecls())
+ const FileID DefinitionFile = SM.getFileID(FD.getLocation());
+ for (const FunctionDecl *Redecl : FD.redecls())
if (SM.getFileID(Redecl->getLocation()) != DefinitionFile)
return Redecl;
return nullptr;
}
+ static const FunctionDecl *getCrossTUDecl(const ParmVarDecl &PVD,
+ SourceManager &SM) {
+ if (const auto *FD = dyn_cast<FunctionDecl>(PVD.getDeclContext()))
+ return getCrossTUDecl(*FD, SM);
+ return nullptr;
+ }
+
+ static void suggestWithScopeForParmVar(LifetimeSafetySemaHelper *SemaHelper,
+ const ParmVarDecl *PVD,
+ SourceManager &SM,
+ const Expr *EscapeExpr) {
+ if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*PVD, SM))
+ SemaHelper->suggestLifetimeboundToParmVar(
+ SuggestionScope::CrossTU,
+ CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()), EscapeExpr);
+ else
+ SemaHelper->suggestLifetimeboundToParmVar(SuggestionScope::IntraTU, PVD,
+ EscapeExpr);
+ }
+
+ static void
+ suggestWithScopeForImplicitThis(LifetimeSafetySemaHelper *SemaHelper,
+ const CXXMethodDecl *MD, SourceManager &SM,
+ const Expr *EscapeExpr) {
+ if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*MD, SM))
+ SemaHelper->suggestLifetimeboundToImplicitThis(
+ SuggestionScope::CrossTU, cast<CXXMethodDecl>(CrossTUDecl),
+ EscapeExpr);
+ else
+ SemaHelper->suggestLifetimeboundToImplicitThis(SuggestionScope::IntraTU,
+ MD, EscapeExpr);
+ }
+
void suggestAnnotations() {
- if (!Reporter)
+ if (!SemaHelper)
return;
SourceManager &SM = AST.getSourceManager();
- for (const auto &[PVD, EscapeExpr] : AnnotationWarningsMap) {
- if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*PVD, SM))
- Reporter->suggestAnnotation(
- SuggestionScope::CrossTU,
- CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()),
- EscapeExpr);
- else
- Reporter->suggestAnnotation(SuggestionScope::IntraTU, PVD, EscapeExpr);
+ for (auto [Target, EscapeExpr] : AnnotationWarningsMap) {
+ if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>())
+ suggestWithScopeForParmVar(SemaHelper, PVD, SM, EscapeExpr);
+ else if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>())
+ suggestWithScopeForImplicitThis(SemaHelper, MD, SM, EscapeExpr);
}
}
void inferAnnotations() {
- for (const auto &[ConstPVD, EscapeExpr] : AnnotationWarningsMap) {
- ParmVarDecl *PVD = const_cast<ParmVarDecl *>(ConstPVD);
- const auto *FD = dyn_cast<FunctionDecl>(PVD->getDeclContext());
- if (!FD)
- continue;
- // Propagates inferred attributes via the most recent declaration to
- // ensure visibility for callers in post-order analysis.
- FD = getDeclWithMergedLifetimeBoundAttrs(FD);
- ParmVarDecl *InferredPVD = const_cast<ParmVarDecl *>(
- FD->getParamDecl(PVD->getFunctionScopeIndex()));
- if (!InferredPVD->hasAttr<LifetimeBoundAttr>())
- InferredPVD->addAttr(
- LifetimeBoundAttr::CreateImplicit(AST, PVD->getLocation()));
+ for (auto [Target, EscapeExpr] : AnnotationWarningsMap) {
+ if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) {
+ if (!implicitObjectParamIsLifetimeBound(MD))
+ SemaHelper->addLifetimeBoundToImplicitThis(cast<CXXMethodDecl>(MD));
+ } else if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>()) {
+ const auto *FD = dyn_cast<FunctionDecl>(PVD->getDeclContext());
+ if (!FD)
+ continue;
+ // Propagates inferred attributes via the most recent declaration to
+ // ensure visibility for callers in post-order analysis.
+ FD = getDeclWithMergedLifetimeBoundAttrs(FD);
+ ParmVarDecl *InferredPVD = const_cast<ParmVarDecl *>(
+ FD->getParamDecl(PVD->getFunctionScopeIndex()));
+ if (!InferredPVD->hasAttr<LifetimeBoundAttr>())
+ InferredPVD->addAttr(
+ LifetimeBoundAttr::CreateImplicit(AST, PVD->getLocation()));
+ }
}
}
};
@@ -216,9 +255,9 @@ class LifetimeChecker {
void runLifetimeChecker(const LoanPropagationAnalysis &LP,
const LiveOriginsAnalysis &LO,
const FactManager &FactMgr, AnalysisDeclContext &ADC,
- LifetimeSafetyReporter *Reporter) {
+ LifetimeSafetySemaHelper *SemaHelper) {
llvm::TimeTraceScope TimeProfile("LifetimeChecker");
- LifetimeChecker Checker(LP, LO, FactMgr, ADC, Reporter);
+ LifetimeChecker Checker(LP, LO, FactMgr, ADC, SemaHelper);
}
} // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 5aad51728054f..c982b255d54ea 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -633,6 +633,13 @@ llvm::SmallVector<Fact *> FactsGenerator::issuePlaceholderLoans() {
return {};
llvm::SmallVector<Fact *> PlaceholderLoanFacts;
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(FD); MD && MD->isInstance()) {
+ OriginList *List = *FactMgr.getOriginMgr().getThisOrigins();
+ const PlaceholderLoan *L =
+ FactMgr.getLoanMgr().createLoan<PlaceholderLoan>(MD);
+ PlaceholderLoanFacts.push_back(
+ FactMgr.createFact<IssueFact>(L->getID(), List->getOuterOriginID()));
+ }
for (const ParmVarDecl *PVD : FD->parameters()) {
OriginList *List = getOriginsList(*PVD);
if (!List)
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
index be0d405bd3086..bf9a65254e8fa 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
@@ -48,9 +48,9 @@ static void DebugOnlyFunction(AnalysisDeclContext &AC, const CFG &Cfg,
}
#endif
-LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC,
- LifetimeSafetyReporter *Reporter)
- : AC(AC), Reporter(Reporter) {}
+LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(
+ AnalysisDeclContext &AC, LifetimeSafetySemaHelper *SemaHelper)
+ : AC(AC), SemaHelper(SemaHelper) {}
void LifetimeSafetyAnalysis::run() {
llvm::TimeTraceScope TimeProfile("LifetimeSafetyAnalysis");
@@ -90,7 +90,7 @@ void LifetimeSafetyAnalysis::run() {
DEBUG_WITH_TYPE("LiveOrigins",
LiveOrigins->dump(llvm::dbgs(), FactMgr->getTestPoints()));
- runLifetimeChecker(*LoanPropagation, *LiveOrigins, *FactMgr, AC, Reporter);
+ runLifetimeChecker(*LoanPropagation, *LiveOrigins, *FactMgr, AC, SemaHelper);
}
void collectLifetimeStats(AnalysisDeclContext &AC, OriginManager &OM,
@@ -103,9 +103,9 @@ void collectLifetimeStats(AnalysisDeclContext &AC, OriginManager &OM,
} // namespace internal
void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC,
- LifetimeSafetyReporter *Reporter,
+ LifetimeSafetySemaHelper *SemaHelper,
LifetimeSafetyStats &Stats, bool CollectStats) {
- internal::LifetimeSafetyAnalysis Analysis(AC, Reporter);
+ internal::LifetimeSafetyAnalysis Analysis(AC, SemaHelper);
Analysis.run();
if (CollectStats)
collectLifetimeStats(AC, Analysis.getFactManager().getOriginMgr(), Stats);
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index ca933f612eb08..bf539303695b1 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -86,6 +86,12 @@ bool doesDeclHaveStorage(const ValueDecl *D) {
return !D->getType()->isReferenceType();
}
+OriginManager::OriginManager(ASTContext &AST, const Decl *D) : AST(AST) {
+ if (const auto *MD = llvm::dyn_cast_or_null<CXXMethodDecl>(D);
+ MD && MD->isInstance())
+ ThisOrigins = buildListForType(MD->getThisType(), MD);
+}
+
OriginList *OriginManager::createNode(const ValueDecl *D, QualType QT) {
OriginID NewID = getNextOriginID();
AllOrigins.emplace_back(NewID, D, QT.getTypePtrOrNull());
@@ -137,6 +143,12 @@ OriginList *OriginManager::getOrCreateList(const Expr *E) {
return It->second;
QualType Type = E->getType();
+ // Special handling for 'this' expressions to share origins with the method's
+ // implicit object parameter.
+ if (llvm::isa<CXXThisExpr>(E)) {
+ assert(ThisOrigins && "origins for 'this' should be set for a method decl");
+ return *ThisOrigins;
+ }
// Special handling for DeclRefExpr to share origins with the underlying decl.
if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 03d84fc935b8e..39995992eb717 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//
#include "clang/Sema/AnalysisBasedWarnings.h"
+#include "TypeLocBuilder.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjC.h"
@@ -30,6 +31,7 @@
#include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
#include "clang/Analysis/Analyses/CalledOnceCheck.h"
#include "clang/Analysis/Analyses/Consumed.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h"
#include "clang/Analysis/Analyses/ReachableCode.h"
#include "clang/Analysis/Analyses/ThreadSafety.h"
@@ -2872,10 +2874,10 @@ class CallableVisitor : public DynamicRecursiveASTVisitor {
namespace clang::lifetimes {
namespace {
-class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter {
+class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
public:
- LifetimeSafetyReporterImpl(Sema &S) : S(S) {}
+ LifetimeSafetySemaHelperImpl(Sema &S) : S(S) {}
void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
SourceLocation FreeLoc, Confidence C) override {
@@ -2901,32 +2903,47 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter {
<< EscapeExpr->getSourceRange();
}
- void suggestAnnotation(SuggestionScope Scope,
- const ParmVarDecl *ParmToAnnotate,
- const Expr *EscapeExpr) override {
- unsigned DiagID;
- switch (Scope) {
- case SuggestionScope::CrossTU:
- DiagID = diag::warn_lifetime_safety_cross_tu_suggestion;
- break;
- case SuggestionScope::IntraTU:
- DiagID = diag::warn_lifetime_safety_intra_tu_suggestion;
- break;
- }
-
+ void suggestLifetimeboundToParmVar(SuggestionScope Scope,
+ const ParmVarDecl *ParmToAnnotate,
+ const Expr *EscapeExpr) override {
+ unsigned DiagID =
+ (Scope == SuggestionScope::CrossTU)
+ ? diag::warn_lifetime_safety_cross_tu_param_suggestion
+ : diag::warn_lifetime_safety_intra_tu_param_suggestion;
SourceLocation InsertionPoint = Lexer::getLocForEndOfToken(
ParmToAnnotate->getEndLoc(), 0, S.getSourceManager(), S.getLangOpts());
-
S.Diag(ParmToAnnotate->getBeginLoc(), DiagID)
<< ParmToAnnotate->getSourceRange()
<< FixItHint::CreateInsertion(InsertionPoint,
" [[clang::lifetimebound]]");
+ S.Diag(EscapeExpr->getBeginLoc(),
+ diag::note_lifetime_safety_suggestion_returned_here)
+ << EscapeExpr->getSourceRange();
+ }
+ void suggestLifetimeboundToImplicitThis(SuggestionScope Scope,
+ const CXXMethodDecl *MD,
+ const Expr *EscapeExpr) override {
+ unsigned DiagID = (Scope == SuggestionScope::CrossTU)
+ ? diag::warn_lifetime_safety_cross_tu_this_suggestion
+ : diag::warn_lifetime_safety_intra_tu_this_suggestion;
+ SourceLocation InsertionPoint;
+ InsertionPoint = Lexer::getLocForEndOfToken(
+ MD->getTypeSourceInfo()->getTypeLoc().getEndLoc(), 0,
+ S.getSourceManager(), S.getLangOpts());
+ S.Diag(InsertionPoint, DiagID)
+ << MD->getNameInfo().getSourceRange()
+ << FixItHint::CreateInsertion(InsertionPoint,
+ " [[clang::lifetimebound]]");
S.Diag(EscapeExpr->getBeginLoc(),
diag::note_lifetime_safety_suggestion_returned_here)
<< EscapeExpr->getSourceRange();
}
+ void addLifetimeBoundToImplicitThis(const CXXMethodDecl *MD) override {
+ S.addLifetimeBoundToImplicitThis(const_cast<CXXMethodDecl *>(MD));
+ }
+
private:
Sema &S;
};
@@ -2939,7 +2956,7 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU,
llvm::TimeTraceScope TimeProfile("LifetimeSafetyTUAnalysis");
CallGraph CG;
CG.addToCallGraph(TU);
- lifetimes::LifetimeSafetyReporterImpl Reporter(S);
+ lifetimes::LifetimeSafetySemaHelperImpl SemaHelper(S);
for (auto *Node : llvm::post_order(&CG)) {
const clang::FunctionDecl *CanonicalFD =
dyn_cast_or_null<clang::FunctionDecl>(Node->getDecl());
@@ -2955,7 +2972,7 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU,
AC.getCFGBuildOptions().AddTemporaryDtors = true;
AC.getCFGBuildOptions().setAllAlwaysAdd();
if (AC.getCFG())
- runLifetimeSafetyAnalysis(AC, &Reporter, LSStats, S.CollectStats);
+ runLifetimeSafetyAnalysis(AC, &SemaHelper, LSStats, S.CollectStats);
}
}
@@ -3190,9 +3207,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
// stable.
if (EnableLifetimeSafetyAnalysis && S.getLangOpts().CPlusPlus) {
if (AC.getCFG()) {
- lifetimes::LifetimeSafetyReporterImpl LifetimeSafetyReporter(S);
- lifetimes::runLifetimeSafetyAnalysis(AC, &LifetimeSafetyReporter, LSStats,
- S.CollectStats);
+ lifetimes::LifetimeSafetySemaHelperImpl LifetimeSafetySemaHelper(S);
+ lifetimes::runLifetimeSafetyAnalysis(AC, &LifetimeSafetySemaHelper,
+ LSStats, S.CollectStats);
}
}
// Check for violations of "called once" parameter properties.
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 8a6d1617151a7..0b3416a12f9f4 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -14772,6 +14772,22 @@ StmtResult Sema::ActOnCXXForRangeIdentifier(Scope *S, SourceLocation IdentLoc,
: IdentLoc);
}
+void Sema::addLifetimeBoundToImplicitThis(CXXMethodDecl *MD) {
+ if (!MD || lifetimes::implicitObjectParamIsLifetimeBound(MD))
+ return;
+ auto *Attr = LifetimeBoundAttr::CreateImplicit(Context, MD->getLocation());
+ QualType MethodType = MD->getType();
+ QualType AttributedType =
+ Context.getAttributedType(Attr, MethodType, MethodType);
+ TypeLocBuilder TLB;
+ if (TypeSourceInfo *TSI = MD->getTypeSourceInfo())
+ TLB.pushFullCopy(TSI->getTypeLoc());
+ AttributedTypeLoc TyLoc = TLB.push<AttributedTypeLoc>(AttributedType);
+ TyLoc.setAttr(Attr);
+ MD->setType(AttributedType);
+ MD->setTypeSourceInfo(TLB.getTypeSourceInfo(Context, AttributedType));
+}
+
void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
if (var->isInvalidDecl()) return;
diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index 974ea06e39651..cfae34420baa7 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -12,6 +12,8 @@ struct View;
struct [[gsl::Owner]] MyObj {
int id;
+ MyObj(int i) : id(i) {}
+ MyObj() {}
~MyObj() {} // Non-trivial destructor
MyObj operator+(MyObj);
@@ -46,6 +48,15 @@ inline View redeclared_in_header(View a) { // expected-warning {{parameter in i
return a; // expected-note {{param returned here}}
}
+struct ReturnThis {
+ const ReturnThis& get() const; // expected-warning {{implicit this in cross-TU function should be marked [[clang::lifetimebound]]}}.
+};
+
+struct ReturnThisPointer {
+ const ReturnThisPointer* get() const; // expected-warning {{implicit this in cross-TU function should be marked [[clang::lifetimebound]]}}.
+};
+
+
#endif // TEST_HEADER_H
//--- test_source.cpp
@@ -161,35 +172,86 @@ static View return_view_static(View a) { // expected-warning {{parameter in int
return a; // expected-note {{param returned here}}
}
-//===----------------------------------------------------------------------===//
-// FIXME Test Cases
-//===----------------------------------------------------------------------===//
+const ReturnThis& ReturnThis::get() const {
+ return *this; // expected-note {{param returned here}}
+}
+
+const ReturnThisPointer* ReturnThisPointer::get() const {
+ return this; // expected-note {{param returned here}}
+}
struct ReturnsSelf {
- const ReturnsSelf& get() const {
- return *this;
+ ReturnsSelf() {}
+ ~ReturnsSelf() {}
+ const ReturnsSelf& get() const { // expected-warning {{implicit this in intra-TU function should be marked [[clang::lifetimebound]]}}.
+ return *this; // expected-note {{param returned here}}
}
};
+struct ReturnThisAnnotated {
+ const ReturnThisAnnotated& get() [[clang::lifetimebound]] { return *this; }
+};
+
struct ViewProvider {
+ ViewProvider(int d) : data(d) {}
+ ~ViewProvider() {}
MyObj data;
- View getView() const {
- return data;
+ View getView() const { // expected-warning {{implicit this in intra-TU function should be marked [[clang::lifetimebound]]}}.
+ return data; // expected-note {{param returned here}}
}
};
-// FIXME: Fails to generate lifetime suggestions for the implicit 'this' parameter, as this feature is not yet implemented.
-void test_get_on_temporary() {
- const ReturnsSelf& s_ref = ReturnsSelf().get();
- (void)s_ref;
+View return_view_field(const ViewProvider& v) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}.
+ return v.data; // expected-note {{param returned here}}
+}
+
+void test_get_on_temporary_pointer() {
+ const ReturnsSelf* s_ref = &ReturnsSelf().get(); // expected-warning {{object whose reference is captured does not live long enough}}.
+ // expected-note at -1 {{destroyed here}}
+ (void)s_ref; // expected-note {{later used here}}
+}
+
+void test_get_on_temporary_ref() {
+ const ReturnsSelf& s_ref = ReturnsSelf().get(); // expected-warning {{object whose reference is captured does not live long enough}}.
+ // expected-note at -1 {{destroyed here}}
+ (void)s_ref; // expected-note {{later used here}}
}
-// FIXME: Fails to generate lifetime suggestions for the implicit 'this' parameter, as this feature is not yet implemented.
void test_getView_on_temporary() {
- View sv = ViewProvider{1}.getView();
- (void)sv;
+ View sv = ViewProvider{1}.getView(); // expected-warning {{object whose reference is captured does not live long enough}}.
+ // expected-note at -1 {{destroyed here}}
+ (void)sv; // expected-note {{later used here}}
}
+void test_get_on_temporary_copy() {
+ ReturnsSelf copy = ReturnsSelf().get();
+ (void)copy;
+}
+
+struct MemberReturn {
+ MyObj data;
+
+ MyObj& getRef() { // expected-warning {{implicit this in intra-TU function should be marked [[clang::lifetimebound]]}}.
+ return data; // expected-note {{param returned here}}
+ }
+
+ MyObj& getRefExplicit() { // expected-warning {{implicit this in intra-TU function should be marked [[clang::lifetimebound]]}}.
+ return this->data; // expected-note {{param returned here}}
+ }
+
+ MyObj& getRefDereference() { // expected-warning {{implicit this in intra-TU function should be marked [[clang::lifetimebound]]}}.
+ return (*this).data; // expected-note {{param returned here}}
+ }
+
+ const MyObj* getPtr() { // expected-warning {{implicit this in intra-TU function should be marked [[clang::lifetimebound]]}}.
+ return &data; // expected-note {{param returned here}}
+ }
+
+ const MyObj* getPtrExplicit() { // expected-warning {{implicit this in intra-TU function should be marked [[clang::lifetimebound]]}}.
+ return &(this->data); // expected-note {{param returned here}}
+ }
+};
+
//===----------------------------------------------------------------------===//
// Annotation Inference Test Cases
//===----------------------------------------------------------------------===//
More information about the cfe-commits
mailing list