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 <[email protected]> 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 <[email protected]> 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); + } }; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
