[clang] [LifetimeSafety] Detect dangling fields (PR #177363)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Tue Jan 27 05:31:09 PST 2026
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/177363
>From 1054742703766df84f46b9cba3f7fb9105f6415f 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 | 11 +-
.../Analysis/Analyses/LifetimeSafety/Facts.h | 52 +++++-
.../Analyses/LifetimeSafety/FactsGenerator.h | 4 +-
.../Analyses/LifetimeSafety/LifetimeSafety.h | 9 +-
clang/include/clang/Analysis/CFG.h | 5 +
clang/include/clang/Basic/DiagnosticGroups.td | 11 +-
.../clang/Basic/DiagnosticSemaKinds.td | 7 +
clang/lib/Analysis/CFG.cpp | 20 +-
.../lib/Analysis/FlowSensitive/AdornedCFG.cpp | 1 +
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 70 ++++---
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 | 29 ++-
clang/test/Analysis/lifetime-cfg-output.cpp | 28 ---
clang/test/Analysis/scopes-cfg-output.cpp | 2 -
.../Sema/warn-lifetime-analysis-nocfg.cpp | 26 ++-
.../warn-lifetime-safety-dangling-field.cpp | 175 ++++++++++++++++++
.../Sema/warn-lifetime-safety-dataflow.cpp | 2 +-
.../Sema/warn-lifetime-safety-noescape.cpp | 5 +-
21 files changed, 460 insertions(+), 112 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 f0d3d81f14e43..99ed9e2e64b53 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -140,7 +140,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.
@@ -181,6 +181,15 @@ Improvements to Clang's diagnostics
note: returned here
int* p(int *in [[clang::noescape]]) { 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``
+ 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 1bb34e6986857..d948965af34d5 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 2ab60d918c8d1..9f22db20e79b1 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -62,10 +62,14 @@ class LifetimeSafetySemaHelper {
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 suggestLifetimeboundToParmVar(SuggestionScope Scope,
const ParmVarDecl *ParmToAnnotate,
@@ -74,6 +78,9 @@ class LifetimeSafetySemaHelper {
// Reports misuse of [[clang::noescape]] when parameter escapes through return
virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
const Expr *EscapeExpr) {}
+ // Reports misuse of [[clang::noescape]] when parameter escapes through field
+ virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
+ const FieldDecl *EscapeField) {}
// Suggests lifetime bound annotations for implicit this.
virtual void suggestLifetimeboundToImplicitThis(SuggestionScope Scope,
diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h
index a4bafd4927df0..e675935d9230a 100644
--- a/clang/include/clang/Analysis/CFG.h
+++ b/clang/include/clang/Analysis/CFG.h
@@ -1236,6 +1236,11 @@ class CFG {
bool AddInitializers = false;
bool AddImplicitDtors = false;
bool AddLifetime = false;
+ // Add lifetime markers 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 lifetime ends of parameters on function exit.
+ bool AddParameterLifetimes = false;
bool AddLoopExit = false;
bool AddTemporaryDtors = false;
bool AddScopes = false;
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 488f3a94c4fb6..f7ec24922a50b 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 c786eb4486829..807440c107897 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10826,9 +10826,16 @@ 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 note_lifetime_safety_escapes_to_field_here: Note<"escapes to this field">;
def warn_lifetime_safety_intra_tu_param_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..8001a67a5e158 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -1667,12 +1667,20 @@ 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.AddLifetime && BuildOpts.AddParameterLifetimes) {
+ // Add parameters to the initial scope to handle 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..6f4c3e6531e9b 100644
--- a/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp
+++ b/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp
@@ -162,6 +162,7 @@ llvm::Expected<AdornedCFG> AdornedCFG::build(const Decl &D, Stmt &S,
Options.AddInitializers = true;
Options.AddCXXDefaultInitExprInCtors = true;
Options.AddLifetime = true;
+ Options.AddParameterLifetimes = true;
// Ensure that all sub-expressions in basic blocks are evaluated.
Options.setAllAlwaysAdd();
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index c6368786f34fe..c954a9b14bcdf 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -12,6 +12,7 @@
//===----------------------------------------------------------------------===//
#include "clang/Analysis/Analyses/LifetimeSafety/Checker.h"
+#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
@@ -51,12 +52,13 @@ struct PendingWarning {
using AnnotationTarget =
llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>;
+using EscapingTarget = llvm::PointerUnion<const Expr *, const FieldDecl *>;
class LifetimeChecker {
private:
llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
llvm::DenseMap<AnnotationTarget, const Expr *> AnnotationWarningsMap;
- llvm::DenseMap<const ParmVarDecl *, const Expr *> NoescapeWarningsMap;
+ llvm::DenseMap<const ParmVarDecl *, EscapingTarget> NoescapeWarningsMap;
const LoanPropagationAnalysis &LoanPropagation;
const LiveOriginsAnalysis &LiveOrigins;
const FactManager &FactMgr;
@@ -92,22 +94,36 @@ class LifetimeChecker {
void checkAnnotations(const OriginEscapesFact *OEF) {
OriginID EscapedOID = OEF->getEscapedOriginID();
LoanSet EscapedLoans = LoanPropagation.getLoans(EscapedOID, OEF);
+ auto CheckParam = [&](const ParmVarDecl *PVD) {
+ // NoEscape param should not escape.
+ if (PVD->hasAttr<NoEscapeAttr>()) {
+ if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
+ NoescapeWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
+ if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF))
+ NoescapeWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl());
+ return;
+ }
+ // Suggest lifetimebound for parameter escaping through return.
+ if (!PVD->hasAttr<LifetimeBoundAttr>())
+ if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
+ AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
+ // TODO: Suggest lifetime_capture_by(this) for parameter escaping to a
+ // field!
+ };
+ auto CheckImplicitThis = [&](const CXXMethodDecl *MD) {
+ if (!implicitObjectParamIsLifetimeBound(MD))
+ if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
+ AnnotationWarningsMap.try_emplace(MD, ReturnEsc->getReturnExpr());
+ };
for (LoanID LID : EscapedLoans) {
const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
- if (const auto *PL = dyn_cast<PlaceholderLoan>(L)) {
- if (const auto *PVD = PL->getParmVarDecl()) {
- if (PVD->hasAttr<NoEscapeAttr>()) {
- NoescapeWarningsMap.try_emplace(PVD, OEF->getEscapeExpr());
- continue;
- }
- 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());
- }
- }
+ const auto *PL = dyn_cast<PlaceholderLoan>(L);
+ if (!PL)
+ continue;
+ if (const auto *PVD = PL->getParmVarDecl())
+ CheckParam(PVD);
+ else if (const auto *MD = PL->getMethodDecl())
+ CheckImplicitThis(MD);
}
}
@@ -169,10 +185,16 @@ class LifetimeChecker {
SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(), ExpiryLoc,
Confidence);
else if (const auto *OEF =
- CausingFact.dyn_cast<const OriginEscapesFact *>())
- SemaHelper->reportUseAfterReturn(IssueExpr, OEF->getEscapeExpr(),
- ExpiryLoc, Confidence);
- else
+ CausingFact.dyn_cast<const OriginEscapesFact *>()) {
+ if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF))
+ SemaHelper->reportUseAfterReturn(
+ IssueExpr, RetEscape->getReturnExpr(), ExpiryLoc, Confidence);
+ else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF))
+ SemaHelper->reportDanglingField(
+ IssueExpr, FieldEscape->getFieldDecl(), ExpiryLoc);
+ else
+ llvm_unreachable("Unhandled OriginEscapesFact type");
+ } else
llvm_unreachable("Unhandled CausingFact type");
}
}
@@ -237,8 +259,14 @@ class LifetimeChecker {
}
void reportNoescapeViolations() {
- for (auto [PVD, EscapeExpr] : NoescapeWarningsMap)
- SemaHelper->reportNoescapeViolation(PVD, EscapeExpr);
+ for (auto [PVD, EscapeTarget] : NoescapeWarningsMap) {
+ if (const auto *E = EscapeTarget.dyn_cast<const Expr *>())
+ SemaHelper->reportNoescapeViolation(PVD, E);
+ else if (const auto *FD = EscapeTarget.dyn_cast<const FieldDecl *>())
+ SemaHelper->reportNoescapeViolation(PVD, FD);
+ else
+ llvm_unreachable("Unhandled EscapingTarget type");
+ }
}
void inferAnnotations() {
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 c982b255d54ea..98fadaa11137f 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{};`
@@ -317,31 +331,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) {
@@ -464,6 +489,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 bf539303695b1..9141859a81345 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"
@@ -150,25 +151,33 @@ OriginList *OriginManager::getOrCreateList(const Expr *E) {
return *ThisOrigins;
}
- // 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();
+ else 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 913962dc0c3e0..0c96b0afef1a7 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2891,16 +2891,24 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
<< 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 suggestLifetimeboundToParmVar(SuggestionScope Scope,
@@ -2951,6 +2959,17 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
<< EscapeExpr->getSourceRange();
}
+ void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
+ const FieldDecl *EscapeField) override {
+ S.Diag(ParmWithNoescape->getBeginLoc(),
+ diag::warn_lifetime_safety_noescape_escapes)
+ << ParmWithNoescape->getSourceRange();
+
+ S.Diag(EscapeField->getLocation(),
+ diag::note_lifetime_safety_escapes_to_field_here)
+ << EscapeField->getEndLoc();
+ }
+
void addLifetimeBoundToImplicitThis(const CXXMethodDecl *MD) override {
S.addLifetimeBoundToImplicitThis(const_cast<CXXMethodDecl *>(MD));
}
@@ -2979,6 +2998,7 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU,
AnalysisDeclContext AC(nullptr, FD);
AC.getCFGBuildOptions().PruneTriviallyFalseEdges = false;
AC.getCFGBuildOptions().AddLifetime = true;
+ AC.getCFGBuildOptions().AddParameterLifetimes = true;
AC.getCFGBuildOptions().AddImplicitDtors = true;
AC.getCFGBuildOptions().AddTemporaryDtors = true;
AC.getCFGBuildOptions().setAllAlwaysAdd();
@@ -3087,6 +3107,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
AC.getCFGBuildOptions().AddEHEdges = false;
AC.getCFGBuildOptions().AddInitializers = true;
AC.getCFGBuildOptions().AddImplicitDtors = true;
+ AC.getCFGBuildOptions().AddParameterLifetimes = true;
AC.getCFGBuildOptions().AddTemporaryDtors = true;
AC.getCFGBuildOptions().AddCXXNewAllocator = false;
AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true;
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 29554f5a98029..c82cf41b07361 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -1,5 +1,7 @@
// 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 -Wlifetime-safety -Wno-dangling -verify=cfg,cfg-field %s
+
+// FIXME: cfg-field should be detected in end-of-TU analysis but it doesn't work for constructors!
// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety -Wno-dangling -verify=cfg %s
#include "Inputs/lifetime-analysis.h"
@@ -80,11 +82,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-field-note 3 {{this field dangles}}
+ MyLongPointerFromConversion p2; // expected-note {{pointer member declared here}} \
+ // cfg-field-note 2 {{this field dangles}}
+
+ DanglingGslPtrField(int i) : p(&i) {} // cfg-field-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-field-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-field-warning {{address of stack memory escapes to a field}}
+ DanglingGslPtrField(MyIntOwner io) : p(io) {} // cfg-field-warning {{address of stack memory escapes to a field}}
+ DanglingGslPtrField(MyLongOwnerWithConversion lo) : p2(lo) {} // cfg-field-warning {{address of stack memory escapes to a field}}
};
MyIntPointer danglingGslPtrFromLocal() {
@@ -1099,10 +1108,11 @@ struct Foo2 {
};
struct Test {
- Test(Foo2 foo) : bar(foo.bar.get()), // OK
+ Test(Foo2 foo) : bar(foo.bar.get()), // OK \
+ // FIXME: cfg-field-warning {{address of stack memory escapes to a field}}
storage(std::move(foo.bar)) {};
- Bar* bar;
+ Bar* bar; // cfg-field-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..dfc43e49906f2
--- /dev/null
+++ b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
@@ -0,0 +1,175 @@
+// 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();
+ }
+};
+
+// FIXME: Detect escape to field of field.
+struct IndirectEscape{
+ struct {
+ const char *p;
+ } b;
+
+ void foo() {
+ std::string s;
+ b.p = s.data();
+ }
+};
+
+// FIXME: Detect escape to field of field.
+struct IndirectEscape2 {
+ struct {
+ const char *p;
+ };
+
+ void foo() {
+ std::string s;
+ p = s.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++)
diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
index 91edd2e33edf8..ee661add0acc8 100644
--- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
@@ -120,13 +120,12 @@ void escape_through_global_var(const MyObj& in [[clang::noescape]]) {
global_view = in;
}
-// FIXME: Escaping through a member variable is not detected.
struct ObjConsumer {
- void escape_through_member(const MyObj& in [[clang::noescape]]) {
+ void escape_through_member(const MyObj& in [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
member_view = in;
}
- View member_view;
+ View member_view; // expected-note {{escapes to this field}}
};
// FIXME: Escaping through another param is not detected.
More information about the cfe-commits
mailing list