Author: Utkarsh Saxena Date: 2026-01-28T13:42:32Z New Revision: 47e9c1db618c4d4a7dc6d9880160cc32b317b686
URL: https://github.com/llvm/llvm-project/commit/47e9c1db618c4d4a7dc6d9880160cc32b317b686 DIFF: https://github.com/llvm/llvm-project/commit/47e9c1db618c4d4a7dc6d9880160cc32b317b686.diff LOG: [LifetimeSafety] Detect dangling fields (#177363) Detect dangling field references when stack memory escapes to class fields. This change extends lifetime safety analysis to detect a common class of temporal memory safety bugs where local variables or parameters are stored in class fields but outlive their scope. - Added a new `FieldEscapeFact` class to represent when an origin escapes via assignment to a field - Refactored `OriginEscapesFact` into a base class with specialized subclasses for different escape scenarios - Added detection for stack memory escaping to fields in constructors and member functions - Implemented new diagnostic for dangling field references with appropriate warning messages Importantly, - Added `AddParameterDtors` option to CFG to add parameter dtors and lifetime ends behind an option. In principle, parameters ctors and dtors do not belong in the function context but in the caller context. This becomes incorrect to include in function's CFG when we have inlined CFGs like some analyses in the analyzer (produces double dtors for arguments). Therefore this provides a way to opt-in to know about destructed params on function exits. Added: clang/test/Sema/warn-lifetime-safety-dangling-field.cpp Modified: clang/docs/ReleaseNotes.rst clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h clang/include/clang/Analysis/CFG.h clang/include/clang/Basic/DiagnosticGroups.td clang/include/clang/Basic/DiagnosticSemaKinds.td clang/lib/Analysis/CFG.cpp clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp clang/lib/Analysis/LifetimeSafety/Checker.cpp clang/lib/Analysis/LifetimeSafety/Facts.cpp clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp clang/lib/Analysis/LifetimeSafety/Origins.cpp clang/lib/Sema/AnalysisBasedWarnings.cpp clang/test/Analysis/lifetime-cfg-output.cpp clang/test/Analysis/scopes-cfg-output.cpp clang/test/Sema/warn-lifetime-analysis-nocfg.cpp clang/test/Sema/warn-lifetime-safety-dataflow.cpp clang/test/Sema/warn-lifetime-safety-noescape.cpp Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index e22e7e0fce05c..b5327c01fbb81 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -145,7 +145,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. @@ -187,6 +187,18 @@ Improvements to Clang's diagnostics 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 diff erent 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..d36ee57fe7651 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -533,14 +533,23 @@ 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..c1b8322c5ec55 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,14 @@ 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 (auto *FD = dyn_cast_if_present<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. _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
