[llvm-branch-commits] [clang] [LifetimeSafety] Detect dangling fields (PR #177363)
Utkarsh Saxena via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Thu Jan 22 08:01:38 PST 2026
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/177363
>From c956327cfa91371714aea7747f34b2fcb182ba1c Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Thu, 22 Jan 2026 15:48:44 +0000
Subject: [PATCH] [LifetimeSafety] Detect dangling fields
---
clang/docs/ReleaseNotes.rst | 12 +-
.../Analysis/Analyses/LifetimeSafety/Facts.h | 52 +++++-
.../Analyses/LifetimeSafety/FactsGenerator.h | 4 +-
.../Analyses/LifetimeSafety/LifetimeSafety.h | 6 +-
clang/include/clang/Analysis/CFG.h | 5 +
clang/include/clang/Basic/DiagnosticGroups.td | 11 +-
.../clang/Basic/DiagnosticSemaKinds.td | 6 +
clang/lib/Analysis/CFG.cpp | 21 ++-
.../lib/Analysis/FlowSensitive/AdornedCFG.cpp | 1 +
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 17 +-
clang/lib/Analysis/LifetimeSafety/Facts.cpp | 13 +-
.../LifetimeSafety/FactsGenerator.cpp | 68 ++++++--
.../Analysis/LifetimeSafety/LiveOrigins.cpp | 9 +-
clang/lib/Analysis/LifetimeSafety/Origins.cpp | 25 ++-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 16 +-
clang/test/Analysis/lifetime-cfg-output.cpp | 28 ----
clang/test/Analysis/scopes-cfg-output.cpp | 2 -
.../Sema/warn-lifetime-analysis-nocfg.cpp | 25 +--
.../warn-lifetime-safety-dangling-field.cpp | 151 ++++++++++++++++++
.../Sema/warn-lifetime-safety-dataflow.cpp | 2 +-
20 files changed, 380 insertions(+), 94 deletions(-)
create mode 100644 clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index e97b9c14cc700..80f7e39e762f0 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -128,7 +128,7 @@ Improvements to Clang's diagnostics
a CFG-based intra-procedural analysis that detects use-after-free and related
temporal safety bugs. See the
`RFC <https://discourse.llvm.org/t/rfc-intra-procedural-lifetime-analysis-in-clang/86291>`_
- for more details. By design, this warning is enabled in ``-Wall``. To disable
+ for more details. By design, this warning is enabled in ``-Weverything``. To disable
the analysis, use ``-Wno-lifetime-safety`` or ``-fno-lifetime-safety``.
- Added ``-Wlifetime-safety-suggestions`` to enable lifetime annotation suggestions.
@@ -152,6 +152,16 @@ Improvements to Clang's diagnostics
int* p(int *in) { return in; }
^~
+- Added ``-Wlifetime-safety-dangling-field`` to detect dangling field references
+ when stack memory escapes to class fields. This is part of ``-Wlifetime-safety-permissive``
+ and detects cases where local variables or parameters are stored in fields but
+ outlive their scope. For example:
+ .. code-block:: c++
+ struct DanglingView {
+ std::string_view view;
+ DanglingView(std::string s) : view(s) {} // warning: address of stack memory escapes to a field
+ };
+
Improvements to Clang's time-trace
----------------------------------
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index a66925b7302ca..61a4eea05bcb5 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -136,19 +136,63 @@ class OriginFlowFact : public Fact {
const OriginManager &OM) const override;
};
+/// Represents that an origin escapes the current scope through various means.
+/// This is the base class for different escape scenarios.
class OriginEscapesFact : public Fact {
OriginID OID;
- const Expr *EscapeExpr;
public:
+ /// The way an origin can escape the current scope.
+ enum class EscapeKind : uint8_t {
+ Return, /// Escapes via return statement.
+ Field, /// Escapes via assignment to a field.
+ // FIXME: Add support for escape to global (dangling global ptr).
+ } EscKind;
+
static bool classof(const Fact *F) {
return F->getKind() == Kind::OriginEscapes;
}
- OriginEscapesFact(OriginID OID, const Expr *EscapeExpr)
- : Fact(Kind::OriginEscapes), OID(OID), EscapeExpr(EscapeExpr) {}
+ OriginEscapesFact(OriginID OID, EscapeKind EscKind)
+ : Fact(Kind::OriginEscapes), OID(OID), EscKind(EscKind) {}
OriginID getEscapedOriginID() const { return OID; }
- const Expr *getEscapeExpr() const { return EscapeExpr; };
+ EscapeKind getEscapeKind() const { return EscKind; }
+};
+
+/// Represents that an origin escapes via a return statement.
+class ReturnEscapeFact : public OriginEscapesFact {
+ const Expr *ReturnExpr;
+
+public:
+ ReturnEscapeFact(OriginID OID, const Expr *ReturnExpr)
+ : OriginEscapesFact(OID, EscapeKind::Return), ReturnExpr(ReturnExpr) {}
+
+ static bool classof(const Fact *F) {
+ return F->getKind() == Kind::OriginEscapes &&
+ static_cast<const OriginEscapesFact *>(F)->getEscapeKind() ==
+ EscapeKind::Return;
+ }
+ const Expr *getReturnExpr() const { return ReturnExpr; };
+ void dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &OM) const override;
+};
+
+/// Represents that an origin escapes via assignment to a field.
+/// Example: `this->view = local_var;` where local_var outlives the assignment
+/// but not the object containing the field.
+class FieldEscapeFact : public OriginEscapesFact {
+ const FieldDecl *FDecl;
+
+public:
+ FieldEscapeFact(OriginID OID, const FieldDecl *FDecl)
+ : OriginEscapesFact(OID, EscapeKind::Field), FDecl(FDecl) {}
+
+ static bool classof(const Fact *F) {
+ return F->getKind() == Kind::OriginEscapes &&
+ static_cast<const OriginEscapesFact *>(F)->getEscapeKind() ==
+ EscapeKind::Field;
+ }
+ const FieldDecl *getFieldDecl() const { return FDecl; };
void dump(llvm::raw_ostream &OS, const LoanManager &,
const OriginManager &OM) const override;
};
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index a47505ee9f159..e4487b0d1dbc7 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -58,10 +58,12 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr);
+ void handleCXXCtorInitializer(const CXXCtorInitializer *CII);
void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds);
-
void handleTemporaryDtor(const CFGTemporaryDtor &TemporaryDtor);
+ void handleExitBlock();
+
void handleGSLPointerConstruction(const CXXConstructExpr *CCE);
/// Checks if a call-like expression creates a borrow by passing a value to a
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 9c91355355233..8256d5829dcb9 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -52,10 +52,14 @@ class LifetimeSafetyReporter {
Confidence Confidence) {}
virtual void reportUseAfterReturn(const Expr *IssueExpr,
- const Expr *EscapeExpr,
+ const Expr *ReturnExpr,
SourceLocation ExpiryLoc,
Confidence Confidence) {}
+ virtual void reportDanglingField(const Expr *IssueExpr,
+ const FieldDecl *Field,
+ SourceLocation ExpiryLoc) {}
+
// Suggests lifetime bound annotations for function paramters
virtual void suggestAnnotation(SuggestionScope Scope,
const ParmVarDecl *ParmToAnnotate,
diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h
index a4bafd4927df0..16efb24e58211 100644
--- a/clang/include/clang/Analysis/CFG.h
+++ b/clang/include/clang/Analysis/CFG.h
@@ -1235,6 +1235,11 @@ class CFG {
bool AddEHEdges = false;
bool AddInitializers = false;
bool AddImplicitDtors = false;
+ // Add dtors for function parameters. In principle, function parameters are
+ // constructed and destructed in the caller context but analyses could still
+ // choose to include these in the callee's CFG to represent the dtors run on
+ // function exit.
+ bool AddParameterDtors = false;
bool AddLifetime = false;
bool AddLoopExit = false;
bool AddTemporaryDtors = false;
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 34624dd3eed3a..c4cae4a30ec4f 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -533,14 +533,21 @@ def Dangling : DiagGroup<"dangling", [DanglingAssignment,
DanglingGsl,
ReturnStackAddress]>;
-def LifetimeSafetyPermissive : DiagGroup<"lifetime-safety-permissive">;
+def LifetimeSafetyDanglingField : DiagGroup<"lifetime-safety-dangling-field"> {
+ code Documentation = [{Warning to detect dangling field references.}];
+}
+
+def LifetimeSafetyPermissive : DiagGroup<"lifetime-safety-permissive",
+ [LifetimeSafetyDanglingField]>;
def LifetimeSafetyStrict : DiagGroup<"lifetime-safety-strict">;
+
def LifetimeSafety : DiagGroup<"lifetime-safety",
[LifetimeSafetyPermissive, LifetimeSafetyStrict]> {
code Documentation = [{
- Experimental warnings to detect use-after-free and related temporal safety bugs based on lifetime safety analysis.
+ Warnings to detect use-after-free and related temporal safety bugs based on lifetime safety analysis.
}];
}
+
def LifetimeSafetyCrossTUSuggestions
: DiagGroup<"lifetime-safety-cross-tu-suggestions">;
def LifetimeSafetyIntraTUSuggestions
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a2be7ab3791b9..8b05b0ffdd04d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10816,9 +10816,15 @@ def warn_lifetime_safety_return_stack_addr_strict
InGroup<LifetimeSafetyStrict>,
DefaultIgnore;
+def warn_lifetime_safety_dangling_field
+ : Warning<"address of stack memory escapes to a field">,
+ InGroup<LifetimeSafetyDanglingField>,
+ DefaultIgnore;
+
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 note_lifetime_safety_dangling_field_here: Note<"this field dangles">;
def warn_lifetime_safety_intra_tu_suggestion
: Warning<"parameter in intra-TU function should be marked "
diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index a9c7baa00543c..fdca00a0f196f 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -1667,12 +1667,21 @@ std::unique_ptr<CFG> CFGBuilder::buildCFG(const Decl *D, Stmt *Statement) {
assert(Succ == &cfg->getExit());
Block = nullptr; // the EXIT block is empty. Create all other blocks lazily.
- // Add parameters to the initial scope to handle their dtos and lifetime ends.
- LocalScope *paramScope = nullptr;
- if (const auto *FD = dyn_cast_or_null<FunctionDecl>(D))
- for (ParmVarDecl *PD : FD->parameters())
- paramScope = addLocalScopeForVarDecl(PD, paramScope);
-
+ if (BuildOpts.AddParameterDtors) {
+ // Add parameters to the initial scope to handle their dtos and lifetime
+ // ends.
+ LocalScope *paramScope = nullptr;
+ if (const auto *FD = dyn_cast_or_null<FunctionDecl>(D))
+ for (ParmVarDecl *PD : FD->parameters()) {
+ paramScope = addLocalScopeForVarDecl(PD, paramScope);
+ }
+ if (auto *C = dyn_cast<CompoundStmt>(Statement))
+ if (C->body_empty() || !isa<ReturnStmt>(*C->body_rbegin()))
+ // If the body ends with a ReturnStmt, the dtors will be added in
+ // VisitReturnStmt.
+ addAutomaticObjHandling(ScopePos, LocalScope::const_iterator(),
+ Statement);
+ }
if (BuildOpts.AddImplicitDtors)
if (const CXXDestructorDecl *DD = dyn_cast_or_null<CXXDestructorDecl>(D))
addImplicitDtorsForDestructor(DD);
diff --git a/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp b/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp
index 6c4847c7c23fb..c47d7b5f9c3d6 100644
--- a/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp
+++ b/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp
@@ -159,6 +159,7 @@ llvm::Expected<AdornedCFG> AdornedCFG::build(const Decl &D, Stmt &S,
Options.PruneTriviallyFalseEdges = true;
Options.AddImplicitDtors = true;
Options.AddTemporaryDtors = true;
+ Options.AddParameterDtors = true;
Options.AddInitializers = true;
Options.AddCXXDefaultInitExprInCtors = true;
Options.AddLifetime = true;
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index f7383126fac38..9ae17168d9b93 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -91,7 +91,8 @@ class LifetimeChecker {
const ParmVarDecl *PVD = PL->getParmVarDecl();
if (PVD->hasAttr<LifetimeBoundAttr>())
continue;
- AnnotationWarningsMap.try_emplace(PVD, OEF->getEscapeExpr());
+ if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
+ AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
}
}
}
@@ -154,10 +155,16 @@ class LifetimeChecker {
Reporter->reportUseAfterFree(IssueExpr, UF->getUseExpr(), ExpiryLoc,
Confidence);
else if (const auto *OEF =
- CausingFact.dyn_cast<const OriginEscapesFact *>())
- Reporter->reportUseAfterReturn(IssueExpr, OEF->getEscapeExpr(),
- ExpiryLoc, Confidence);
- else
+ CausingFact.dyn_cast<const OriginEscapesFact *>()) {
+ if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF))
+ Reporter->reportUseAfterReturn(IssueExpr, RetEscape->getReturnExpr(),
+ ExpiryLoc, Confidence);
+ else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF))
+ Reporter->reportDanglingField(IssueExpr, FieldEscape->getFieldDecl(),
+ ExpiryLoc);
+ else
+ llvm_unreachable("Unhandled OriginEscapesFact type");
+ } else
llvm_unreachable("Unhandled CausingFact type");
}
}
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 2673ce5ba354b..1fc72aa0a4259 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -45,11 +45,18 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &,
OS << "\n";
}
-void OriginEscapesFact::dump(llvm::raw_ostream &OS, const LoanManager &,
- const OriginManager &OM) const {
+void ReturnEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &OM) const {
OS << "OriginEscapes (";
OM.dump(getEscapedOriginID(), OS);
- OS << ")\n";
+ OS << ", via Return)\n";
+}
+
+void FieldEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &OM) const {
+ OS << "OriginEscapes (";
+ OM.dump(getEscapedOriginID(), OS);
+ OS << ", via Field)\n";
}
void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index d5990597e1614..47c919b7d139d 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -10,6 +10,7 @@
#include <string>
#include "clang/AST/OperationKinds.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
#include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
@@ -107,6 +108,9 @@ void FactsGenerator::run() {
const CFGElement &Element = Block->Elements[I];
if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
Visit(CS->getStmt());
+ else if (std::optional<CFGInitializer> Initializer =
+ Element.getAs<CFGInitializer>())
+ handleCXXCtorInitializer(Initializer->getInitializer());
else if (std::optional<CFGLifetimeEnds> LifetimeEnds =
Element.getAs<CFGLifetimeEnds>())
handleLifetimeEnds(*LifetimeEnds);
@@ -114,6 +118,9 @@ void FactsGenerator::run() {
Element.getAs<CFGTemporaryDtor>())
handleTemporaryDtor(*TemporaryDtor);
}
+ if (Block == &Cfg.getExit())
+ handleExitBlock();
+
CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
EscapesInCurrentBlock.end());
FactMgr.addBlockFacts(Block, CurrentBlockFacts);
@@ -180,6 +187,13 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
}
}
+void FactsGenerator::handleCXXCtorInitializer(const CXXCtorInitializer *CII) {
+ // Flows origins from the initializer expression to the field.
+ // Example: `MyObj(std::string s) : view(s) {}`
+ if (const FieldDecl *FD = CII->getAnyMember())
+ killAndFlowOrigin(*FD, *CII->getInit());
+}
+
void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
// Specifically for conversion operators,
// like `std::string_view p = std::string{};`
@@ -316,31 +330,42 @@ void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) {
if (const Expr *RetExpr = RS->getRetValue()) {
if (OriginList *List = getOriginsList(*RetExpr))
for (OriginList *L = List; L != nullptr; L = L->peelOuterOrigin())
- EscapesInCurrentBlock.push_back(FactMgr.createFact<OriginEscapesFact>(
+ EscapesInCurrentBlock.push_back(FactMgr.createFact<ReturnEscapeFact>(
L->getOuterOriginID(), RetExpr));
}
}
void FactsGenerator::handleAssignment(const Expr *LHSExpr,
const Expr *RHSExpr) {
- if (const auto *DRE_LHS =
- dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) {
- OriginList *LHSList = getOriginsList(*DRE_LHS);
- assert(LHSList && "LHS is a DRE and should have an origin list");
- OriginList *RHSList = getOriginsList(*RHSExpr);
-
- // For operator= with reference parameters (e.g.,
- // `View& operator=(const View&)`), the RHS argument stays an lvalue,
- // unlike built-in assignment where LValueToRValue cast strips the outer
- // lvalue origin. Strip it manually to get the actual value origins being
- // assigned.
- RHSList = getRValueOrigins(RHSExpr, RHSList);
+ LHSExpr = LHSExpr->IgnoreParenImpCasts();
+ OriginList *LHSList = nullptr;
- markUseAsWrite(DRE_LHS);
- // Kill the old loans of the destination origin and flow the new loans
- // from the source origin.
- flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true);
+ if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) {
+ LHSList = getOriginsList(*DRE_LHS);
+ assert(LHSList && "LHS is a DRE and should have an origin list");
+ }
+ // Handle assignment to member fields (e.g., `this->view = s` or `view = s`).
+ // This enables detection of dangling fields when local values escape to
+ // fields.
+ if (const auto *ME_LHS = dyn_cast<MemberExpr>(LHSExpr)) {
+ LHSList = getOriginsList(*ME_LHS);
+ assert(LHSList && "LHS is a MemberExpr and should have an origin list");
}
+ if (!LHSList)
+ return;
+ OriginList *RHSList = getOriginsList(*RHSExpr);
+ // For operator= with reference parameters (e.g.,
+ // `View& operator=(const View&)`), the RHS argument stays an lvalue,
+ // unlike built-in assignment where LValueToRValue cast strips the outer
+ // lvalue origin. Strip it manually to get the actual value origins being
+ // assigned.
+ RHSList = getRValueOrigins(RHSExpr, RHSList);
+
+ if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr))
+ markUseAsWrite(DRE_LHS);
+ // Kill the old loans of the destination origin and flow the new loans
+ // from the source origin.
+ flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true);
}
void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
@@ -463,6 +488,15 @@ void FactsGenerator::handleTemporaryDtor(
}
}
+void FactsGenerator::handleExitBlock() {
+ // Creates FieldEscapeFacts for all field origins that remain live at exit.
+ for (const Origin &O : FactMgr.getOriginMgr().getOrigins())
+ if (O.getDecl())
+ if (auto *FD = dyn_cast<FieldDecl>(O.getDecl()))
+ EscapesInCurrentBlock.push_back(
+ FactMgr.createFact<FieldEscapeFact>(O.ID, FD));
+}
+
void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) {
assert(isGslPointerType(CCE->getType()));
if (CCE->getNumArgs() != 1)
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index 862dca256280f..f210fb4d752d4 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -8,6 +8,7 @@
#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h"
#include "Dataflow.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
#include "llvm/Support/ErrorHandling.h"
namespace clang::lifetimes::internal {
@@ -56,8 +57,12 @@ struct Lattice {
static SourceLocation GetFactLoc(CausingFactType F) {
if (const auto *UF = F.dyn_cast<const UseFact *>())
return UF->getUseExpr()->getExprLoc();
- if (const auto *OEF = F.dyn_cast<const OriginEscapesFact *>())
- return OEF->getEscapeExpr()->getExprLoc();
+ if (const auto *OEF = F.dyn_cast<const OriginEscapesFact *>()) {
+ if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
+ return ReturnEsc->getReturnExpr()->getExprLoc();
+ if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF))
+ return FieldEsc->getFieldDecl()->getLocation();
+ }
llvm_unreachable("unhandled causing fact in PointerUnion");
}
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index ca933f612eb08..6062b3d42ebe2 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -9,6 +9,7 @@
#include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
+#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Expr.h"
@@ -138,25 +139,33 @@ OriginList *OriginManager::getOrCreateList(const Expr *E) {
QualType Type = E->getType();
- // Special handling for DeclRefExpr to share origins with the underlying decl.
- if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+ // Special handling for expressions referring to a decl to share origins with
+ // the underlying decl.
+ const ValueDecl *ReferencedDecl = nullptr;
+ if (auto *DRE = dyn_cast<DeclRefExpr>(E))
+ ReferencedDecl = DRE->getDecl();
+ if (auto *ME = dyn_cast<MemberExpr>(E))
+ if (auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
+ Field && isa<CXXThisExpr>(ME->getBase()))
+ ReferencedDecl = Field;
+ if (ReferencedDecl) {
OriginList *Head = nullptr;
- // For non-reference declarations (e.g., `int* p`), the DeclRefExpr is an
+ // For non-reference declarations (e.g., `int* p`), the expression is an
// lvalue (addressable) that can be borrowed, so we create an outer origin
// for the lvalue itself, with the pointee being the declaration's list.
// This models taking the address: `&p` borrows the storage of `p`, not what
// `p` points to.
- if (doesDeclHaveStorage(DRE->getDecl())) {
- Head = createNode(DRE, QualType{});
- // This ensures origin sharing: multiple DeclRefExprs to the same
+ if (doesDeclHaveStorage(ReferencedDecl)) {
+ Head = createNode(E, QualType{});
+ // This ensures origin sharing: multiple expressions to the same
// declaration share the same underlying origins.
- Head->setInnerOriginList(getOrCreateList(DRE->getDecl()));
+ Head->setInnerOriginList(getOrCreateList(ReferencedDecl));
} else {
// For reference-typed declarations (e.g., `int& r = p`) which have no
// storage, the DeclRefExpr directly reuses the declaration's list since
// references don't add an extra level of indirection at the expression
// level.
- Head = getOrCreateList(DRE->getDecl());
+ Head = getOrCreateList(ReferencedDecl);
}
return ExprToList[E] = Head;
}
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 03d84fc935b8e..8e5a9dadddd8f 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2889,16 +2889,24 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter {
<< UseExpr->getSourceRange();
}
- void reportUseAfterReturn(const Expr *IssueExpr, const Expr *EscapeExpr,
+ void reportUseAfterReturn(const Expr *IssueExpr, const Expr *ReturnExpr,
SourceLocation ExpiryLoc, Confidence C) override {
S.Diag(IssueExpr->getExprLoc(),
C == Confidence::Definite
? diag::warn_lifetime_safety_return_stack_addr_permissive
: diag::warn_lifetime_safety_return_stack_addr_strict)
<< IssueExpr->getSourceRange();
-
- S.Diag(EscapeExpr->getExprLoc(), diag::note_lifetime_safety_returned_here)
- << EscapeExpr->getSourceRange();
+ S.Diag(ReturnExpr->getExprLoc(), diag::note_lifetime_safety_returned_here)
+ << ReturnExpr->getSourceRange();
+ }
+ void reportDanglingField(const Expr *IssueExpr,
+ const FieldDecl *DanglingField,
+ SourceLocation ExpiryLoc) override {
+ S.Diag(IssueExpr->getExprLoc(), diag::warn_lifetime_safety_dangling_field)
+ << IssueExpr->getSourceRange();
+ S.Diag(DanglingField->getLocation(),
+ diag::note_lifetime_safety_dangling_field_here)
+ << DanglingField->getEndLoc();
}
void suggestAnnotation(SuggestionScope Scope,
diff --git a/clang/test/Analysis/lifetime-cfg-output.cpp b/clang/test/Analysis/lifetime-cfg-output.cpp
index 36b36eddc440c..0a75c5bcc0bcc 100644
--- a/clang/test/Analysis/lifetime-cfg-output.cpp
+++ b/clang/test/Analysis/lifetime-cfg-output.cpp
@@ -935,31 +935,3 @@ int backpatched_goto() {
goto label;
i++;
}
-
-// CHECK: [B2 (ENTRY)]
-// CHECK-NEXT: Succs (1): B1
-// CHECK: [B1]
-// CHECK-NEXT: 1: a
-// CHECK-NEXT: 2: [B1.1] (ImplicitCastExpr, LValueToRValue, int)
-// CHECK-NEXT: 3: b
-// CHECK-NEXT: 4: [B1.3] (ImplicitCastExpr, LValueToRValue, int)
-// CHECK-NEXT: 5: [B1.2] + [B1.4]
-// CHECK-NEXT: 6: c
-// CHECK-NEXT: 7: [B1.6] (ImplicitCastExpr, LValueToRValue, int)
-// CHECK-NEXT: 8: [B1.5] + [B1.7]
-// CHECK-NEXT: 9: int res = a + b + c;
-// CHECK-NEXT: 10: res
-// CHECK-NEXT: 11: [B1.10] (ImplicitCastExpr, LValueToRValue, int)
-// CHECK-NEXT: 12: return [B1.11];
-// CHECK-NEXT: 13: [B1.9] (Lifetime ends)
-// CHECK-NEXT: 14: [Parm: c] (Lifetime ends)
-// CHECK-NEXT: 15: [Parm: b] (Lifetime ends)
-// CHECK-NEXT: 16: [Parm: a] (Lifetime ends)
-// CHECK-NEXT: Preds (1): B2
-// CHECK-NEXT: Succs (1): B0
-// CHECK: [B0 (EXIT)]
-// CHECK-NEXT: Preds (1): B1
-int test_param_scope_end_order(int a, int b, int c) {
- int res = a + b + c;
- return res;
-}
diff --git a/clang/test/Analysis/scopes-cfg-output.cpp b/clang/test/Analysis/scopes-cfg-output.cpp
index 9c75492c33a42..6ed6f3638f75b 100644
--- a/clang/test/Analysis/scopes-cfg-output.cpp
+++ b/clang/test/Analysis/scopes-cfg-output.cpp
@@ -1437,14 +1437,12 @@ void test_cleanup_functions() {
// CHECK-NEXT: 4: return;
// CHECK-NEXT: 5: CleanupFunction (cleanup_int)
// CHECK-NEXT: 6: CFGScopeEnd(i)
-// CHECK-NEXT: 7: CFGScopeEnd(m)
// CHECK-NEXT: Preds (1): B3
// CHECK-NEXT: Succs (1): B0
// CHECK: [B2]
// CHECK-NEXT: 1: return;
// CHECK-NEXT: 2: CleanupFunction (cleanup_int)
// CHECK-NEXT: 3: CFGScopeEnd(i)
-// CHECK-NEXT: 4: CFGScopeEnd(m)
// CHECK-NEXT: Preds (1): B3
// CHECK-NEXT: Succs (1): B0
// CHECK: [B3]
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index 99a796c360a7f..2f4c2156d23cf 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -1,6 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -Wdangling -Wdangling-field -Wreturn-stack-address -verify %s
-// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify=cfg %s
-// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety -Wno-dangling -verify=cfg %s
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -mllvm --debug-only=LifetimeFacts -verify=cfg %s
#include "Inputs/lifetime-analysis.h"
@@ -80,11 +79,18 @@ void dangligGslPtrFromTemporary() {
}
struct DanglingGslPtrField {
- MyIntPointer p; // expected-note {{pointer member declared here}}
- MyLongPointerFromConversion p2; // expected-note {{pointer member declared here}}
- DanglingGslPtrField(int i) : p(&i) {} // TODO
- DanglingGslPtrField() : p2(MyLongOwnerWithConversion{}) {} // expected-warning {{initializing pointer member 'p2' to point to a temporary object whose lifetime is shorter than the lifetime of the constructed object}}
- DanglingGslPtrField(double) : p(MyIntOwner{}) {} // expected-warning {{initializing pointer member 'p' to point to a temporary object whose lifetime is shorter than the lifetime of the constructed object}}
+ MyIntPointer p; // expected-note {{pointer member declared here}} \
+ // cfg-note 3 {{this field dangles}}
+ MyLongPointerFromConversion p2; // expected-note {{pointer member declared here}} \
+ // cfg-note 2 {{this field dangles}}
+
+ DanglingGslPtrField(int i) : p(&i) {} // cfg-warning {{address of stack memory escapes to a field}}
+ DanglingGslPtrField() : p2(MyLongOwnerWithConversion{}) {} // expected-warning {{initializing pointer member 'p2' to point to a temporary object whose lifetime is shorter than the lifetime of the constructed object}} \
+ // cfg-warning {{address of stack memory escapes to a field}}
+ DanglingGslPtrField(double) : p(MyIntOwner{}) {} // expected-warning {{initializing pointer member 'p' to point to a temporary object whose lifetime is shorter than the lifetime of the constructed object}} \
+ // cfg-warning {{address of stack memory escapes to a field}}
+ DanglingGslPtrField(MyIntOwner io) : p(io) {} // cfg-warning {{address of stack memory escapes to a field}}
+ DanglingGslPtrField(MyLongOwnerWithConversion lo) : p2(lo) {} // cfg-warning {{address of stack memory escapes to a field}}
};
MyIntPointer danglingGslPtrFromLocal() {
@@ -1099,10 +1105,11 @@ struct Foo2 {
};
struct Test {
- Test(Foo2 foo) : bar(foo.bar.get()), // OK
+ Test(Foo2 foo) : bar(foo.bar.get()), // OK \
+ // FIXME: cfg-warning {{address of stack memory escapes to a field}}
storage(std::move(foo.bar)) {};
- Bar* bar;
+ Bar* bar; // cfg-note {{this field dangles}}
std::unique_ptr<Bar> storage;
};
diff --git a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
new file mode 100644
index 0000000000000..c12b9088e9a38
--- /dev/null
+++ b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
@@ -0,0 +1,151 @@
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify %s
+
+#include "Inputs/lifetime-analysis.h"
+
+template<int N> struct Dummy {};
+static std::string kGlobal = "GLOBAL";
+void takeString(std::string&& s);
+
+std::string_view construct_view(const std::string& str [[clang::lifetimebound]]);
+
+struct CtorInit {
+ std::string_view view; // expected-note {{this field dangles}}
+ CtorInit(std::string s) : view(s) {} // expected-warning {{address of stack memory escapes to a field}}
+};
+
+struct CtorSet {
+ std::string_view view; // expected-note {{this field dangles}}
+ CtorSet(std::string s) { view = s; } // expected-warning {{address of stack memory escapes to a field}}
+};
+
+struct CtorInitLifetimeBound {
+ std::string_view view; // expected-note {{this field dangles}}
+ CtorInitLifetimeBound(std::string s) : view(construct_view(s)) {} // expected-warning {{address of stack memory escapes to a field}}
+};
+
+struct CtorInitButMoved {
+ std::string_view view;
+ CtorInitButMoved(std::string s) : view(s) { takeString(std::move(s)); }
+};
+
+struct CtorInitButMovedOwned {
+ std::string owned;
+ std::string_view view;
+ CtorInitButMovedOwned(std::string s) : view(s), owned(std::move(s)) {}
+ CtorInitButMovedOwned(Dummy<1>, std::string s) : owned(std::move(s)), view(owned) {}
+};
+
+struct CtorInitMultipleViews {
+ std::string_view view1; // expected-note {{this field dangles}}
+ std::string_view view2; // expected-note {{this field dangles}}
+ CtorInitMultipleViews(std::string s) : view1(s), // expected-warning {{address of stack memory escapes to a field}}
+ view2(s) {} // expected-warning {{address of stack memory escapes to a field}}
+};
+
+struct CtorInitMultipleParams {
+ std::string_view view1; // expected-note {{this field dangles}}
+ std::string_view view2; // expected-note {{this field dangles}}
+ CtorInitMultipleParams(std::string s1, std::string s2) : view1(s1), // expected-warning {{address of stack memory escapes to a field}}
+ view2(s2) {} // expected-warning {{address of stack memory escapes to a field}}
+};
+
+struct CtorRefField {
+ const std::string& str; // expected-note {{this field dangles}}
+ const std::string_view& view; // expected-note {{this field dangles}}
+ CtorRefField(std::string s, std::string_view v) : str(s), // expected-warning {{address of stack memory escapes to a field}}
+ view(v) {} // expected-warning {{address of stack memory escapes to a field}}
+ CtorRefField(Dummy<1> ok, const std::string& s, const std::string_view& v): str(s), view(v) {}
+};
+
+struct CtorPointerField {
+ const char* ptr; // expected-note {{this field dangles}}
+ CtorPointerField(std::string s) : ptr(s.data()) {} // expected-warning {{address of stack memory escapes to a field}}
+ CtorPointerField(Dummy<1> ok, const std::string& s) : ptr(s.data()) {}
+ CtorPointerField(Dummy<2> ok, std::string_view view) : ptr(view.data()) {}
+};
+
+struct MemberSetters {
+ std::string_view view; // expected-note 5 {{this field dangles}}
+ const char* p; // expected-note 5 {{this field dangles}}
+
+ void setWithParam(std::string s) {
+ view = s; // expected-warning {{address of stack memory escapes to a field}}
+ p = s.data(); // expected-warning {{address of stack memory escapes to a field}}
+ }
+
+ void setWithParamAndReturn(std::string s) {
+ view = s; // expected-warning {{address of stack memory escapes to a field}}
+ p = s.data(); // expected-warning {{address of stack memory escapes to a field}}
+ return;
+ }
+
+ void setWithParamOk(const std::string& s) {
+ view = s;
+ p = s.data();
+ }
+
+ void setWithParamOkAndReturn(const std::string& s) {
+ view = s;
+ p = s.data();
+ return;
+ }
+
+ void setWithLocal() {
+ std::string s;
+ view = s; // expected-warning {{address of stack memory escapes to a field}}
+ p = s.data(); // expected-warning {{address of stack memory escapes to a field}}
+ }
+
+ void setWithLocalButMoved() {
+ std::string s;
+ view = s;
+ p = s.data();
+ takeString(std::move(s));
+ }
+
+ void setWithGlobal() {
+ view = kGlobal;
+ p = kGlobal.data();
+ }
+
+ void setWithLocalThenWithGlobal() {
+ std::string local;
+ view = local;
+ p = local.data();
+
+ view = kGlobal;
+ p = kGlobal.data();
+ }
+
+ void setWithGlobalThenWithLocal() {
+ view = kGlobal;
+ p = kGlobal.data();
+
+ std::string local;
+ view = local; // expected-warning {{address of stack memory escapes to a field}}
+ p = local.data(); // expected-warning {{address of stack memory escapes to a field}}
+ }
+
+ void use_after_scope() {
+ {
+ std::string local;
+ view = local; // expected-warning {{address of stack memory escapes to a field}}
+ p = local.data(); // expected-warning {{address of stack memory escapes to a field}}
+ }
+ (void)view;
+ (void)p;
+ }
+
+ void use_after_scope_saved_after_reassignment() {
+ {
+ std::string local;
+ view = local;
+ p = local.data();
+ }
+ (void)view;
+ (void)p;
+
+ view = kGlobal;
+ p = kGlobal.data();
+ }
+};
diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
index a45100feb3f28..7e2215b8deedc 100644
--- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
@@ -27,7 +27,7 @@ MyObj* return_local_addr() {
// CHECK-NEXT: Src: [[O_P]] (Decl: p, Type : MyObj *)
// CHECK: Expire ([[L_X]] (Path: x))
// CHECK: Expire ({{[0-9]+}} (Path: p))
-// CHECK: OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr, Type : MyObj *))
+// CHECK: OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr, Type : MyObj *), via Return)
}
// Loan Expiration (Automatic Variable, C++)
More information about the llvm-branch-commits
mailing list