[clang] [LifetimeSafety] Catch intra-method lifetime_capture_by(this) dangles (PR #204630)
Gábor Horváth via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 22 18:49:50 PDT 2026
https://github.com/Xazax-hun updated https://github.com/llvm/llvm-project/pull/204630
>From 593184989a2c9b69f69443413a2addfe508a1e85 Mon Sep 17 00:00:00 2001
From: Gabor Horvath <gaborh at apple.com>
Date: Thu, 18 Jun 2026 17:28:18 +0100
Subject: [PATCH 1/2] [LifetimeSafety] Catch intra-method
lifetime_capture_by(this) dangles
A borrow captured into the object via [[clang::lifetime_capture_by(this)]]
flows into the never-expiring `this` origin, so when the capture and the
captured local's expiry both happen inside one method, `this` is not otherwise
live at the expiry and the dangle was missed. Add a ThisEscapeFact, emitted
for `this` at function exit, so checkExpiry sees the captured local going out of
scope while still held by the object and reports it as use-after-scope.
Assisted-by: Claude Opus 4.8
---
.../Analysis/Analyses/LifetimeSafety/Facts.h | 24 +++++++++++++
.../Analyses/LifetimeSafety/LifetimeSafety.h | 7 ++++
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 13 ++++++-
clang/lib/Analysis/LifetimeSafety/Facts.cpp | 8 +++++
.../LifetimeSafety/FactsGenerator.cpp | 7 ++++
.../Analysis/LifetimeSafety/LiveOrigins.cpp | 2 ++
clang/lib/Sema/SemaLifetimeSafety.h | 21 +++++++++++
.../LifetimeSafety/noescape-violation.cpp | 12 +++++++
clang/test/Sema/LifetimeSafety/safety.cpp | 35 +++++++++++++++++++
9 files changed, 128 insertions(+), 1 deletion(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 16fe31a577fa1b..a78d22a80b527e 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -174,6 +174,8 @@ class OriginEscapesFact : public Fact {
Return, /// Escapes via return statement.
Field, /// Escapes via assignment to a field.
Global, /// Escapes via assignment to global storage.
+ This, /// Escapes via the enclosing object `this`, which outlives the
+ /// method.
} EscKind;
static bool classof(const Fact *F) {
@@ -243,6 +245,28 @@ class GlobalEscapeFact : public OriginEscapesFact {
const LoanPropagationAnalysis *LPA = nullptr) const override;
};
+/// Represents an origin escaping through the enclosing object `this`, which
+/// outlives the current method. Emitted for `this` at function exit so a borrow
+/// still held by the object there is seen as escaping -- e.g. a borrow captured
+/// via [[clang::lifetime_capture_by_this]] that outlived the captured local is
+/// reported as a use-after-scope.
+class ThisEscapeFact : public OriginEscapesFact {
+ SourceLocation Loc;
+
+public:
+ ThisEscapeFact(OriginID OID, SourceLocation Loc)
+ : OriginEscapesFact(OID, EscapeKind::This), Loc(Loc) {}
+
+ static bool classof(const Fact *F) {
+ return F->getKind() == Kind::OriginEscapes &&
+ static_cast<const OriginEscapesFact *>(F)->getEscapeKind() ==
+ EscapeKind::This;
+ }
+ SourceLocation getLoc() const { return Loc; }
+ void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM,
+ const LoanPropagationAnalysis *LPA = nullptr) const override;
+};
+
class UseFact : public Fact {
const Expr *UseExpr;
const OriginList *OList;
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index f8b7d2008bd789..16b8a414a6d10f 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -75,6 +75,13 @@ class LifetimeSafetySemaHelper {
SourceLocation FreeLoc,
llvm::ArrayRef<const Expr *> ExprChain) {}
+ // Overload for a use with only a location and no expression (e.g. a borrow
+ // captured into the object and still held at the capturing method's exit).
+ virtual void reportUseAfterScope(const Expr *IssueExpr, SourceLocation UseLoc,
+ const Expr *MovedExpr,
+ SourceLocation FreeLoc,
+ llvm::ArrayRef<const Expr *> ExprChain) {}
+
// TODO: Report where the object was destroyed when that happens before the
// return (inner scopes, temporaries).
virtual void reportUseAfterReturn(const Expr *IssueExpr,
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 3d2cab92b384bf..6e092896310bc4 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -351,6 +351,8 @@ class LifetimeChecker {
Warning.InvalidatedByExpr);
} else if (isa<ReturnEscapeFact>(OEF)) {
// FIXME: Diagnose invalidated return escapes separately.
+ } else if (isa<ThisEscapeFact>(OEF)) {
+ // FIXME: Diagnose a `this`-held loan invalidated through `this`.
} else
llvm_unreachable("Unhandled OriginEscapesFact type");
} else if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF)) {
@@ -373,7 +375,16 @@ class LifetimeChecker {
IsMain = Func->isMain();
SemaHelper->reportDanglingGlobal(IssueExpr, GlobalEscape->getGlobal(),
MovedExpr, ExpiryLoc, IsMain);
- } else
+ } else if (const auto *ThisEscape = dyn_cast<ThisEscapeFact>(OEF))
+ // A borrow is still held by the object (e.g. captured via
+ // lifetime_capture_by_this) when the captured local goes out of
+ // scope; reuse the use-after-scope diagnostic at the capturing
+ // method.
+ SemaHelper->reportUseAfterScope(
+ IssueExpr, ThisEscape->getLoc(), MovedExpr, ExpiryLoc,
+ getExprChain(
+ LoanPropagation.buildOriginFlowChain(OEF, LID, Cfg)));
+ 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 2d3c161ae8f113..9d3428b59b769c 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -155,6 +155,14 @@ void GlobalEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &,
OS << ", via Global)\n";
}
+void ThisEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &OM,
+ const LoanPropagationAnalysis *) const {
+ OS << "OriginEscapes (";
+ OM.dump(getEscapedOriginID(), OS);
+ OS << ", via This)\n";
+}
+
void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
const OriginManager &OM,
const LoanPropagationAnalysis *) const {
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 6a805bb7be2d86..5e6fe3e458c973 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -858,6 +858,13 @@ void FactsGenerator::handleExitBlock() {
FactMgr.createFact<GlobalEscapeFact>(O.ID, VD));
}
}
+
+ // A borrow captured via [[clang::lifetime_capture_by_this]] flows into the
+ // never-expiring `this` origin, so it is not otherwise live at the captured
+ // local's expiry. Keep `this` live at exit so the dangle is caught.
+ if (auto ThisOrigins = FactMgr.getOriginMgr().getThisOrigins())
+ EscapesInCurrentBlock.push_back(FactMgr.createFact<ThisEscapeFact>(
+ (*ThisOrigins)->getOuterOriginID(), AC.getDecl()->getEndLoc()));
}
void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) {
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index 7f827a28657823..cc9ee66ab0f868 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -69,6 +69,8 @@ static SourceLocation GetFactLoc(CausingFactType F) {
return FieldEsc->getFieldDecl()->getLocation();
if (auto *GlobalEsc = dyn_cast<GlobalEscapeFact>(OEF))
return GlobalEsc->getGlobal()->getLocation();
+ if (auto *ThisEsc = dyn_cast<ThisEscapeFact>(OEF))
+ return ThisEsc->getLoc();
}
llvm_unreachable("unhandled causing fact in PointerUnion");
}
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index 3db298b6fec85c..97a6bed90a4c77 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -164,6 +164,27 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
<< UseExpr->getSourceRange();
}
+ void reportUseAfterScope(const Expr *IssueExpr, SourceLocation UseLoc,
+ const Expr *MovedExpr, SourceLocation FreeLoc,
+ llvm::ArrayRef<const Expr *> ExprChain) override {
+ unsigned DiagID = MovedExpr
+ ? diag::warn_lifetime_safety_use_after_scope_moved
+ : diag::warn_lifetime_safety_use_after_scope;
+ std::string DestroyedSubject = getDiagSubjectDescription(IssueExpr);
+
+ S.Diag(IssueExpr->getExprLoc(), DiagID)
+ << DestroyedSubject << IssueExpr->getSourceRange();
+ if (MovedExpr)
+ S.Diag(MovedExpr->getExprLoc(), diag::note_lifetime_safety_moved_here)
+ << MovedExpr->getSourceRange();
+ S.Diag(FreeLoc, diag::note_lifetime_safety_destroyed_here)
+ << DestroyedSubject;
+
+ reportAliasingChain(ExprChain);
+
+ S.Diag(UseLoc, diag::note_lifetime_safety_used_here);
+ }
+
void reportUseAfterReturn(const Expr *IssueExpr, const Expr *ReturnExpr,
const Expr *MovedExpr,
llvm::ArrayRef<const Expr *> ExprChain) override {
diff --git a/clang/test/Sema/LifetimeSafety/noescape-violation.cpp b/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
index 9978850a92e51b..0ee36d240903b7 100644
--- a/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
+++ b/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
@@ -192,6 +192,18 @@ int* return_spaced_brackets(int* p [ [clang::noescape] /*some comment*/ ]) { //
return p; // expected-note {{returned here}}
}
+// FIXME: A [[clang::noescape]] parameter captured into the object via
+// [[clang::lifetime_capture_by_this]] escapes the function and should be
+// diagnosed. It currently is not: the escape is modeled as a ThisEscapeFact at
+// exit, which does not feed the noescape-violation machinery.
+struct CaptureByThisNoescape {
+ const int *p;
+ void store(const int &x [[clang::lifetime_capture_by_this]]);
+ void captures_noescape(const int &n [[clang::noescape]]) {
+ store(n); // FIXME-warning: parameter is marked [[clang::noescape]] but escapes
+ }
+};
+
namespace callable_wrappers {
std::function<void()> escape_noescape_via_function(int &x [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp
index 18800830cb59f4..5a2ca923dbb1a7 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -4874,3 +4874,38 @@ GslOnlyDerived *DerivedAnnotatedMismatchDropsBorrow() {
return static_cast<GslOnlyDerived *>(b); // no note
}
} // namespace DifferentOriginShapes
+
+const int &passthrough(const int &x [[clang::lifetimebound]]);
+
+// A borrow captured into the implicit object via
+// [[clang::lifetime_capture_by_this]] that outlives the captured local is
+// caught at the capturing method's exit.
+struct CaptureByThis {
+ int member;
+ const int *p;
+ void store(const int &x [[clang::lifetime_capture_by_this]]);
+ void captures_dangling_local() {
+ int local = 0;
+ store(local); // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}} expected-note {{later used here}}
+ void captures_through_alias_chain() {
+ int local = 0;
+ const int &r = passthrough(local); // expected-warning {{local variable 'local' does not live long enough}} \
+ // expected-note {{result of call to 'passthrough' aliases the storage of local variable 'local' because parameter 'x' is marked as lifetimebound}}
+ store(passthrough(r)); // expected-note {{result of call to 'passthrough' aliases the storage of local variable 'local'}}
+ } // expected-note {{local variable 'local' is destroyed here}} expected-note {{later used here}}
+ void captures_in_nested_scope() {
+ {
+ int local = 0;
+ store(local); // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ } // expected-note {{later used here}}
+ // Negative: capturing a caller-scoped reference into `this` does not dangle.
+ void captures_caller_ref(const int &caller_ref) {
+ store(caller_ref);
+ }
+ // Negative: capturing a member (same lifetime as the object) does not dangle.
+ void captures_member() {
+ store(member);
+ }
+};
>From 1b57e81d2fe7a242be80406098d85e7f19be4d39 Mon Sep 17 00:00:00 2001
From: Gabor Horvath <gaborh at apple.com>
Date: Fri, 23 Oct 2026 00:38:26 +0100
Subject: [PATCH 2/2] [LifetimeSafety] Address review comments on
ThisEscapeFact
- Do not emit ThisEscapeFact in destructors, like FieldEscapeFact; the object
is being destroyed, so a borrow captured there does not outlive it.
- Diagnose loans held by `this` that are invalidated before the method exits.
Previously the pending invalidation replaced the use-after-scope warning and
was then dropped, so such dangles went unreported entirely.
- Clarify why `this` is kept live at exit for every method.
Assisted-by: Claude Opus 4.8
---
.../Analyses/LifetimeSafety/LifetimeSafety.h | 8 +++
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 15 ++++-
.../LifetimeSafety/FactsGenerator.cpp | 12 ++--
clang/lib/Sema/SemaLifetimeSafety.h | 64 ++++++++++++-------
.../Sema/LifetimeSafety/invalidations.cpp | 17 +++++
clang/test/Sema/LifetimeSafety/safety.cpp | 5 ++
6 files changed, 93 insertions(+), 28 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 16b8a414a6d10f..eb227eb1566868 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -111,6 +111,14 @@ class LifetimeSafetySemaHelper {
reportUseAfterInvalidation(const ParmVarDecl *PVD, const Expr *UseExpr,
const Expr *InvalidationExpr,
llvm::ArrayRef<const Expr *> ExprChain) {}
+ virtual void
+ reportUseAfterInvalidation(const Expr *IssueExpr, SourceLocation UseLoc,
+ const Expr *InvalidationExpr,
+ llvm::ArrayRef<const Expr *> ExprChain) {}
+ virtual void
+ reportUseAfterInvalidation(const ParmVarDecl *PVD, SourceLocation UseLoc,
+ const Expr *InvalidationExpr,
+ llvm::ArrayRef<const Expr *> ExprChain) {}
virtual void reportInvalidatedField(const Expr *IssueExpr,
const FieldDecl *Field,
const Expr *InvalidationExpr) {}
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 6e092896310bc4..ae0297af25d8d5 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -351,8 +351,19 @@ class LifetimeChecker {
Warning.InvalidatedByExpr);
} else if (isa<ReturnEscapeFact>(OEF)) {
// FIXME: Diagnose invalidated return escapes separately.
- } else if (isa<ThisEscapeFact>(OEF)) {
- // FIXME: Diagnose a `this`-held loan invalidated through `this`.
+ } else if (const auto *ThisEscape = dyn_cast<ThisEscapeFact>(OEF)) {
+ // A loan still held by the object at the method's exit was
+ // invalidated.
+ llvm::SmallVector<const Expr *> ExprChain = getExprChain(
+ LoanPropagation.buildOriginFlowChain(OEF, LID, Cfg));
+ if (IssueExpr)
+ SemaHelper->reportUseAfterInvalidation(
+ IssueExpr, ThisEscape->getLoc(), Warning.InvalidatedByExpr,
+ ExprChain);
+ else if (InvalidatedPVD)
+ SemaHelper->reportUseAfterInvalidation(
+ InvalidatedPVD, ThisEscape->getLoc(),
+ Warning.InvalidatedByExpr, ExprChain);
} else
llvm_unreachable("Unhandled OriginEscapesFact type");
} else if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF)) {
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 5e6fe3e458c973..f222ddfb277a8f 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -859,10 +859,14 @@ void FactsGenerator::handleExitBlock() {
}
}
- // A borrow captured via [[clang::lifetime_capture_by_this]] flows into the
- // never-expiring `this` origin, so it is not otherwise live at the captured
- // local's expiry. Keep `this` live at exit so the dangle is caught.
- if (auto ThisOrigins = FactMgr.getOriginMgr().getThisOrigins())
+ // Keep the never-expiring `this` origin live at exit: the object outlives
+ // the method, so whatever it holds (e.g. borrows captured via
+ // [[clang::lifetime_capture_by_this]], or loans reachable through its
+ // fields) is considered used after the method returns. This is emitted for
+ // every method with a `this` origin, not only those that capture something.
+ // Destructors are skipped since the object is being destroyed.
+ if (auto ThisOrigins = FactMgr.getOriginMgr().getThisOrigins();
+ ThisOrigins && !IsDestructor)
EscapesInCurrentBlock.push_back(FactMgr.createFact<ThisEscapeFact>(
(*ThisOrigins)->getOuterOriginID(), AC.getDecl()->getEndLoc()));
}
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index 97a6bed90a4c77..d3efe874675b05 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -261,33 +261,37 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
reportUseAfterInvalidation(const Expr *IssueExpr, const Expr *UseExpr,
const Expr *InvalidationExpr,
llvm::ArrayRef<const Expr *> ExprChain) override {
- auto WarnDiag = isa<CXXDeleteExpr>(InvalidationExpr)
- ? diag::warn_lifetime_safety_use_after_free
- : diag::warn_lifetime_safety_invalidation;
- std::string InvalidatedSubject = getDiagSubjectDescription(IssueExpr);
- S.Diag(IssueExpr->getExprLoc(), WarnDiag)
- << InvalidatedSubject << IssueExpr->getSourceRange();
- reportInvalidationSite(InvalidationExpr, InvalidatedSubject);
- reportAliasingChain(ExprChain);
- S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here)
- << UseExpr->getSourceRange();
+ reportUseAfterInvalidationImpl(
+ IssueExpr->getExprLoc(), IssueExpr->getSourceRange(),
+ getDiagSubjectDescription(IssueExpr), InvalidationExpr, ExprChain,
+ UseExpr->getExprLoc(), UseExpr->getSourceRange());
}
void
reportUseAfterInvalidation(const ParmVarDecl *PVD, const Expr *UseExpr,
const Expr *InvalidationExpr,
llvm::ArrayRef<const Expr *> ExprChain) override {
-
- auto WarnDiag = isa<CXXDeleteExpr>(InvalidationExpr)
- ? diag::warn_lifetime_safety_use_after_free
- : diag::warn_lifetime_safety_invalidation;
- std::string InvalidatedSubject = getDiagSubjectDescription(PVD);
-
- S.Diag(PVD->getSourceRange().getBegin(), WarnDiag)
- << InvalidatedSubject << PVD->getSourceRange();
- reportInvalidationSite(InvalidationExpr, InvalidatedSubject);
- reportAliasingChain(ExprChain);
- S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here)
- << UseExpr->getSourceRange();
+ reportUseAfterInvalidationImpl(
+ PVD->getSourceRange().getBegin(), PVD->getSourceRange(),
+ getDiagSubjectDescription(PVD), InvalidationExpr, ExprChain,
+ UseExpr->getExprLoc(), UseExpr->getSourceRange());
+ }
+ void
+ reportUseAfterInvalidation(const Expr *IssueExpr, SourceLocation UseLoc,
+ const Expr *InvalidationExpr,
+ llvm::ArrayRef<const Expr *> ExprChain) override {
+ reportUseAfterInvalidationImpl(
+ IssueExpr->getExprLoc(), IssueExpr->getSourceRange(),
+ getDiagSubjectDescription(IssueExpr), InvalidationExpr, ExprChain,
+ UseLoc, SourceRange());
+ }
+ void
+ reportUseAfterInvalidation(const ParmVarDecl *PVD, SourceLocation UseLoc,
+ const Expr *InvalidationExpr,
+ llvm::ArrayRef<const Expr *> ExprChain) override {
+ reportUseAfterInvalidationImpl(
+ PVD->getSourceRange().getBegin(), PVD->getSourceRange(),
+ getDiagSubjectDescription(PVD), InvalidationExpr, ExprChain, UseLoc,
+ SourceRange());
}
void reportInvalidatedField(const Expr *IssueExpr,
@@ -623,6 +627,22 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
<< InvalidatedSubject << InvalidationExpr->getSourceRange();
}
+ void reportUseAfterInvalidationImpl(SourceLocation IssueLoc,
+ SourceRange IssueRange,
+ StringRef InvalidatedSubject,
+ const Expr *InvalidationExpr,
+ llvm::ArrayRef<const Expr *> ExprChain,
+ SourceLocation UseLoc,
+ SourceRange UseRange) {
+ auto WarnDiag = isa<CXXDeleteExpr>(InvalidationExpr)
+ ? diag::warn_lifetime_safety_use_after_free
+ : diag::warn_lifetime_safety_invalidation;
+ S.Diag(IssueLoc, WarnDiag) << InvalidatedSubject << IssueRange;
+ reportInvalidationSite(InvalidationExpr, InvalidatedSubject);
+ reportAliasingChain(ExprChain);
+ S.Diag(UseLoc, diag::note_lifetime_safety_used_here) << UseRange;
+ }
+
std::string getLifetimeBoundFixItText(SourceLocation Loc, bool LeadingSpace,
bool AllowGNUAttrMacro = true) {
const bool UseCXX11AttrSpelling =
diff --git a/clang/test/Sema/LifetimeSafety/invalidations.cpp b/clang/test/Sema/LifetimeSafety/invalidations.cpp
index f96e8259b64ebc..82ce139f5fbfac 100644
--- a/clang/test/Sema/LifetimeSafety/invalidations.cpp
+++ b/clang/test/Sema/LifetimeSafety/invalidations.cpp
@@ -992,3 +992,20 @@ void invalid_after_ternary_reset(bool flag) {
}
} // namespace unique_ptr_invalidation
+
+namespace CapturedByThis {
+struct S {
+ void store(const std::string &s [[clang::lifetime_capture_by_this]]);
+
+ void InvalidatedParam(std::string &s) { // expected-warning {{parameter 's' is later invalidated}}
+ store(s);
+ s.clear(); // expected-note {{parameter 's' is invalidated here}}
+ } // expected-note {{later used here}}
+
+ void InvalidatedLocal() {
+ std::string s;
+ store(s); // expected-warning {{local variable 's' is later invalidated}}
+ s.clear(); // expected-note {{local variable 's' is invalidated here}}
+ } // expected-note {{later used here}}
+};
+} // namespace CapturedByThis
diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp
index 5a2ca923dbb1a7..ada2ebf3e35171 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -4908,4 +4908,9 @@ struct CaptureByThis {
void captures_member() {
store(member);
}
+ // Negative: the object is being destroyed, so nothing outlives the local.
+ ~CaptureByThis() {
+ int local = 0;
+ store(local);
+ }
};
More information about the cfe-commits
mailing list