https://github.com/necto updated https://github.com/llvm/llvm-project/pull/201123
>From 6445e885e166190295650ce25276e028a68e737b Mon Sep 17 00:00:00 2001 From: tomasz-kaminski-sonarsource <[email protected]> Date: Fri, 23 Jun 2023 14:17:47 +0200 Subject: [PATCH 01/29] [clang] Trigger checkLifetimeEnd callback from CFGLifetimeEnds element This patch adds handling of the `CFGLifetimeEnd` element to the CSA, and produces a newly created callback `checkLifetimeEnd` for each occurrence of it. It is useful to implement detection of dangling pointers as in: ``` void su_use_after_block () { int* p=0; { int x=1; p=&x; } *p = 2; } // ^ p dangles ``` This patch does not implement the check itself. it is motivated by the discussion in https://discourse.llvm.org/t/what-is-the-status-of-scopeend-and-scopebegin/90861 -- upstreamed from CPP-4539 --- clang/include/clang/Analysis/CFG.h | 75 ++++++++------- clang/include/clang/Analysis/ProgramPoint.h | 80 ++++++++++------ .../clang/StaticAnalyzer/Core/Checker.h | 14 +++ .../StaticAnalyzer/Core/CheckerManager.h | 13 +++ .../Core/PathSensitive/ExprEngine.h | 1 + clang/lib/Analysis/PathDiagnostic.cpp | 3 + clang/lib/Analysis/ProgramPoint.cpp | 5 + .../StaticAnalyzer/Core/CheckerManager.cpp | 55 +++++++++-- clang/lib/StaticAnalyzer/Core/CoreEngine.cpp | 18 ++-- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 16 ++++ clang/unittests/StaticAnalyzer/CMakeLists.txt | 1 + .../StaticAnalyzer/CheckLifetimeEndTest.cpp | 93 +++++++++++++++++++ 12 files changed, 302 insertions(+), 72 deletions(-) create mode 100644 clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index 6c214d9ce10e2..57ebfa47ec90e 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -57,12 +57,15 @@ class CFGElement { enum Kind { // main kind Initializer, - ScopeBegin, - ScopeEnd, NewAllocator, - LifetimeEnds, LoopExit, FullExprCleanup, + // scope marker kind + ScopeBegin, + ScopeEnd, + LifetimeEnds, + SCOPE_BEGIN = ScopeBegin, + SCOPE_END = LifetimeEnds, // stmt kind Statement, Constructor, @@ -290,18 +293,38 @@ class CFGLoopExit : public CFGElement { } }; +/// Base class for representing elements related to the lifetime of automatic +/// objects +class CFGScopeMarker : public CFGElement { +public: + LLVM_ATTRIBUTE_RETURNS_NONNULL const Stmt *getTriggerStmt() const { + return static_cast<const Stmt *>(Data1.getPointer()); + } + +private: + friend class CFGElement; + + static bool isKind(const CFGElement &E) { + return E.getKind() >= SCOPE_BEGIN && E.getKind() <= SCOPE_END; + } + +protected: + CFGScopeMarker() = default; + + explicit CFGScopeMarker(Kind K, const Stmt *S, const void *Ptr2 = nullptr) + : CFGElement(K, S, Ptr2) { + assert(isKind(*this)); + } +}; + /// Represents the point where the lifetime of an automatic object ends -class CFGLifetimeEnds : public CFGElement { +class CFGLifetimeEnds : public CFGScopeMarker { public: explicit CFGLifetimeEnds(const VarDecl *var, const Stmt *stmt) - : CFGElement(LifetimeEnds, var, stmt) {} + : CFGScopeMarker(LifetimeEnds, stmt, var) {} const VarDecl *getVarDecl() const { - return static_cast<const VarDecl *>(Data1.getPointer()); - } - - const Stmt *getTriggerStmt() const { - return static_cast<const Stmt *>(Data2.getPointer()); + return static_cast<const VarDecl *>(Data2.getPointer()); } private: @@ -349,50 +372,40 @@ class CFGFullExprCleanup : public CFGElement { /// Represents beginning of a scope implicitly generated /// by the compiler on encountering a CompoundStmt -class CFGScopeBegin : public CFGElement { +class CFGScopeBegin : public CFGScopeMarker { public: CFGScopeBegin() {} CFGScopeBegin(const VarDecl *VD, const Stmt *S) - : CFGElement(ScopeBegin, VD, S) {} - - // Get statement that triggered a new scope. - const Stmt *getTriggerStmt() const { - return static_cast<const Stmt *>(Data2.getPointer()); - } + : CFGScopeMarker(ScopeBegin, S, VD) {} // Get VD that triggered a new scope. const VarDecl *getVarDecl() const { - return static_cast<const VarDecl *>(Data1.getPointer()); + return static_cast<const VarDecl *>(Data2.getPointer()); } private: friend class CFGElement; - static bool isKind(const CFGElement &E) { - Kind kind = E.getKind(); - return kind == ScopeBegin; + static bool isKind(const CFGElement &elem) { + return elem.getKind() == ScopeBegin; } }; /// Represents end of a scope implicitly generated by /// the compiler after the last Stmt in a CompoundStmt's body -class CFGScopeEnd : public CFGElement { +class CFGScopeEnd : public CFGScopeMarker { public: CFGScopeEnd() {} - CFGScopeEnd(const VarDecl *VD, const Stmt *S) : CFGElement(ScopeEnd, VD, S) {} + CFGScopeEnd(const VarDecl *VD, const Stmt *S) + : CFGScopeMarker(ScopeEnd, S, VD) {} const VarDecl *getVarDecl() const { - return static_cast<const VarDecl *>(Data1.getPointer()); - } - - const Stmt *getTriggerStmt() const { - return static_cast<const Stmt *>(Data2.getPointer()); + return static_cast<const VarDecl *>(Data2.getPointer()); } private: friend class CFGElement; - static bool isKind(const CFGElement &E) { - Kind kind = E.getKind(); - return kind == ScopeEnd; + static bool isKind(const CFGElement &elem) { + return elem.getKind() == ScopeEnd; } }; diff --git a/clang/include/clang/Analysis/ProgramPoint.h b/clang/include/clang/Analysis/ProgramPoint.h index b0fedea2fd8ee..51ae43a334f1f 100644 --- a/clang/include/clang/Analysis/ProgramPoint.h +++ b/clang/include/clang/Analysis/ProgramPoint.h @@ -59,33 +59,36 @@ class SimpleProgramPointTag : public ProgramPointTag { class ProgramPoint { public: - enum Kind { BlockEdgeKind, - BlockEntranceKind, - BlockExitKind, - PreStmtKind, - PreStmtPurgeDeadSymbolsKind, - PostStmtPurgeDeadSymbolsKind, - PostStmtKind, - PreLoadKind, - PostLoadKind, - PreStoreKind, - PostStoreKind, - PostConditionKind, - PostLValueKind, - PostAllocatorCallKind, - MinPostStmtKind = PostStmtKind, - MaxPostStmtKind = PostAllocatorCallKind, - PostInitializerKind, - CallEnterKind, - CallExitBeginKind, - CallExitEndKind, - FunctionExitKind, - PreImplicitCallKind, - PostImplicitCallKind, - MinImplicitCallKind = PreImplicitCallKind, - MaxImplicitCallKind = PostImplicitCallKind, - LoopExitKind, - EpsilonKind}; + enum Kind { + BlockEdgeKind, + BlockEntranceKind, + BlockExitKind, + PreStmtKind, + PreStmtPurgeDeadSymbolsKind, + PostStmtPurgeDeadSymbolsKind, + PostStmtKind, + PreLoadKind, + PostLoadKind, + PreStoreKind, + PostStoreKind, + PostConditionKind, + PostLValueKind, + PostAllocatorCallKind, + MinPostStmtKind = PostStmtKind, + MaxPostStmtKind = PostAllocatorCallKind, + PostInitializerKind, + CallEnterKind, + CallExitBeginKind, + CallExitEndKind, + FunctionExitKind, + PreImplicitCallKind, + PostImplicitCallKind, + MinImplicitCallKind = PreImplicitCallKind, + MaxImplicitCallKind = PostImplicitCallKind, + LoopExitKind, + LifetimeEndKind, + EpsilonKind + }; static StringRef getProgramPointKindName(Kind K); std::optional<SourceLocation> getSourceLocation() const; @@ -725,6 +728,29 @@ class LoopExit : public ProgramPoint { } }; +/// Represents a point when the lifetime ends of any automatic object. +class LifetimeEnd : public ProgramPoint { +public: + LifetimeEnd(const Stmt *S, const VarDecl *D, const StackFrame *SF) + : ProgramPoint(S, D, LifetimeEndKind, SF) {} + + LLVM_ATTRIBUTE_RETURNS_NONNULL const Stmt *getTriggerStmt() const { + return static_cast<const Stmt *>(getData1()); + } + + /// Returns a variable declaration that uniquely identifies the scope + LLVM_ATTRIBUTE_RETURNS_NONNULL const VarDecl *getDecl() const { + return static_cast<const VarDecl *>(getData2()); + } + +private: + friend class ProgramPoint; + LifetimeEnd() = default; + static bool isKind(const ProgramPoint &Location) { + return Location.getKind() == LifetimeEndKind; + } +}; + /// This is a meta program point, which should be skipped by all the diagnostic /// reasoning etc. class EpsilonPoint : public ProgramPoint { diff --git a/clang/include/clang/StaticAnalyzer/Core/Checker.h b/clang/include/clang/StaticAnalyzer/Core/Checker.h index ab38a05bfd79d..78772b12d4709 100644 --- a/clang/include/clang/StaticAnalyzer/Core/Checker.h +++ b/clang/include/clang/StaticAnalyzer/Core/Checker.h @@ -206,6 +206,20 @@ class Location { } }; +class LifetimeEnd { + template <typename CHECKER> + static void _checkCall(void *checker, const VarDecl *D, CheckerContext &C) { + ((const CHECKER *)checker)->checkLifetimeEnd(D, C); + } + +public: + template <typename CHECKER> + static void _register(CHECKER *checker, CheckerManager &mgr) { + mgr._registerForLifetimeEnd( + CheckerManager::CheckLifetimeEndFunc(checker, _checkCall<CHECKER>)); + } +}; + class Bind { template <typename CHECKER> static void _checkBind(void *checker, SVal location, SVal val, const Stmt *S, diff --git a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h index 9d352960c1db7..e1cd01ef13f2d 100644 --- a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h +++ b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h @@ -330,6 +330,12 @@ class CheckerManager { const CallEvent &Call, ExprEngine &Eng, bool wasInlined = false); + /// Run checkers for end of variable lifetime + void runCheckersForLifetimeEnd(ExplodedNodeSet &Dst, + const ExplodedNodeSet &Src, + const VarDecl *Decl, const Stmt *TriggerStmt, + ExprEngine &Eng); + /// Run checkers for load/store of a location. void runCheckersForLocation(ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, @@ -493,6 +499,9 @@ class CheckerManager { using CheckCallFunc = CheckerFn<void (const CallEvent &, CheckerContext &)>; + using CheckLifetimeEndFunc = + CheckerFn<void(const VarDecl *, CheckerContext &)>; + using CheckLocationFunc = CheckerFn<void(SVal location, bool isLoad, const Stmt *S, CheckerContext &)>; @@ -557,6 +566,8 @@ class CheckerManager { void _registerForPreCall(CheckCallFunc checkfn); void _registerForPostCall(CheckCallFunc checkfn); + void _registerForLifetimeEnd(CheckLifetimeEndFunc checkfn); + void _registerForLocation(CheckLocationFunc checkfn); void _registerForBind(CheckBindFunc checkfn); @@ -665,6 +676,8 @@ class CheckerManager { std::vector<CheckCallFunc> PreCallCheckers; std::vector<CheckCallFunc> PostCallCheckers; + std::vector<CheckLifetimeEndFunc> LifetimeEndCheckers; + std::vector<CheckLocationFunc> LocationCheckers; std::vector<CheckBindFunc> BindCheckers; diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h index e4ddabbe9c927..bb2de45cec92a 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -368,6 +368,7 @@ class ExprEngine { void ProcessStmt(const Stmt *S, ExplodedNode *Pred); void ProcessLoopExit(const Stmt* S, ExplodedNode *Pred); + void ProcessLifetimeEnd(const Stmt *S, const VarDecl *D, ExplodedNode *Pred); void ProcessInitializer(const CFGInitializer I, ExplodedNode *Pred); diff --git a/clang/lib/Analysis/PathDiagnostic.cpp b/clang/lib/Analysis/PathDiagnostic.cpp index 24fed40a93ed1..a95ef4582be99 100644 --- a/clang/lib/Analysis/PathDiagnostic.cpp +++ b/clang/lib/Analysis/PathDiagnostic.cpp @@ -720,6 +720,9 @@ PathDiagnosticLocation::create(const ProgramPoint& P, } else if (std::optional<FunctionExitPoint> FE = P.getAs<FunctionExitPoint>()) { return PathDiagnosticLocation(FE->getStmt(), SMng, FE->getStackFrame()); + } else if (std::optional<LifetimeEnd> LE = P.getAs<LifetimeEnd>()) { + return PathDiagnosticLocation::createEnd(LE->getTriggerStmt(), SMng, + LE->getStackFrame()); } else { llvm_unreachable("Unexpected ProgramPoint"); } diff --git a/clang/lib/Analysis/ProgramPoint.cpp b/clang/lib/Analysis/ProgramPoint.cpp index 11c8c8242eb19..697e0afd30ed2 100644 --- a/clang/lib/Analysis/ProgramPoint.cpp +++ b/clang/lib/Analysis/ProgramPoint.cpp @@ -217,6 +217,11 @@ void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { << castAs<LoopExit>().getLoopStmt()->getStmtClassName() << '\"'; break; + case ProgramPoint::LifetimeEndKind: + Out << "LifetimeEnd\", \"var\": \"" + << castAs<LifetimeEnd>().getDecl()->getNameAsString() << '\"'; + break; + case ProgramPoint::PreImplicitCallKind: { ImplicitCallPoint PC = castAs<ImplicitCallPoint>(); Out << "PreCall\", \"decl\": \"" diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp index 35603e333bf17..138d4bc8d95ad 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -35,17 +35,18 @@ using namespace clang; using namespace ento; bool CheckerManager::hasPathSensitiveCheckers() const { - const auto IfAnyAreNonEmpty = [](const auto &... Callbacks) -> bool { + const auto IfAnyAreNonEmpty = [](const auto &...Callbacks) -> bool { return (!Callbacks.empty() || ...); }; return IfAnyAreNonEmpty( StmtCheckers, PreObjCMessageCheckers, ObjCMessageNilCheckers, PostObjCMessageCheckers, PreCallCheckers, PostCallCheckers, - LocationCheckers, BindCheckers, BlockEntranceCheckers, - EndAnalysisCheckers, BeginFunctionCheckers, EndFunctionCheckers, - BranchConditionCheckers, NewAllocatorCheckers, LiveSymbolsCheckers, - DeadSymbolsCheckers, RegionChangesCheckers, PointerEscapeCheckers, - EvalAssumeCheckers, EvalCallCheckers, EndOfTranslationUnitCheckers); + LifetimeEndCheckers, LocationCheckers, BindCheckers, + BlockEntranceCheckers, EndAnalysisCheckers, BeginFunctionCheckers, + EndFunctionCheckers, BranchConditionCheckers, NewAllocatorCheckers, + LiveSymbolsCheckers, DeadSymbolsCheckers, RegionChangesCheckers, + PointerEscapeCheckers, EvalAssumeCheckers, EvalCallCheckers, + EndOfTranslationUnitCheckers); } void CheckerManager::reportInvalidCheckerOptionValue( @@ -311,6 +312,44 @@ void CheckerManager::runCheckersForCallEvent(bool isPreVisit, expandGraphWithCheckers(C, Dst, Src); } +namespace { + +struct CheckLifetimeEndContext { + using CheckersTy = std::vector<CheckerManager::CheckLifetimeEndFunc>; + + const CheckersTy &Checkers; + const VarDecl *Decl; + const Stmt *TriggerStmt; + ExprEngine &Eng; + + CheckLifetimeEndContext(const CheckersTy &checkers, const VarDecl *decl, + const Stmt *stmt, ExprEngine &eng) + : Checkers(checkers), Decl(decl), TriggerStmt(stmt), Eng(eng) {} + + CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } + CheckersTy::const_iterator checkers_end() { return Checkers.end(); } + + void runChecker(CheckerManager::CheckLifetimeEndFunc checkFn, + NodeBuilder &Bldr, ExplodedNode *Pred) { + assert(Pred->getLocation().getAs<LifetimeEnd>().has_value()); + const ProgramPoint L = Pred->getLocation().withTag(checkFn.Checker); + CheckerContext C(Bldr, Eng, Pred, L); + checkFn(Decl, C); + } +}; + +} // namespace + +/// Run checkers for end of variable lifetime +void CheckerManager::runCheckersForLifetimeEnd(ExplodedNodeSet &Dst, + const ExplodedNodeSet &Src, + const VarDecl *Decl, + const Stmt *TriggerStmt, + ExprEngine &Eng) { + CheckLifetimeEndContext C(LifetimeEndCheckers, Decl, TriggerStmt, Eng); + expandGraphWithCheckers(C, Dst, Src); +} + namespace { struct CheckLocationContext { @@ -903,6 +942,10 @@ void CheckerManager::_registerForPostCall(CheckCallFunc checkfn) { PostCallCheckers.push_back(checkfn); } +void CheckerManager::_registerForLifetimeEnd(CheckLifetimeEndFunc checkfn) { + LifetimeEndCheckers.push_back(checkfn); +} + void CheckerManager::_registerForLocation(CheckLocationFunc checkfn) { LocationCheckers.push_back(checkfn); } diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp index 2a64e340ed214..b38dfdfa6f2c4 100644 --- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -254,11 +254,9 @@ void CoreEngine::dispatchWorkItem(ExplodedNode *Pred, ProgramPoint Loc, break; } default: - assert(Loc.getAs<PostStmt>() || - Loc.getAs<PostInitializer>() || - Loc.getAs<PostImplicitCall>() || - Loc.getAs<CallExitEnd>() || - Loc.getAs<LoopExit>() || + assert(Loc.getAs<PostStmt>() || Loc.getAs<PostInitializer>() || + Loc.getAs<PostImplicitCall>() || Loc.getAs<CallExitEnd>() || + Loc.getAs<LoopExit>() || Loc.getAs<LifetimeEnd>() || Loc.getAs<PostAllocatorCall>()); HandlePostStmt(WU.getBlock(), WU.getIndex(), Pred); break; @@ -306,6 +304,9 @@ void CoreEngine::HandleBlockEdge(const BlockEdge &L, ExplodedNode *Pred) { } else if (std::optional<CFGAutomaticObjDtor> AutoDtor = LastElement.getAs<CFGAutomaticObjDtor>()) { RS = dyn_cast<ReturnStmt>(AutoDtor->getTriggerStmt()); + } else if (std::optional<CFGScopeMarker> ScopeMarker = + LastElement.getAs<CFGScopeMarker>()) { + RS = dyn_cast<ReturnStmt>(ScopeMarker->getTriggerStmt()); } } @@ -591,9 +592,10 @@ void CoreEngine::enqueueStmtNode(ExplodedNode *N, // Do not create extra nodes. Move to the next CFG element. if (N->getLocation().getAs<PostInitializer>() || - N->getLocation().getAs<PostImplicitCall>()|| - N->getLocation().getAs<LoopExit>()) { - WList->enqueue(N, Block, Idx+1); + N->getLocation().getAs<PostImplicitCall>() || + N->getLocation().getAs<LoopExit>() || + N->getLocation().getAs<LifetimeEnd>()) { + WList->enqueue(N, Block, Idx + 1); return; } diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 34ca88705cba6..21dff1961a76d 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -977,6 +977,9 @@ void ExprEngine::processCFGElement(const CFGElement E, ExplodedNode *Pred, ProcessLoopExit(E.castAs<CFGLoopExit>().getLoopStmt(), Pred); return; case CFGElement::LifetimeEnds: + ProcessLifetimeEnd(E.castAs<CFGLifetimeEnds>().getTriggerStmt(), + E.castAs<CFGLifetimeEnds>().getVarDecl(), Pred); + return; case CFGElement::CleanupFunction: case CFGElement::FullExprCleanup: case CFGElement::ScopeBegin: @@ -1140,6 +1143,19 @@ void ExprEngine::ProcessLoopExit(const Stmt* S, ExplodedNode *Pred) { Engine.enqueueStmtNode(N, getCurrBlock(), currStmtIdx); } +void ExprEngine::ProcessLifetimeEnd(const Stmt *S, const VarDecl *D, + ExplodedNode *Pred) { + ExplodedNodeSet Src; + NodeBuilder Bldr(Pred, Src, *currBldrCtx); + LifetimeEnd PP(S, D, Pred->getStackFrame()); + Bldr.generateNode(PP, Pred->getState(), Pred); + + ExplodedNodeSet Dst; + getCheckerManager().runCheckersForLifetimeEnd(Dst, Src, D, S, *this); + Engine.enqueueStmtNodes(Dst, currBldrCtx->getBlock(), currStmtIdx); + return; +} + void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit, ExplodedNode *Pred) { const CXXCtorInitializer *BMI = CFGInit.getInitializer(); diff --git a/clang/unittests/StaticAnalyzer/CMakeLists.txt b/clang/unittests/StaticAnalyzer/CMakeLists.txt index ed16a1372bea2..943850e49b0b5 100644 --- a/clang/unittests/StaticAnalyzer/CMakeLists.txt +++ b/clang/unittests/StaticAnalyzer/CMakeLists.txt @@ -6,6 +6,7 @@ add_clang_unittest(StaticAnalysisTests BugReportInterestingnessTest.cpp CallDescriptionTest.cpp CallEventTest.cpp + CheckLifetimeEndTest.cpp ConflictingEvalCallsTest.cpp ExprEngineVisitTest.cpp FalsePositiveRefutationBRVisitorTest.cpp diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp new file mode 100644 index 0000000000000..9b3dd4bae7d9a --- /dev/null +++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp @@ -0,0 +1,93 @@ +//===- unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp --===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "CheckerRegistration.h" +#include "Reusables.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" +#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" +#include "llvm/Config/llvm-config.h" +#include "gtest/gtest.h" + +namespace clang { +namespace ento { +namespace { + +class LifetimeEndReporter : public Checker<check::LifetimeEnd> { + using Self = LifetimeEndReporter; + const BugType LifetimeEndNode{this, "LifetimeEndReporter"}; + + bool report(CheckerContext &C, StringRef Description) const { + ExplodedNode *Node = C.generateNonFatalErrorNode(C.getState()); + if (!Node) + return false; + + auto Report = std::make_unique<PathSensitiveBugReport>(LifetimeEndNode, + Description, Node); + C.emitReport(std::move(Report)); + return true; + } + +public: + void checkLifetimeEnd(const VarDecl *D, CheckerContext &C) const { + if (auto II = D->getIdentifier()) + report(C, (II->getName() + " LIFETIME END").str()); + } +}; + +void addLifetimeEndReporter(AnalysisASTConsumer &AnalysisConsumer, + AnalyzerOptions &AnOpts) { + AnOpts.CheckersAndPackages = { + {"test.LifetimeEndReporter", true}, + }; + AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) { + Registry.addChecker<LifetimeEndReporter>( + "test.LifetimeEndReporter", "EmptyDescription", "EmptyDocsUri"); + }); +} + +const std::vector<std::string> DisableLifetimeArgs{ + "-Xclang", "-analyzer-config", "-Xclang", "cfg-lifetime=false"}; +const std::vector<std::string> EnableLifetimeArgs{ + "-Xclang", "-analyzer-config", "-Xclang", "cfg-lifetime=true"}; + +TEST(CheckLifetimeEnd, CFGLifetimeEnabled) { + constexpr auto Code = R"( +void foo() { + int i = 0; +} + )"; + + std::string Diags; + EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( + Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n"); +} + +TEST(CheckLifetimeEnd, CFGLifetimeDisabled) { + constexpr auto Code = R"( +void foo() { + int i = 0; +} + )"; + + std::string Diags; + EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( + Code, DisableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); + EXPECT_TRUE(Diags.empty()); +} + +} // namespace +} // namespace ento +} // namespace clang >From c7f7b3cd8f25cdab81aeaf9dc676a5b091d0c11a Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 16:10:11 +0200 Subject: [PATCH 02/29] Handle the new enumeration value --- clang/lib/Analysis/ProgramPoint.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clang/lib/Analysis/ProgramPoint.cpp b/clang/lib/Analysis/ProgramPoint.cpp index 697e0afd30ed2..bca2581d923b6 100644 --- a/clang/lib/Analysis/ProgramPoint.cpp +++ b/clang/lib/Analysis/ProgramPoint.cpp @@ -95,6 +95,8 @@ StringRef ProgramPoint::getProgramPointKindName(Kind K) { return "PostImplicitCall"; case LoopExitKind: return "LoopExit"; + case LifetimeEndKind: + return "LifetimeEnd"; case EpsilonKind: return "Epsilon"; } @@ -158,6 +160,10 @@ std::optional<SourceLocation> ProgramPoint::getSourceLocation() const { if (const Stmt *S = castAs<LoopExit>().getLoopStmt()) return S->getBeginLoc(); return std::nullopt; + case LifetimeEndKind: + if (const Stmt *S = castAs<LifetimeEnd>().getTriggerStmt()) + return S->getBeginLoc(); + return std::nullopt; case EpsilonKind: return std::nullopt; } >From df5f26b566b64f26db01f861de8fbe5b5aa0dc96 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 16:17:34 +0200 Subject: [PATCH 03/29] [NFC] Remove unused trigger statement plumbing --- clang/include/clang/StaticAnalyzer/Core/CheckerManager.h | 3 +-- clang/lib/StaticAnalyzer/Core/CheckerManager.cpp | 8 +++----- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h index e1cd01ef13f2d..11de0ab86eb11 100644 --- a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h +++ b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h @@ -333,8 +333,7 @@ class CheckerManager { /// Run checkers for end of variable lifetime void runCheckersForLifetimeEnd(ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, - const VarDecl *Decl, const Stmt *TriggerStmt, - ExprEngine &Eng); + const VarDecl *Decl, ExprEngine &Eng); /// Run checkers for load/store of a location. void runCheckersForLocation(ExplodedNodeSet &Dst, diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp index 138d4bc8d95ad..8487c89ccffa8 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -319,12 +319,11 @@ struct CheckLifetimeEndContext { const CheckersTy &Checkers; const VarDecl *Decl; - const Stmt *TriggerStmt; ExprEngine &Eng; CheckLifetimeEndContext(const CheckersTy &checkers, const VarDecl *decl, - const Stmt *stmt, ExprEngine &eng) - : Checkers(checkers), Decl(decl), TriggerStmt(stmt), Eng(eng) {} + ExprEngine &eng) + : Checkers(checkers), Decl(decl), Eng(eng) {} CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } CheckersTy::const_iterator checkers_end() { return Checkers.end(); } @@ -344,9 +343,8 @@ struct CheckLifetimeEndContext { void CheckerManager::runCheckersForLifetimeEnd(ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, const VarDecl *Decl, - const Stmt *TriggerStmt, ExprEngine &Eng) { - CheckLifetimeEndContext C(LifetimeEndCheckers, Decl, TriggerStmt, Eng); + CheckLifetimeEndContext C(LifetimeEndCheckers, Decl, Eng); expandGraphWithCheckers(C, Dst, Src); } diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 21dff1961a76d..aa4bd6aec75b3 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1151,7 +1151,7 @@ void ExprEngine::ProcessLifetimeEnd(const Stmt *S, const VarDecl *D, Bldr.generateNode(PP, Pred->getState(), Pred); ExplodedNodeSet Dst; - getCheckerManager().runCheckersForLifetimeEnd(Dst, Src, D, S, *this); + getCheckerManager().runCheckersForLifetimeEnd(Dst, Src, D, *this); Engine.enqueueStmtNodes(Dst, currBldrCtx->getBlock(), currStmtIdx); return; } >From 5efa9eacafecc75c09b42141fae27a640e41de48 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 16:23:53 +0200 Subject: [PATCH 04/29] [NFC] Use more appropriate name for the internal private function proxy --- clang/include/clang/StaticAnalyzer/Core/Checker.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/Checker.h b/clang/include/clang/StaticAnalyzer/Core/Checker.h index 78772b12d4709..249d9c512f7a9 100644 --- a/clang/include/clang/StaticAnalyzer/Core/Checker.h +++ b/clang/include/clang/StaticAnalyzer/Core/Checker.h @@ -208,15 +208,16 @@ class Location { class LifetimeEnd { template <typename CHECKER> - static void _checkCall(void *checker, const VarDecl *D, CheckerContext &C) { + static void _checkLifetimeEnd(void *checker, const VarDecl *D, + CheckerContext &C) { ((const CHECKER *)checker)->checkLifetimeEnd(D, C); } public: template <typename CHECKER> static void _register(CHECKER *checker, CheckerManager &mgr) { - mgr._registerForLifetimeEnd( - CheckerManager::CheckLifetimeEndFunc(checker, _checkCall<CHECKER>)); + mgr._registerForLifetimeEnd(CheckerManager::CheckLifetimeEndFunc( + checker, _checkLifetimeEnd<CHECKER>)); } }; >From 6b188744ba8150277552cb7055ab902b402fee69 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 16:35:25 +0200 Subject: [PATCH 05/29] [NFC] Remove pointless return statement --- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index aa4bd6aec75b3..d6676969fbc52 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1153,7 +1153,6 @@ void ExprEngine::ProcessLifetimeEnd(const Stmt *S, const VarDecl *D, ExplodedNodeSet Dst; getCheckerManager().runCheckersForLifetimeEnd(Dst, Src, D, *this); Engine.enqueueStmtNodes(Dst, currBldrCtx->getBlock(), currStmtIdx); - return; } void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit, >From 368ae051cca220119e4895cdc582011b908bfbb3 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 16:41:10 +0200 Subject: [PATCH 06/29] [NFC] Avoid placing unit tests into clang::ento namespace --- clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp index 9b3dd4bae7d9a..cdd5ed9a3e801 100644 --- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp +++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp @@ -20,9 +20,9 @@ #include "llvm/Config/llvm-config.h" #include "gtest/gtest.h" -namespace clang { -namespace ento { namespace { +using namespace clang; +using namespace ento; class LifetimeEndReporter : public Checker<check::LifetimeEnd> { using Self = LifetimeEndReporter; @@ -89,5 +89,3 @@ void foo() { } } // namespace -} // namespace ento -} // namespace clang >From 7247c5979e1d5f6606aedd7acce1628792efa8c4 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 16:42:09 +0200 Subject: [PATCH 07/29] [NFC] Remove unused type alias --- clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp index cdd5ed9a3e801..8a683039a50c0 100644 --- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp +++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp @@ -25,7 +25,6 @@ using namespace clang; using namespace ento; class LifetimeEndReporter : public Checker<check::LifetimeEnd> { - using Self = LifetimeEndReporter; const BugType LifetimeEndNode{this, "LifetimeEndReporter"}; bool report(CheckerContext &C, StringRef Description) const { >From 46b6c0dc3d1bc730d7a31cdec922c54d8acbd54a Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 16:44:46 +0200 Subject: [PATCH 08/29] [NFC] Postpone Twine materialization to PSBR constructor call --- clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp index 8a683039a50c0..7e245a70de44c 100644 --- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp +++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp @@ -27,13 +27,13 @@ using namespace ento; class LifetimeEndReporter : public Checker<check::LifetimeEnd> { const BugType LifetimeEndNode{this, "LifetimeEndReporter"}; - bool report(CheckerContext &C, StringRef Description) const { + bool report(CheckerContext &C, Twine Description) const { ExplodedNode *Node = C.generateNonFatalErrorNode(C.getState()); if (!Node) return false; - auto Report = std::make_unique<PathSensitiveBugReport>(LifetimeEndNode, - Description, Node); + auto Report = std::make_unique<PathSensitiveBugReport>( + LifetimeEndNode, Description.str(), Node); C.emitReport(std::move(Report)); return true; } @@ -41,7 +41,7 @@ class LifetimeEndReporter : public Checker<check::LifetimeEnd> { public: void checkLifetimeEnd(const VarDecl *D, CheckerContext &C) const { if (auto II = D->getIdentifier()) - report(C, (II->getName() + " LIFETIME END").str()); + report(C, II->getName() + " LIFETIME END"); } }; >From 70718075ca287ac58ebd3610862935098d898ba0 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 16:45:54 +0200 Subject: [PATCH 09/29] Assert the identifier is not null in the test --- clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp index 7e245a70de44c..7f7849b189a9c 100644 --- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp +++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp @@ -40,8 +40,9 @@ class LifetimeEndReporter : public Checker<check::LifetimeEnd> { public: void checkLifetimeEnd(const VarDecl *D, CheckerContext &C) const { - if (auto II = D->getIdentifier()) - report(C, II->getName() + " LIFETIME END"); + auto II = D->getIdentifier(); + EXPECT_TRUE(II != nullptr); + report(C, II->getName() + " LIFETIME END"); } }; >From 45d68480656264bb38e7bfb17bf5d58cdc954743 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 16:49:11 +0200 Subject: [PATCH 10/29] [NFC] Adjust comment length --- clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp index 7f7849b189a9c..71fd90d9b2454 100644 --- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp +++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp @@ -1,4 +1,4 @@ -//===- unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp --===// +//===- unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp ------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. >From 949f37a49fa69335246b53dfcf4512e8eb047cbe Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 16:52:05 +0200 Subject: [PATCH 11/29] Update clang/include/clang/StaticAnalyzer/Core/CheckerManager.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gábor Horváth <[email protected]> --- clang/include/clang/StaticAnalyzer/Core/CheckerManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h index 11de0ab86eb11..3311774f069ea 100644 --- a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h +++ b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h @@ -330,7 +330,7 @@ class CheckerManager { const CallEvent &Call, ExprEngine &Eng, bool wasInlined = false); - /// Run checkers for end of variable lifetime + /// Run checkers for the end of a variable's lifetime. void runCheckersForLifetimeEnd(ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, const VarDecl *Decl, ExprEngine &Eng); >From 6c707671e4c1f8408e80ddb877e86dc975211444 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 16:52:25 +0200 Subject: [PATCH 12/29] Update clang/include/clang/Analysis/CFG.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gábor Horváth <[email protected]> --- clang/include/clang/Analysis/CFG.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index 57ebfa47ec90e..9b0a4d63cc49f 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -294,7 +294,7 @@ class CFGLoopExit : public CFGElement { }; /// Base class for representing elements related to the lifetime of automatic -/// objects +/// objects. class CFGScopeMarker : public CFGElement { public: LLVM_ATTRIBUTE_RETURNS_NONNULL const Stmt *getTriggerStmt() const { >From 4c4a8edb8aed75a54f3f431be2536ae990bb4748 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 16:52:41 +0200 Subject: [PATCH 13/29] Update clang/include/clang/Analysis/ProgramPoint.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gábor Horváth <[email protected]> --- clang/include/clang/Analysis/ProgramPoint.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/Analysis/ProgramPoint.h b/clang/include/clang/Analysis/ProgramPoint.h index 51ae43a334f1f..d93f3e356cb0f 100644 --- a/clang/include/clang/Analysis/ProgramPoint.h +++ b/clang/include/clang/Analysis/ProgramPoint.h @@ -738,7 +738,7 @@ class LifetimeEnd : public ProgramPoint { return static_cast<const Stmt *>(getData1()); } - /// Returns a variable declaration that uniquely identifies the scope + /// Returns a variable declaration that uniquely identifies the scope. LLVM_ATTRIBUTE_RETURNS_NONNULL const VarDecl *getDecl() const { return static_cast<const VarDecl *>(getData2()); } >From bed95c003474acd07a054f898f9c470676eb9de7 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 16:54:14 +0200 Subject: [PATCH 14/29] Add ftime-trace scope for LifetimeEnd callbacks --- clang/lib/StaticAnalyzer/Core/CheckerManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp index 8487c89ccffa8..80c03899d1e39 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -344,6 +344,7 @@ void CheckerManager::runCheckersForLifetimeEnd(ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, const VarDecl *Decl, ExprEngine &Eng) { + llvm::TimeTraceScope TimeScope("CheckerManager::runCheckersForLifetimeEnd"); CheckLifetimeEndContext C(LifetimeEndCheckers, Decl, Eng); expandGraphWithCheckers(C, Dst, Src); } >From 382d3ef779f9b5e920075c1fc4314a2e349d5057 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 16:58:02 +0200 Subject: [PATCH 15/29] Add a stack-trace loc to lifetime-end processing --- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index d6676969fbc52..32da5e097c76e 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1145,6 +1145,9 @@ void ExprEngine::ProcessLoopExit(const Stmt* S, ExplodedNode *Pred) { void ExprEngine::ProcessLifetimeEnd(const Stmt *S, const VarDecl *D, ExplodedNode *Pred) { + PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(), + S->getBeginLoc(), + "Error evaluating end of a lifetime"); ExplodedNodeSet Src; NodeBuilder Bldr(Pred, Src, *currBldrCtx); LifetimeEnd PP(S, D, Pred->getStackFrame()); >From 88f0590bd5260518aca0f519d67004afacb801a9 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 17:24:02 +0200 Subject: [PATCH 16/29] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <[email protected]> --- clang/include/clang/Analysis/ProgramPoint.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/Analysis/ProgramPoint.h b/clang/include/clang/Analysis/ProgramPoint.h index d93f3e356cb0f..85ebd2afadcf3 100644 --- a/clang/include/clang/Analysis/ProgramPoint.h +++ b/clang/include/clang/Analysis/ProgramPoint.h @@ -728,7 +728,7 @@ class LoopExit : public ProgramPoint { } }; -/// Represents a point when the lifetime ends of any automatic object. +/// Represents a point when the lifetime of an automatic object ends. class LifetimeEnd : public ProgramPoint { public: LifetimeEnd(const Stmt *S, const VarDecl *D, const StackFrame *SF) >From 6fff6eb9a18ce300481e66c8eb0988d47190c091 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 17:26:19 +0200 Subject: [PATCH 17/29] [NFC] More accurate doc comment --- clang/include/clang/Analysis/ProgramPoint.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/Analysis/ProgramPoint.h b/clang/include/clang/Analysis/ProgramPoint.h index 85ebd2afadcf3..3f33e5fef007e 100644 --- a/clang/include/clang/Analysis/ProgramPoint.h +++ b/clang/include/clang/Analysis/ProgramPoint.h @@ -738,7 +738,7 @@ class LifetimeEnd : public ProgramPoint { return static_cast<const Stmt *>(getData1()); } - /// Returns a variable declaration that uniquely identifies the scope. + /// Returns the variable declaration whose lifetime has ended. LLVM_ATTRIBUTE_RETURNS_NONNULL const VarDecl *getDecl() const { return static_cast<const VarDecl *>(getData2()); } >From 75fea4ed9194151534ac58628798936ca74651ac Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 17:36:14 +0200 Subject: [PATCH 18/29] Add a test case with non-trivial destructor --- .../StaticAnalyzer/CheckLifetimeEndTest.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp index 71fd90d9b2454..7c9f5d8153756 100644 --- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp +++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp @@ -88,4 +88,19 @@ void foo() { EXPECT_TRUE(Diags.empty()); } +TEST(CheckLifetimeEnd, NonTrivialDtor) { + constexpr auto Code = R"( + struct A { + ~A() {} + }; + void foo() { + A a; + } + )"; + std::string Diags; + EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( + Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: a LIFETIME END\n"); +} + } // namespace >From d89cc248b0b9e49710c5ce060dab4cd7363fdef5 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 17:39:12 +0200 Subject: [PATCH 19/29] Update clang/include/clang/Analysis/CFG.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Donát Nagy <[email protected]> --- clang/include/clang/Analysis/CFG.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index 9b0a4d63cc49f..0fd74979863d6 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -385,8 +385,8 @@ class CFGScopeBegin : public CFGScopeMarker { private: friend class CFGElement; - static bool isKind(const CFGElement &elem) { - return elem.getKind() == ScopeBegin; + static bool isKind(const CFGElement &Elem) { + return Elem.getKind() == ScopeBegin; } }; >From 2788acdd8cd80fc46f091b3ec769bec5e515056b Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Tue, 2 Jun 2026 17:40:10 +0200 Subject: [PATCH 20/29] [NFC] Capitalize params --- clang/include/clang/Analysis/CFG.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index 0fd74979863d6..2079f99046021 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -320,8 +320,8 @@ class CFGScopeMarker : public CFGElement { /// Represents the point where the lifetime of an automatic object ends class CFGLifetimeEnds : public CFGScopeMarker { public: - explicit CFGLifetimeEnds(const VarDecl *var, const Stmt *stmt) - : CFGScopeMarker(LifetimeEnds, stmt, var) {} + explicit CFGLifetimeEnds(const VarDecl *Var, const Stmt *Stmt) + : CFGScopeMarker(LifetimeEnds, Stmt, Var) {} const VarDecl *getVarDecl() const { return static_cast<const VarDecl *>(Data2.getPointer()); >From fa768712a2938f5b924d390628e6effbbc7cdebb Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Wed, 3 Jun 2026 09:37:49 +0200 Subject: [PATCH 21/29] Avoid potential UB risk in the test --- clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp index 7c9f5d8153756..02a7b34ebe756 100644 --- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp +++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp @@ -41,7 +41,7 @@ class LifetimeEndReporter : public Checker<check::LifetimeEnd> { public: void checkLifetimeEnd(const VarDecl *D, CheckerContext &C) const { auto II = D->getIdentifier(); - EXPECT_TRUE(II != nullptr); + ASSERT_TRUE(II != nullptr); report(C, II->getName() + " LIFETIME END"); } }; >From 730c3693b6931c87a25c4f92e79ae5a39d7cd12f Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Wed, 3 Jun 2026 09:41:31 +0200 Subject: [PATCH 22/29] Handle LifetimeEnd in getStmtForDiagnostics --- clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp index 5b978cc540059..454eff97511b1 100644 --- a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp @@ -337,6 +337,8 @@ const Stmt *ExplodedNode::getStmtForDiagnostics() const { return CEB->getReturnStmt(); if (auto FEP = P.getAs<FunctionExitPoint>()) return FEP->getStmt(); + if (auto LE = P.getAs<LifetimeEnd>()) + return LE->getTriggerStmt(); return nullptr; } >From e1acc2ec5b5be3a3b5e7cc7806361af5b6ca67ee Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Thu, 4 Jun 2026 14:38:36 +0200 Subject: [PATCH 23/29] Add tests for different declaration scopes --- .../StaticAnalyzer/CheckLifetimeEndTest.cpp | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp index 02a7b34ebe756..31f0dfba30933 100644 --- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp +++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp @@ -103,4 +103,84 @@ TEST(CheckLifetimeEnd, NonTrivialDtor) { EXPECT_EQ(Diags, "test.LifetimeEndReporter: a LIFETIME END\n"); } +TEST(CheckLifetimeEnd, MultipleVariablesAndNestedScopes) { + constexpr auto Code = R"( +void foo() { + int a = 0; + int b = 0; + { + int c = 0, d = 0; + } +} + )"; + + std::string Diags; + EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( + Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: a LIFETIME END\n" + "test.LifetimeEndReporter: b LIFETIME END\n" + "test.LifetimeEndReporter: c LIFETIME END\n" + "test.LifetimeEndReporter: d LIFETIME END\n"); +} + +TEST(CheckLifetimeEnd, LocalStaticVariable) { + constexpr auto Code = R"( +void foo() { + static int i = 0; + int j = 0; +} + )"; + + std::string Diags; + EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( + Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: j LIFETIME END\n"); +} + +TEST(CheckLifetimeEnd, GlobalVariable) { + constexpr auto Code = R"( +int g = 0; +void foo() { + int i = 0; +} + )"; + + std::string Diags; + EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( + Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n"); +} + +TEST(CheckLifetimeEnd, LoopBodyVariable) { + constexpr auto Code = R"( +void foo() { + while (true) { + int i = 0; + break; + } +} + )"; + + std::string Diags; + EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( + Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n"); +} + +TEST(CheckLifetimeEnd, ForLoopInductionVariable) { + constexpr auto Code = R"( +void foo() { + for (int i = 0; i < 1; i++) { + int j = 0; + } +} + )"; + + std::string Diags; + EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( + Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n" + "test.LifetimeEndReporter: j LIFETIME END\n"); +} + } // namespace >From d0dafb1eac48ec33f6df505a3b809f1ec3146e5f Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Thu, 4 Jun 2026 14:57:46 +0200 Subject: [PATCH 24/29] Add a test case for lifetime-extended temporary --- .../StaticAnalyzer/CheckLifetimeEndTest.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp index 31f0dfba30933..c59226828b77f 100644 --- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp +++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp @@ -183,4 +183,17 @@ void foo() { "test.LifetimeEndReporter: j LIFETIME END\n"); } +TEST(CheckLifetimeEnd, LifetimeExtendedTemporary) { + constexpr auto Code = R"( +void foo() { + const int& r = 42; +} + )"; + + std::string Diags; + EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( + Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: r LIFETIME END\n"); +} + } // namespace >From c8fbda4f0ae42438a19b5f211e1f0c4cdf1013e9 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Thu, 4 Jun 2026 15:41:17 +0200 Subject: [PATCH 25/29] Avoid printing diagnostics from unit tests --- clang/unittests/StaticAnalyzer/CheckerRegistration.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clang/unittests/StaticAnalyzer/CheckerRegistration.h b/clang/unittests/StaticAnalyzer/CheckerRegistration.h index c4c6e7a9a896d..75b04f5dabd69 100644 --- a/clang/unittests/StaticAnalyzer/CheckerRegistration.h +++ b/clang/unittests/StaticAnalyzer/CheckerRegistration.h @@ -8,6 +8,7 @@ #include "clang/Analysis/PathDiagnostic.h" #include "clang/Frontend/CompilerInstance.h" +#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -91,6 +92,9 @@ template <AddCheckerFn... Fns> class TestAction : public ASTFrontendAction { std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler, StringRef File) override { + // Suppress the default HTML/text path diagnostic consumers that would + // otherwise emit to stderr via DiagnosticsEngine::Report(). + Compiler.getAnalyzerOpts().AnalysisDiagOpt = PD_NONE; std::unique_ptr<AnalysisASTConsumer> AnalysisConsumer = CreateAnalysisConsumer(Compiler); if (OnlyEmitWarnings) >From 5abfdfac79e81e899301a2f448aec27c337f03bd Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Thu, 4 Jun 2026 17:38:39 +0200 Subject: [PATCH 26/29] [NFC] Pass Twine by const-ref --- clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp index c59226828b77f..644ada31b11a7 100644 --- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp +++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp @@ -27,7 +27,7 @@ using namespace ento; class LifetimeEndReporter : public Checker<check::LifetimeEnd> { const BugType LifetimeEndNode{this, "LifetimeEndReporter"}; - bool report(CheckerContext &C, Twine Description) const { + bool report(CheckerContext &C, const Twine &Description) const { ExplodedNode *Node = C.generateNonFatalErrorNode(C.getState()); if (!Node) return false; >From d15f73561f754285616c1fd52db891c3d684cc11 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Thu, 4 Jun 2026 17:38:53 +0200 Subject: [PATCH 27/29] [NFC] Use getDeclName().getAsString() in the test --- clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp index 644ada31b11a7..43e16dde17239 100644 --- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp +++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp @@ -40,9 +40,7 @@ class LifetimeEndReporter : public Checker<check::LifetimeEnd> { public: void checkLifetimeEnd(const VarDecl *D, CheckerContext &C) const { - auto II = D->getIdentifier(); - ASSERT_TRUE(II != nullptr); - report(C, II->getName() + " LIFETIME END"); + report(C, D->getDeclName().getAsString() + " LIFETIME END"); } }; >From afebf268c8bd27d26c21ec69c759cae98614973f Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Fri, 5 Jun 2026 16:00:24 +0200 Subject: [PATCH 28/29] Demonstrate how lifetime-end is reported for 2 loop iterations --- .../StaticAnalyzer/CheckLifetimeEndTest.cpp | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp index 43e16dde17239..f7b6bdce2744a 100644 --- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp +++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp @@ -20,15 +20,19 @@ #include "llvm/Config/llvm-config.h" #include "gtest/gtest.h" -namespace { using namespace clang; using namespace ento; +REGISTER_TRAIT_WITH_PROGRAMSTATE(TestLifetimeEndReportCountTrait, unsigned) + +namespace { + class LifetimeEndReporter : public Checker<check::LifetimeEnd> { const BugType LifetimeEndNode{this, "LifetimeEndReporter"}; - bool report(CheckerContext &C, const Twine &Description) const { - ExplodedNode *Node = C.generateNonFatalErrorNode(C.getState()); + bool report(CheckerContext &C, const Twine &Description, + ProgramStateRef State) const { + ExplodedNode *Node = C.generateNonFatalErrorNode(State); if (!Node) return false; @@ -40,7 +44,13 @@ class LifetimeEndReporter : public Checker<check::LifetimeEnd> { public: void checkLifetimeEnd(const VarDecl *D, CheckerContext &C) const { - report(C, D->getDeclName().getAsString() + " LIFETIME END"); + ProgramStateRef State = C.getState(); + // Intentionally add a unique number to each report to avoid deduplication. + unsigned Count = State->get<TestLifetimeEndReportCountTrait>(); + State = State->set<TestLifetimeEndReportCountTrait>(Count + 1); + auto Description = llvm::formatv("{0} LIFETIME END {1}", + D->getDeclName().getAsString(), Count); + report(C, Description, State); } }; @@ -70,7 +80,7 @@ void foo() { std::string Diags; EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); - EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n"); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END 0\n"); } TEST(CheckLifetimeEnd, CFGLifetimeDisabled) { @@ -98,7 +108,7 @@ TEST(CheckLifetimeEnd, NonTrivialDtor) { std::string Diags; EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); - EXPECT_EQ(Diags, "test.LifetimeEndReporter: a LIFETIME END\n"); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: a LIFETIME END 0\n"); } TEST(CheckLifetimeEnd, MultipleVariablesAndNestedScopes) { @@ -115,10 +125,10 @@ void foo() { std::string Diags; EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); - EXPECT_EQ(Diags, "test.LifetimeEndReporter: a LIFETIME END\n" - "test.LifetimeEndReporter: b LIFETIME END\n" - "test.LifetimeEndReporter: c LIFETIME END\n" - "test.LifetimeEndReporter: d LIFETIME END\n"); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: a LIFETIME END 3\n" + "test.LifetimeEndReporter: b LIFETIME END 2\n" + "test.LifetimeEndReporter: c LIFETIME END 1\n" + "test.LifetimeEndReporter: d LIFETIME END 0\n"); } TEST(CheckLifetimeEnd, LocalStaticVariable) { @@ -132,7 +142,7 @@ void foo() { std::string Diags; EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); - EXPECT_EQ(Diags, "test.LifetimeEndReporter: j LIFETIME END\n"); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: j LIFETIME END 0\n"); } TEST(CheckLifetimeEnd, GlobalVariable) { @@ -146,7 +156,7 @@ void foo() { std::string Diags; EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); - EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n"); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END 0\n"); } TEST(CheckLifetimeEnd, LoopBodyVariable) { @@ -162,14 +172,18 @@ void foo() { std::string Diags; EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); - EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n"); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END 0\n"); } TEST(CheckLifetimeEnd, ForLoopInductionVariable) { constexpr auto Code = R"( void foo() { - for (int i = 0; i < 1; i++) { + for (int i = 0; i < 2; i++) { int j = 0; + { + int nested = 0; + } + ++j; } } )"; @@ -177,8 +191,11 @@ void foo() { std::string Diags; EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); - EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n" - "test.LifetimeEndReporter: j LIFETIME END\n"); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END 4\n" + "test.LifetimeEndReporter: j LIFETIME END 1\n" + "test.LifetimeEndReporter: j LIFETIME END 3\n" + "test.LifetimeEndReporter: nested LIFETIME END 0\n" + "test.LifetimeEndReporter: nested LIFETIME END 2\n"); } TEST(CheckLifetimeEnd, LifetimeExtendedTemporary) { @@ -191,7 +208,7 @@ void foo() { std::string Diags; EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>( Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true)); - EXPECT_EQ(Diags, "test.LifetimeEndReporter: r LIFETIME END\n"); + EXPECT_EQ(Diags, "test.LifetimeEndReporter: r LIFETIME END 0\n"); } } // namespace >From 4c9f82faff47b1719ad1b6371d52e42f16de17ad Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <[email protected]> Date: Fri, 5 Jun 2026 17:44:27 +0200 Subject: [PATCH 29/29] Lit test for lifetime-end nodes in CFG --- .../lifetime-end-simple-cfg-output.cpp | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 clang/test/Analysis/lifetime-end-simple-cfg-output.cpp diff --git a/clang/test/Analysis/lifetime-end-simple-cfg-output.cpp b/clang/test/Analysis/lifetime-end-simple-cfg-output.cpp new file mode 100644 index 0000000000000..2033264952558 --- /dev/null +++ b/clang/test/Analysis/lifetime-end-simple-cfg-output.cpp @@ -0,0 +1,138 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -analyzer-config cfg-lifetime=true %s > %t 2>&1 +// RUN: FileCheck --input-file=%t %s + +// Tests for lifetime-end CFG nodes. + +void test_simple_variable() { + int i = 0; +} +// CHECK: void test_simple_variable() +// CHECK: [B2 (ENTRY)] +// CHECK-NEXT: Succs (1): B1 +// CHECK: [B1] +// CHECK-NEXT: 1: 0 +// CHECK-NEXT: 2: int i = 0; +// CHECK-NEXT: 3: [B1.2] (Lifetime ends) +// CHECK-NEXT: Preds (1): B2 +// CHECK-NEXT: Succs (1): B0 +// CHECK: [B0 (EXIT)] +// CHECK-NEXT: Preds (1): B1 + +struct A { + ~A() {} +}; +void test_nontrivial_dtor() { + A a; +} +// CHECK: void test_nontrivial_dtor() +// CHECK: [B2 (ENTRY)] +// CHECK-NEXT: Succs (1): B1 +// CHECK: [B1] +// CHECK-NEXT: 1: (CXXConstructExpr, [B1.2], A) +// CHECK-NEXT: 2: A a; +// CHECK-NEXT: 3: [B1.2].~A() (Implicit destructor) +// CHECK-NEXT: 4: [B1.2] (Lifetime ends) +// CHECK-NEXT: Preds (1): B2 +// CHECK-NEXT: Succs (1): B0 +// CHECK: [B0 (EXIT)] +// CHECK-NEXT: Preds (1): B1 + +void test_multiple_variables_nested_scopes() { + int a = 0; + int b = 0; + { + int c = 0, d = 0; + } +} +// CHECK: void test_multiple_variables_nested_scopes() +// CHECK: [B2 (ENTRY)] +// CHECK-NEXT: Succs (1): B1 +// CHECK: [B1] +// CHECK-NEXT: 1: 0 +// CHECK-NEXT: 2: int a = 0; +// CHECK-NEXT: 3: 0 +// CHECK-NEXT: 4: int b = 0; +// CHECK-NEXT: 5: 0 +// CHECK-NEXT: 6: int c = 0; +// CHECK-NEXT: 7: 0 +// CHECK-NEXT: 8: int d = 0; +// CHECK-NEXT: 9: [B1.8] (Lifetime ends) +// CHECK-NEXT: 10: [B1.6] (Lifetime ends) +// CHECK-NEXT: 11: [B1.4] (Lifetime ends) +// CHECK-NEXT: 12: [B1.2] (Lifetime ends) +// CHECK-NEXT: Preds (1): B2 +// CHECK-NEXT: Succs (1): B0 +// CHECK: [B0 (EXIT)] +// CHECK-NEXT: Preds (1): B1 + +void test_local_static() { + static int i = 0; + int j = 0; +} +// CHECK: void test_local_static() +// CHECK: [B4 (ENTRY)] +// CHECK-NEXT: Succs (1): B3 +// CHECK: [B1] +// CHECK-NEXT: 1: 0 +// CHECK-NEXT: 2: int j = 0; +// CHECK-NEXT: 3: [B1.2] (Lifetime ends) +// CHECK-NEXT: Preds (2): B2 B3 +// CHECK-NEXT: Succs (1): B0 +// CHECK: [B2] +// CHECK-NEXT: 1: 0 +// CHECK-NEXT: 2: static int i = 0; +// CHECK-NEXT: Preds (1): B3 +// CHECK-NEXT: Succs (1): B1 +// CHECK: [B3] +// CHECK-NEXT: T: static init i +// CHECK-NEXT: Preds (1): B4 +// CHECK-NEXT: Succs (2): B1 B2 +// CHECK: [B0 (EXIT)] +// CHECK-NEXT: Preds (1): B1 + +void test_loop_body() { + while (true) { + int i = 0; + break; + } +} +// CHECK: void test_loop_body() +// CHECK: [B5 (ENTRY)] +// CHECK-NEXT: Succs (1): B4 +// CHECK: [B1] +// CHECK-NEXT: Preds (1): B2 +// CHECK-NEXT: Succs (1): B4 +// CHECK: [B2] +// CHECK-NEXT: 1: [B3.2] (Lifetime ends) +// CHECK-NEXT: Succs (1): B1 +// CHECK: [B3] +// CHECK-NEXT: 1: 0 +// CHECK-NEXT: 2: int i = 0; +// CHECK-NEXT: 3: [B3.2] (Lifetime ends) +// CHECK-NEXT: T: break; +// CHECK-NEXT: Preds (1): B4 +// CHECK-NEXT: Succs (1): B0 +// CHECK: [B4] +// CHECK-NEXT: 1: true +// CHECK-NEXT: T: while [B4.1] +// CHECK-NEXT: Preds (2): B1 B5 +// CHECK-NEXT: Succs (2): B3 NULL +// CHECK: [B0 (EXIT)] +// CHECK-NEXT: Preds (1): B3 + +void test_lifetime_extended_temporary() { + const int &r = 42; +} +// CHECK: void test_lifetime_extended_temporary() +// CHECK: [B2 (ENTRY)] +// CHECK-NEXT: Succs (1): B1 +// CHECK: [B1] +// CHECK-NEXT: 1: 42 +// CHECK-NEXT: 2: [B1.1] (ImplicitCastExpr, NoOp, const int) +// CHECK-NEXT: 3: [B1.2] +// CHECK-NEXT: 4: const int &r = 42; +// CHECK-NEXT: 5: [B1.4] (Lifetime ends) +// CHECK-NEXT: Preds (1): B2 +// CHECK-NEXT: Succs (1): B0 +// CHECK: [B0 (EXIT)] +// CHECK-NEXT: Preds (1): B1 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
