https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/200145
>From 826dbd7525158bf0946b41ec0f629c235b8238b2 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Thu, 28 May 2026 11:37:49 +0200 Subject: [PATCH 01/10] Started implementing the lifetime annotation checker. --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 + .../Checkers/LifetimeAnnotations.cpp | 73 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index eca2afbe340a9..85d59fc139728 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -788,6 +788,10 @@ def SmartPtrChecker: Checker<"SmartPtr">, Dependencies<[SmartPtrModeling]>, Documentation<HasDocumentation>; +def LifetimeAnnotations : Checker<"LifetimeAnnotations">, + HelpText<"Check for lifetime violations using lifetime annotations">, + Documentation<NotDocumented>; + } // end: "alpha.cplusplus" //===----------------------------------------------------------------------===// diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 8a0621077b977..3f426186189fa 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -55,6 +55,7 @@ add_clang_library(clangStaticAnalyzerCheckers IteratorModeling.cpp IteratorRangeChecker.cpp IvarInvalidationChecker.cpp + LifetimeAnnotations.cpp LLVMConventionsChecker.cpp LocalizationChecker.cpp MacOSKeychainAPIChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp new file mode 100644 index 0000000000000..54e98b945c7b3 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp @@ -0,0 +1,73 @@ +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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 <AllocationState.h> + +using namespace clang; +using namespace ento; + +REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, const MemRegion *, + const MemRegion *); + +class LifetimeAnnotations : public Checker<check::PostCall> { +public: + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, + const char *Sep) const override; +}; + +void LifetimeAnnotations::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + const auto *MethodDecl = dyn_cast_if_present<CXXMethodDecl>(Call.getDecl()); + + if (!MethodDecl) + return; + + unsigned LBParamIdx = MethodDecl->getNumParams(); + for (unsigned i = 0; i < MethodDecl->getNumParams(); i++) { + if (MethodDecl->getParamDecl(i)->hasAttr<LifetimeBoundAttr>()) { + LBParamIdx = i; + break; + } + } + if (LBParamIdx == MethodDecl->getNumParams()) + return; + + SVal RetVal = Call.getReturnValue(); + const MemRegion *RetValRegion = RetVal.getAsRegion(); + if (!RetValRegion) + return; + + SVal ArgVal = Call.getArgSVal(LBParamIdx); + const MemRegion *ArgValRegion = ArgVal.getAsRegion(); + if (!ArgValRegion) + return; + + State = State->set<LifetimeBoundMap>(RetValRegion, ArgValRegion); + C.addTransition(State); +} + +void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const { + auto LBTy = State->get<LifetimeBoundMap>(); + + if (!LBTy.isEmpty()) { + Out << Sep << "LifetimeBound objects: "; + + for (auto I : LBTy) { + Out << I.first << " bound to " << I.second << NL; + } + } +} + +void ento::registerLifetimeAnnotations(CheckerManager &mgr) { + mgr.registerChecker<LifetimeAnnotations>(); +} + +bool ento::shouldRegisterLifetimeAnnotations(const CheckerManager &mgr) { + return true; +} >From fa2b6c5e8c9de65307f9dfec47019530b005465c Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Sun, 31 May 2026 00:40:48 +0200 Subject: [PATCH 02/10] Addressed mentors feedback. --- .../Checkers/LifetimeAnnotations.cpp | 68 ++++++++++++------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp index 54e98b945c7b3..4052e13859041 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp @@ -3,12 +3,15 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include <AllocationState.h> +#include "AllocationState.h" +#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" + using namespace clang; using namespace ento; +using namespace clang::lifetimes; -REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, const MemRegion *, +REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SymbolRef, const MemRegion *); class LifetimeAnnotations : public Checker<check::PostCall> { @@ -22,45 +25,62 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); - const auto *MethodDecl = dyn_cast_if_present<CXXMethodDecl>(Call.getDecl()); + const auto *FC = dyn_cast_if_present<AnyFunctionCall>(&Call); + if (!FC) + return; - if (!MethodDecl) + const FunctionDecl *FD = FC->getDecl(); + if (!FD) return; - unsigned LBParamIdx = MethodDecl->getNumParams(); - for (unsigned i = 0; i < MethodDecl->getNumParams(); i++) { - if (MethodDecl->getParamDecl(i)->hasAttr<LifetimeBoundAttr>()) { - LBParamIdx = i; + unsigned LBParamIdx = FD->getNumParams(); + // FIXME: Use range based for loop instead. Currently that would require + // to also change how we create ArgVal which would need a new logic to + // be implemented. + for (unsigned I = 0, E = FD->getNumParams(); I != E; I++) { + if (FD->getParamDecl(I)->hasAttr<LifetimeBoundAttr>()) { + LBParamIdx = I; + // FIXME: If multiple parameters are annotated this logic would + // prevent the analyzer to read after the first parameter. break; } } - if (LBParamIdx == MethodDecl->getNumParams()) - return; - SVal RetVal = Call.getReturnValue(); - const MemRegion *RetValRegion = RetVal.getAsRegion(); - if (!RetValRegion) - return; - SVal ArgVal = Call.getArgSVal(LBParamIdx); - const MemRegion *ArgValRegion = ArgVal.getAsRegion(); - if (!ArgValRegion) + SymbolRef RetValSym = RetVal.getAsSymbol(/*IncludeBaseRegions=*/true); + if(!RetValSym) return; - State = State->set<LifetimeBoundMap>(RetValRegion, ArgValRegion); + if (LBParamIdx != FD->getNumParams()) { + SVal ArgVal = Call.getArgSVal(LBParamIdx); + const MemRegion *ArgValRegion = ArgVal.getAsRegion(); + // FIXME: if(!ArgValRegion) should be also handled since in those cases + // the argument has no region, but still needs to be tracked. + if (ArgValRegion) + State = State->set<LifetimeBoundMap>(RetValSym, ArgValRegion); + } + + if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) { + if (implicitObjectParamIsLifetimeBound(FD)) { + const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion(); + + if (AttrRegion) + State = State->set<LifetimeBoundMap>(RetValSym, AttrRegion); + } + } C.addTransition(State); } void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const { - auto LBTy = State->get<LifetimeBoundMap>(); + auto LBVal = State->get<LifetimeBoundMap>(); - if (!LBTy.isEmpty()) { - Out << Sep << "LifetimeBound objects: "; + if (LBVal.isEmpty()) + return; - for (auto I : LBTy) { - Out << I.first << " bound to " << I.second << NL; - } + Out << Sep << "LifetimeBound bindings:" << NL; + for (auto&& [RetValSym, ArgValRegion] : LBVal) { + Out << " Origin " << RetValSym << " contains Loan " << ArgValRegion << NL; } } >From 9ff637735dc8242b6f7735dfd2773649f4602bc6 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Fri, 5 Jun 2026 13:33:50 +0200 Subject: [PATCH 03/10] Save the current work that has the test cases and the helper function implemented. --- ...-AST-matching-to-get-containers-regi.patch | 130 ++++++++++++++++++ .../Checkers/LifetimeAnnotations.cpp | 108 +++++++++++++-- clang/test/Analysis/lifetime-bound.cpp | 111 +++++++++++++++ 3 files changed, 335 insertions(+), 14 deletions(-) create mode 100644 0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch create mode 100644 clang/test/Analysis/lifetime-bound.cpp diff --git a/0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch b/0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch new file mode 100644 index 0000000000000..450bdda9f70dc --- /dev/null +++ b/0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch @@ -0,0 +1,130 @@ +From 00c5cf9e103a88ad9af285104f878c5f932ad8ff Mon Sep 17 00:00:00 2001 +From: benedekaibas <[email protected]> +Date: Tue, 2 Jun 2026 13:13:46 +0200 +Subject: [PATCH] [analyzer] Removed AST matching to get containers' region and + fixed small issues. + +--- + .../StaticAnalyzer/Checkers/MoveChecker.cpp | 40 +++++++++++-------- + .../test/Analysis/use-after-move-iterator.cpp | 24 +++++------ + 2 files changed, 36 insertions(+), 28 deletions(-) + +diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp +index 47e9c585054a..9ff2e90b618a 100644 +--- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp ++++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp +@@ -518,31 +518,29 @@ bool MoveChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { + if (!StdMoveCall.matches(Call)) + return false; + +- const auto *BeginCall = +- dyn_cast<CXXMemberCallExpr>(CE->getArg(0)->IgnoreImpCasts()); +- if (!BeginCall) ++ const auto *POS = getIteratorPosition(State, Call.getArgSVal(0)); ++ if (!POS) + return false; + +- const Expr *ContainerExpr = BeginCall->getImplicitObjectArgument(); +- const auto *DRE = dyn_cast<DeclRefExpr>(ContainerExpr->IgnoreImpCasts()); +- if (!DRE) ++ const MemRegion *ContainerRegion = POS->getContainer(); ++ if (!ContainerRegion) + return false; + +- const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()); +- if (!VD) ++ const auto *TypedRegion = ++ dyn_cast_if_present<TypedValueRegion>(ContainerRegion); ++ if (!TypedRegion) + return false; + +- const MemRegion *Region = +- State->getLValue(VD, C.getLocationContext()).getAsRegion(); +- if (!Region) +- return false; ++ QualType ObjTy = TypedRegion->getValueType(); + +- const CXXRecordDecl *RD = ContainerExpr->getType()->getAsCXXRecordDecl(); ++ const auto *RD = ObjTy->getAsCXXRecordDecl(); + if (!RD) + return false; + +- ObjectKind OK = classifyObject(State, Region, RD); ++ ObjectKind OK = classifyObject(State, ContainerRegion, RD); + ++ // FIXME: Also apply getIteratorPosition from IteratorModeling to recover the ++ // destination region instead of doing AST pattern matching. + const auto *BackInsCall = dyn_cast<CallExpr>(CE->getArg(2)->IgnoreImpCasts()); + if (!BackInsCall) + return false; +@@ -573,7 +571,8 @@ bool MoveChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { + /*CausesPointerEscape=*/false); + + if (shouldBeTracked(OK)) +- State = State->set<TrackedContentsMap>(Region, RegionState::getMoved()); ++ State = State->set<TrackedContentsMap>(ContainerRegion, ++ RegionState::getMoved()); + + C.addTransition(State); + return true; +@@ -736,10 +735,19 @@ void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { + + if (const auto *POS = getIteratorPosition(State, Val)) { + const MemRegion *ContainerRegion = POS->getContainer(); ++ if (!ContainerRegion) ++ return; ++ ++ const auto *TypedRegion = ++ dyn_cast_if_present<TypedValueRegion>(ContainerRegion); ++ if (!TypedRegion) ++ return; + +- const auto *TypedRegion = cast<TypedValueRegion>(ContainerRegion); + QualType ObjTy = TypedRegion->getValueType(); + const auto *R = ObjTy->getAsCXXRecordDecl(); ++ if (!R) ++ return; ++ + if (State->get<TrackedContentsMap>(ContainerRegion)) { + ExplodedNode *N = tryToReportBug(ContainerRegion, R, C, MK_FunCall); + if (!N || N->isSink()) +diff --git a/clang/test/Analysis/use-after-move-iterator.cpp b/clang/test/Analysis/use-after-move-iterator.cpp +index 50dd7e57b42e..2357be3a6bb3 100644 +--- a/clang/test/Analysis/use-after-move-iterator.cpp ++++ b/clang/test/Analysis/use-after-move-iterator.cpp +@@ -10,20 +10,20 @@ + // IteratorModeling is enabled. + //===----------------------------------------------------------------------===// + +-void iteratorDerefSource() { ++std::string iteratorDeref(int rng) { + std::list<std::string> l1; + l1.push_back("l1"); + std::list<std::string> l2; + +- std::move(l1.begin(), l1.end(), std::back_inserter(l2)); +- *l1.cbegin(); // expected-warning {{Method called on moved-from object 'l1'}} +-} +- +-void iteratorDerefDest() { +- std::list<std::string> l1; +- l1.push_back("l1"); +- std::list<std::string> l2; +- +- std::move(l1.begin(), l1.end(), std::back_inserter(l2)); +- *l2.cbegin(); // no-warning ++ switch (rng) { ++ case 10: { ++ std::move(l1.begin(), l1.end(), std::back_inserter(l2)); ++ return *l1.cbegin(); // expected-warning {{Method called on moved-from object 'l1'}} ++ } ++ case 20: { ++ std::move(l1.begin(), l1.end(), std::back_inserter(l2)); ++ return *l2.cbegin(); // no-warning: only l1 was invalidated and not l2! ++ } ++ } ++ return 0; + } +-- +2.43.0 + diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp index 4052e13859041..1f93d25d7a788 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp @@ -3,6 +3,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/Support/raw_ostream.h" #include "AllocationState.h" #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" @@ -13,16 +14,30 @@ using namespace clang::lifetimes; REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SymbolRef, const MemRegion *); +REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMapVal, const MemRegion *, const MemRegion *); -class LifetimeAnnotations : public Checker<check::PostCall> { + +class LifetimeAnnotations : public Checker<check::PostCall, eval::Call> { public: void checkPostCall(const CallEvent &Call, CheckerContext &C) const; void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const override; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; + void analyzerLifetimeBound(const CallEvent &Call, const CallExpr *, CheckerContext &C) const; + + const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"}; +}; + +typedef void (LifetimeAnnotations::*FnCheck)(const CallEvent &Call, const CallExpr *, + CheckerContext &) const; +CallDescriptionMap<FnCheck> Callbacks = { + {{CDM::SimpleFunc, {"clang_analyzer_lifetime_bound"}}, + &LifetimeAnnotations::analyzerLifetimeBound}, }; void LifetimeAnnotations::checkPostCall(const CallEvent &Call, CheckerContext &C) const { + llvm::errs() << "checkPostCall fired" << "\n"; ProgramStateRef State = C.getState(); const auto *FC = dyn_cast_if_present<AnyFunctionCall>(&Call); @@ -33,6 +48,8 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call, if (!FD) return; + SVal RetVal = Call.getReturnValue(); + SymbolRef RetValSym = RetVal.getAsSymbol(/*IncludeBaseRegions=*/true); unsigned LBParamIdx = FD->getNumParams(); // FIXME: Use range based for loop instead. Currently that would require // to also change how we create ArgVal which would need a new logic to @@ -45,27 +62,34 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call, break; } } - SVal RetVal = Call.getReturnValue(); - - SymbolRef RetValSym = RetVal.getAsSymbol(/*IncludeBaseRegions=*/true); - if(!RetValSym) - return; if (LBParamIdx != FD->getNumParams()) { SVal ArgVal = Call.getArgSVal(LBParamIdx); - const MemRegion *ArgValRegion = ArgVal.getAsRegion(); - // FIXME: if(!ArgValRegion) should be also handled since in those cases - // the argument has no region, but still needs to be tracked. - if (ArgValRegion) + if (const MemRegion *ArgValRegion = ArgVal.getAsRegion()) { + if (RetValSym) + llvm::errs() << "RetValSym: "; + RetValSym->dump(); + llvm::errs() << "\n"; State = State->set<LifetimeBoundMap>(RetValSym, ArgValRegion); + llvm::errs() << "State got set with RetValSym" << "\n"; + C.getState()->dump(); + llvm::errs() << "\n"; + if (const MemRegion *RetValRegion = RetVal.getAsRegion()) + State = State->set<LifetimeBoundMapVal>(RetValRegion, ArgValRegion); + } } if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) { + llvm::errs() << "isCXXThisVal true" << "\n"; if (implicitObjectParamIsLifetimeBound(FD)) { - const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion(); - - if (AttrRegion) + llvm::errs() << "isLifetimeBound true" << "\n"; + if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) { + llvm::errs() << "is AttrRegion non null" << "\n"; + if (RetValSym) State = State->set<LifetimeBoundMap>(RetValSym, AttrRegion); + if (const MemRegion *RetValRegion = RetVal.getAsRegion()) + State = State->set<LifetimeBoundMapVal>(RetValRegion, AttrRegion); + } } } C.addTransition(State); @@ -74,14 +98,70 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call, void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const { auto LBVal = State->get<LifetimeBoundMap>(); + auto LBValTwo = State->get<LifetimeBoundMapVal>(); - if (LBVal.isEmpty()) + if (LBVal.isEmpty() && LBValTwo.isEmpty()) return; Out << Sep << "LifetimeBound bindings:" << NL; for (auto&& [RetValSym, ArgValRegion] : LBVal) { Out << " Origin " << RetValSym << " contains Loan " << ArgValRegion << NL; } + for (auto&& [RetVal, ArgValRegion]: LBValTwo) { + Out << " Origin " << RetVal << " contains Loan " << ArgValRegion << NL; + } +} + +bool LifetimeAnnotations::evalCall(const CallEvent &Call, CheckerContext &C) const { + + const auto *CE = llvm::dyn_cast_if_present<CallExpr>(Call.getOriginExpr()); + if (!CE) + return false; + + const FnCheck *Handler = Callbacks.lookup(Call); + if (!Handler) + return false; + + (this->*(*Handler))(Call, CE, C); + return true; + C.addTransition(C.getState()); +} + +void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, const CallExpr *CE, CheckerContext &C) const { + llvm::errs() << "\n"; + llvm::errs() << "lifetime_bound called" << "\n"; + ProgramStateRef State = C.getState(); + unsigned int ArgExpr = CE->getNumArgs(); + if (ArgExpr != 1) + return; + + SVal ArgSVal = Call.getArgSVal(0); + + const MemRegion *ArgValRegion = ArgSVal.getAsRegion(); + SymbolRef ArgSValSym = ArgSVal.getAsSymbol(/*IncludeBaseRegions=*/true); + + llvm::SmallString<128> Str; + llvm::raw_svector_ostream OS(Str); + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return; + + if (ArgSValSym) { + if (const auto *ArgValLookFor = State->get<LifetimeBoundMap>(ArgSValSym)) { + OS << " Origin " << ArgSValSym << " contains loan " << *ArgValLookFor; + auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N); + C.emitReport(std::move(BR)); + Str.clear(); + } + } + if (ArgValRegion) { + if (const auto *AttrValLookFor = State->get<LifetimeBoundMapVal>(ArgValRegion)) { + OS << " Origin " << ArgValRegion << " bound to " << *AttrValLookFor; + auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N); + C.emitReport(std::move(BR)); + Str.clear(); + } + } } void ento::registerLifetimeAnnotations(CheckerManager &mgr) { diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp new file mode 100644 index 0000000000000..1cedfe5b0398f --- /dev/null +++ b/clang/test/Analysis/lifetime-bound.cpp @@ -0,0 +1,111 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.cplusplus.LifetimeAnnotations \ +// RUN: -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.cplusplus.LifetimeAnnotations \ +// RUN: -analyzer-config c++-container-inlining=false -verify %s + +void clang_analyzer_dump(...); + +// These are the cases when the result of function calls are MemRegions. + +struct A {}; + +// Ref type parameter annotated case +struct X { + int& choose(int& a [[clang::lifetimebound]]) { return a; } +}; + +void clang_analyzer_lifetime_bound(int&); + +void caller() { + int v = 0; + X obj; + int& r = obj.choose(v); + clang_analyzer_lifetime_bound(r); // expected-warning {{Origin v bound to v}} + clang_analyzer_dump(r); +} + +// Obj ref type function return annotated case +struct Y { + A a; + A& getA() [[clang::lifetimebound]] { return a; } +}; + +void clang_analyzer_lifetime_bound(A& a); + +void caller_two() { + // Return statement is annotated case. + Y y; + A& f = y.getA(); + clang_analyzer_lifetime_bound(f); // expected-warning {{Origin y.a bound to y}} + clang_analyzer_dump(f); +} + +// Obj ptr type function return annotated case +struct Z { + A a; + A* getA() [[clang::lifetimebound]] { return &a; } +}; + +void clang_analyzer_lifetime_bound(A* a); + +void caller_three() { + Z z; + A* func = z.getA(); + clang_analyzer_lifetime_bound(func); // expected-warning {{Origin z.a bound to z}} + clang_analyzer_dump(func); +} + +// Free function with annotated param and ref return +int& foo(int& num [[clang::lifetimebound]]) { return num; } + +void clang_analyzer_lifetime_bound(int&); + +void caller_four() { + int num = 5; + int& s = foo(num); + clang_analyzer_lifetime_bound(s); // expected-warning {{Origin num bound to num}} + clang_analyzer_dump(s); +} + +// Free function with annotated param and ptr return +int* boo(int* num [[clang::lifetimebound]]) { return num; } + +void clang_analyzer_lifetime_bound(int*); + +void caller_five() { + int n = 55; + int* n_ptr = &n; + int* s = boo(n_ptr); + + clang_analyzer_lifetime_bound(s); // expected-warning {{Origin n bound to n}} + clang_analyzer_dump(s); +} + +// These are the cases when the result of function calls are SymbolRefs. + +// Function returns ptr and has an annotated parameter +int* foo(int* n [[clang::lifetimebound]]); + +void clang_analyzer_lifetime_bound(int*); + +void caller_six() { + int y = 15; + int* y_ptr = &y; + auto bind = foo(y_ptr); + + clang_analyzer_lifetime_bound(bind); + // expected-warning@-1 {{Origin bound to n}} + // expected-warning@-1 {{Origin contains loan n}} + clang_analyzer_dump(bind); + +// FIXME: The full warning does look like this: +// Origin SymRegion{conj_$5{int *, LC1, S847, #1}} bound to n +// Origin conj_$5{int *, LC1, S847, #1} contains loan n +// Since the conj sym number and the ID can change across runs I have decided to just include +// string parts of the error message since that is the only consistent part of the emitted report. +// This does not apply to the test cases above this test case. +} + + +// Function returns a reference and has an annotated parameter + >From 2de8ddc32b07d2217bdeac16fd1c578b02ae5e78 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Fri, 5 Jun 2026 16:09:28 +0200 Subject: [PATCH 04/10] Save the current work that has the test cases and the helper function implemented + more test cases. --- .../Checkers/LifetimeAnnotations.cpp | 9 ++++++++- clang/test/Analysis/lifetime-bound.cpp | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp index 1f93d25d7a788..787698671ae75 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp @@ -145,7 +145,7 @@ void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, const Cal ExplodedNode *N = C.generateNonFatalErrorNode(); if (!N) return; - + llvm::errs() << "ArgSValSym: " << (ArgSValSym ? "non-null" : "null") << "\n"; if (ArgSValSym) { if (const auto *ArgValLookFor = State->get<LifetimeBoundMap>(ArgSValSym)) { OS << " Origin " << ArgSValSym << " contains loan " << *ArgValLookFor; @@ -154,7 +154,13 @@ void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, const Cal Str.clear(); } } + + llvm::errs() << "ArgValRegion: " << (ArgValRegion ? "non-null" : "null") << "\n"; if (ArgValRegion) { + llvm::errs() << "\n"; + llvm::errs() << "ArgValRegion: "; + ArgValRegion->dump(); + llvm::errs() << "\n"; if (const auto *AttrValLookFor = State->get<LifetimeBoundMapVal>(ArgValRegion)) { OS << " Origin " << ArgValRegion << " bound to " << *AttrValLookFor; auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N); @@ -164,6 +170,7 @@ void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, const Cal } } + void ento::registerLifetimeAnnotations(CheckerManager &mgr) { mgr.registerChecker<LifetimeAnnotations>(); } diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp index 1cedfe5b0398f..8cca08aca3f1c 100644 --- a/clang/test/Analysis/lifetime-bound.cpp +++ b/clang/test/Analysis/lifetime-bound.cpp @@ -108,4 +108,20 @@ void caller_six() { // Function returns a reference and has an annotated parameter +int& func(int& some_number [[clang::lifetimebound]]); + +void clang_analyzer_lifetime_bound(int&); + +void caller_seven() { + int f = 15; + auto& bind = func(f); + + clang_analyzer_lifetime_bound(bind); + // expected-warning@-1 {{Origin bound to some_number}} + // expected-warning@-1 {{Origin contains loan some_number}} + clang_analyzer_dump(bind); + +// The FIXME about the full warning applies to this text case as well. +} + >From c8d266749a90750db1e9585034836fad8150c27b Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Fri, 5 Jun 2026 16:37:42 +0200 Subject: [PATCH 05/10] Removed debugged comments. --- .../Checkers/LifetimeAnnotations.cpp | 20 ++-------------- clang/test/Analysis/lifetime-bound.cpp | 24 +++++++++++++++---- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp index 787698671ae75..8baf4b0ba223b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp @@ -67,24 +67,15 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call, SVal ArgVal = Call.getArgSVal(LBParamIdx); if (const MemRegion *ArgValRegion = ArgVal.getAsRegion()) { if (RetValSym) - llvm::errs() << "RetValSym: "; - RetValSym->dump(); - llvm::errs() << "\n"; State = State->set<LifetimeBoundMap>(RetValSym, ArgValRegion); - llvm::errs() << "State got set with RetValSym" << "\n"; - C.getState()->dump(); - llvm::errs() << "\n"; if (const MemRegion *RetValRegion = RetVal.getAsRegion()) State = State->set<LifetimeBoundMapVal>(RetValRegion, ArgValRegion); } } if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) { - llvm::errs() << "isCXXThisVal true" << "\n"; if (implicitObjectParamIsLifetimeBound(FD)) { - llvm::errs() << "isLifetimeBound true" << "\n"; if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) { - llvm::errs() << "is AttrRegion non null" << "\n"; if (RetValSym) State = State->set<LifetimeBoundMap>(RetValSym, AttrRegion); if (const MemRegion *RetValRegion = RetVal.getAsRegion()) @@ -128,8 +119,7 @@ bool LifetimeAnnotations::evalCall(const CallEvent &Call, CheckerContext &C) con } void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, const CallExpr *CE, CheckerContext &C) const { - llvm::errs() << "\n"; - llvm::errs() << "lifetime_bound called" << "\n"; + ProgramStateRef State = C.getState(); unsigned int ArgExpr = CE->getNumArgs(); if (ArgExpr != 1) @@ -145,7 +135,7 @@ void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, const Cal ExplodedNode *N = C.generateNonFatalErrorNode(); if (!N) return; - llvm::errs() << "ArgSValSym: " << (ArgSValSym ? "non-null" : "null") << "\n"; + if (ArgSValSym) { if (const auto *ArgValLookFor = State->get<LifetimeBoundMap>(ArgSValSym)) { OS << " Origin " << ArgSValSym << " contains loan " << *ArgValLookFor; @@ -155,12 +145,7 @@ void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, const Cal } } - llvm::errs() << "ArgValRegion: " << (ArgValRegion ? "non-null" : "null") << "\n"; if (ArgValRegion) { - llvm::errs() << "\n"; - llvm::errs() << "ArgValRegion: "; - ArgValRegion->dump(); - llvm::errs() << "\n"; if (const auto *AttrValLookFor = State->get<LifetimeBoundMapVal>(ArgValRegion)) { OS << " Origin " << ArgValRegion << " bound to " << *AttrValLookFor; auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N); @@ -170,7 +155,6 @@ void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, const Cal } } - void ento::registerLifetimeAnnotations(CheckerManager &mgr) { mgr.registerChecker<LifetimeAnnotations>(); } diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp index 8cca08aca3f1c..2acc1734644f2 100644 --- a/clang/test/Analysis/lifetime-bound.cpp +++ b/clang/test/Analysis/lifetime-bound.cpp @@ -81,6 +81,8 @@ void caller_five() { clang_analyzer_dump(s); } + + // These are the cases when the result of function calls are SymbolRefs. // Function returns ptr and has an annotated parameter @@ -91,11 +93,11 @@ void clang_analyzer_lifetime_bound(int*); void caller_six() { int y = 15; int* y_ptr = &y; - auto bind = foo(y_ptr); + auto* bind = foo(y_ptr); clang_analyzer_lifetime_bound(bind); - // expected-warning@-1 {{Origin bound to n}} - // expected-warning@-1 {{Origin contains loan n}} + // expected-warning@-1 {{Origin bound to y}} + // expected-warning@-1 {{Origin contains loan y}} clang_analyzer_dump(bind); // FIXME: The full warning does look like this: @@ -118,10 +120,24 @@ void caller_seven() { clang_analyzer_lifetime_bound(bind); // expected-warning@-1 {{Origin bound to some_number}} - // expected-warning@-1 {{Origin contains loan some_number}} + // expected-warning@-1 {{Origin contains loan some_number}} clang_analyzer_dump(bind); // The FIXME about the full warning applies to this text case as well. } +// Function returns a reference and has two annotated parameters. +int& f(int& a [[clang::lifetimebound]], int& b [[clang::lifetimebound]]); + +void clang_analyzer_lifetime_bound(int&); + +void caller_eight() { + int first_num = 1; + int second_num = 2; + auto numbers = f(first_num, second_num); + clang_analyzer_lifetime_bound(numbers); + // expected-warning@-1 {{Origin bound to first_num}} + // expected-warning@-1 {{Origin contains loan first_num}} + clang_analyzer_dump(numbers); +} >From 5b83c6cdfb7f0c395f286d2ce45e2ad188a5b0ec Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Fri, 5 Jun 2026 18:27:00 +0200 Subject: [PATCH 06/10] Done with TODOs for testing, but they have to be cleaned. --- .../Checkers/LifetimeAnnotations.cpp | 10 +++++----- clang/test/Analysis/lifetime-bound.cpp | 20 ++++++++++++++++--- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp index 8baf4b0ba223b..ac7d147b916fe 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp @@ -88,17 +88,17 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call, void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const { - auto LBVal = State->get<LifetimeBoundMap>(); - auto LBValTwo = State->get<LifetimeBoundMapVal>(); + auto LBMap = State->get<LifetimeBoundMap>(); + auto LBMapVal = State->get<LifetimeBoundMapVal>(); - if (LBVal.isEmpty() && LBValTwo.isEmpty()) + if (LBMap.isEmpty() && LBMapVal.isEmpty()) return; Out << Sep << "LifetimeBound bindings:" << NL; - for (auto&& [RetValSym, ArgValRegion] : LBVal) { + for (auto&& [RetValSym, ArgValRegion] : LBMap) { Out << " Origin " << RetValSym << " contains Loan " << ArgValRegion << NL; } - for (auto&& [RetVal, ArgValRegion]: LBValTwo) { + for (auto&& [RetVal, ArgValRegion]: LBMapVal) { Out << " Origin " << RetVal << " contains Loan " << ArgValRegion << NL; } } diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp index 2acc1734644f2..5029e6589a6be 100644 --- a/clang/test/Analysis/lifetime-bound.cpp +++ b/clang/test/Analysis/lifetime-bound.cpp @@ -81,6 +81,20 @@ void caller_five() { clang_analyzer_dump(s); } +// Free function with both annotated and non-annotated parameters. +int& fn(int& f, int& s [[clang::lifetimebound]]) { return s; } + +void clang_analyzer_lifetime_bound(int&); + +void caller_six() { + int even = 50; + int odd = 55; + int& s = fn(even, odd); + + clang_analyzer_lifetime_bound(s); // expected-warning {{Origin odd bound to odd}} + clang_analyzer_dump(s); +} + // These are the cases when the result of function calls are SymbolRefs. @@ -90,7 +104,7 @@ int* foo(int* n [[clang::lifetimebound]]); void clang_analyzer_lifetime_bound(int*); -void caller_six() { +void caller_seven() { int y = 15; int* y_ptr = &y; auto* bind = foo(y_ptr); @@ -114,7 +128,7 @@ int& func(int& some_number [[clang::lifetimebound]]); void clang_analyzer_lifetime_bound(int&); -void caller_seven() { +void caller_eight() { int f = 15; auto& bind = func(f); @@ -131,7 +145,7 @@ int& f(int& a [[clang::lifetimebound]], int& b [[clang::lifetimebound]]); void clang_analyzer_lifetime_bound(int&); -void caller_eight() { +void caller_nine() { int first_num = 1; int second_num = 2; auto numbers = f(first_num, second_num); >From e6efa13624dfecfbd33a8a9d9c164695ea4d6c5b Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Fri, 5 Jun 2026 20:59:28 +0200 Subject: [PATCH 07/10] Finished TODOs for the week. --- .../Checkers/LifetimeAnnotations.cpp | 1 - clang/test/Analysis/lifetime-bound.cpp | 72 ++++++------------- 2 files changed, 23 insertions(+), 50 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp index ac7d147b916fe..12cbf6cfe253f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp @@ -37,7 +37,6 @@ CallDescriptionMap<FnCheck> Callbacks = { void LifetimeAnnotations::checkPostCall(const CallEvent &Call, CheckerContext &C) const { - llvm::errs() << "checkPostCall fired" << "\n"; ProgramStateRef State = C.getState(); const auto *FC = dyn_cast_if_present<AnyFunctionCall>(&Call); diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp index 5029e6589a6be..230f6bd47e804 100644 --- a/clang/test/Analysis/lifetime-bound.cpp +++ b/clang/test/Analysis/lifetime-bound.cpp @@ -3,25 +3,25 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=alpha.cplusplus.LifetimeAnnotations \ // RUN: -analyzer-config c++-container-inlining=false -verify %s -void clang_analyzer_dump(...); +struct A {}; -// These are the cases when the result of function calls are MemRegions. +void clang_analyzer_lifetime_bound(int*); +void clang_analyzer_lifetime_bound(int&); +void clang_analyzer_lifetime_bound(A*); +void clang_analyzer_lifetime_bound(A&); -struct A {}; +// These are the cases when the result of function calls are MemRegions. // Ref type parameter annotated case struct X { int& choose(int& a [[clang::lifetimebound]]) { return a; } }; -void clang_analyzer_lifetime_bound(int&); - void caller() { int v = 0; X obj; int& r = obj.choose(v); - clang_analyzer_lifetime_bound(r); // expected-warning {{Origin v bound to v}} - clang_analyzer_dump(r); + clang_analyzer_lifetime_bound(r); // expected-warning {{bound to v}} } // Obj ref type function return annotated case @@ -30,14 +30,11 @@ struct Y { A& getA() [[clang::lifetimebound]] { return a; } }; -void clang_analyzer_lifetime_bound(A& a); - void caller_two() { // Return statement is annotated case. Y y; A& f = y.getA(); - clang_analyzer_lifetime_bound(f); // expected-warning {{Origin y.a bound to y}} - clang_analyzer_dump(f); + clang_analyzer_lifetime_bound(f); // expected-warning {{bound to y}} } // Obj ptr type function return annotated case @@ -46,53 +43,41 @@ struct Z { A* getA() [[clang::lifetimebound]] { return &a; } }; -void clang_analyzer_lifetime_bound(A* a); - void caller_three() { Z z; A* func = z.getA(); - clang_analyzer_lifetime_bound(func); // expected-warning {{Origin z.a bound to z}} - clang_analyzer_dump(func); + clang_analyzer_lifetime_bound(func); // expected-warning {{bound to z}} } // Free function with annotated param and ref return int& foo(int& num [[clang::lifetimebound]]) { return num; } -void clang_analyzer_lifetime_bound(int&); - void caller_four() { int num = 5; int& s = foo(num); - clang_analyzer_lifetime_bound(s); // expected-warning {{Origin num bound to num}} - clang_analyzer_dump(s); + clang_analyzer_lifetime_bound(s); // expected-warning {{bound to num}} } // Free function with annotated param and ptr return int* boo(int* num [[clang::lifetimebound]]) { return num; } -void clang_analyzer_lifetime_bound(int*); - void caller_five() { int n = 55; int* n_ptr = &n; int* s = boo(n_ptr); - clang_analyzer_lifetime_bound(s); // expected-warning {{Origin n bound to n}} - clang_analyzer_dump(s); + clang_analyzer_lifetime_bound(s); // expected-warning {{bound to n}} } // Free function with both annotated and non-annotated parameters. int& fn(int& f, int& s [[clang::lifetimebound]]) { return s; } -void clang_analyzer_lifetime_bound(int&); - void caller_six() { int even = 50; int odd = 55; int& s = fn(even, odd); - clang_analyzer_lifetime_bound(s); // expected-warning {{Origin odd bound to odd}} - clang_analyzer_dump(s); + clang_analyzer_lifetime_bound(s); // expected-warning {{bound to odd}} } @@ -102,18 +87,13 @@ void caller_six() { // Function returns ptr and has an annotated parameter int* foo(int* n [[clang::lifetimebound]]); -void clang_analyzer_lifetime_bound(int*); - void caller_seven() { int y = 15; int* y_ptr = &y; auto* bind = foo(y_ptr); - clang_analyzer_lifetime_bound(bind); - // expected-warning@-1 {{Origin bound to y}} - // expected-warning@-1 {{Origin contains loan y}} - clang_analyzer_dump(bind); - + clang_analyzer_lifetime_bound(bind); // expected-warning {{bound to y}} + // expected-warning@-1 {{contains loan y}} // FIXME: The full warning does look like this: // Origin SymRegion{conj_$5{int *, LC1, S847, #1}} bound to n // Origin conj_$5{int *, LC1, S847, #1} contains loan n @@ -122,36 +102,30 @@ void caller_seven() { // This does not apply to the test cases above this test case. } - // Function returns a reference and has an annotated parameter int& func(int& some_number [[clang::lifetimebound]]); -void clang_analyzer_lifetime_bound(int&); - void caller_eight() { int f = 15; auto& bind = func(f); - clang_analyzer_lifetime_bound(bind); - // expected-warning@-1 {{Origin bound to some_number}} - // expected-warning@-1 {{Origin contains loan some_number}} - clang_analyzer_dump(bind); - + clang_analyzer_lifetime_bound(bind); // expected-warning {{bound to f}} + // expected-warning@-1 {{contains loan f}} // The FIXME about the full warning applies to this text case as well. } // Function returns a reference and has two annotated parameters. int& f(int& a [[clang::lifetimebound]], int& b [[clang::lifetimebound]]); -void clang_analyzer_lifetime_bound(int&); - void caller_nine() { int first_num = 1; int second_num = 2; - auto numbers = f(first_num, second_num); + int& numbers = f(first_num, second_num); + + clang_analyzer_lifetime_bound(numbers); // expected-warning {{bound to first_num}} + // expected-warning@-1 {{contains loan first_num}} - clang_analyzer_lifetime_bound(numbers); - // expected-warning@-1 {{Origin bound to first_num}} - // expected-warning@-1 {{Origin contains loan first_num}} - clang_analyzer_dump(numbers); +// FIXME: Currently the callback only iterates until the first annotated parameter which +// means the second annotation never gets read here. That is a clear bug. It should be fixed +// in order to analyze all the parameters which are annotated. } >From 89332c6f707135b2de7dbd1ea8b578879ffca119 Mon Sep 17 00:00:00 2001 From: Benedek Kaibas <[email protected]> Date: Fri, 5 Jun 2026 21:03:21 +0200 Subject: [PATCH 08/10] Removed non-related fie. --- ...-AST-matching-to-get-containers-regi.patch | 130 ------------------ 1 file changed, 130 deletions(-) delete mode 100644 0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch diff --git a/0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch b/0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch deleted file mode 100644 index 450bdda9f70dc..0000000000000 --- a/0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch +++ /dev/null @@ -1,130 +0,0 @@ -From 00c5cf9e103a88ad9af285104f878c5f932ad8ff Mon Sep 17 00:00:00 2001 -From: benedekaibas <[email protected]> -Date: Tue, 2 Jun 2026 13:13:46 +0200 -Subject: [PATCH] [analyzer] Removed AST matching to get containers' region and - fixed small issues. - ---- - .../StaticAnalyzer/Checkers/MoveChecker.cpp | 40 +++++++++++-------- - .../test/Analysis/use-after-move-iterator.cpp | 24 +++++------ - 2 files changed, 36 insertions(+), 28 deletions(-) - -diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp -index 47e9c585054a..9ff2e90b618a 100644 ---- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp -+++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp -@@ -518,31 +518,29 @@ bool MoveChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { - if (!StdMoveCall.matches(Call)) - return false; - -- const auto *BeginCall = -- dyn_cast<CXXMemberCallExpr>(CE->getArg(0)->IgnoreImpCasts()); -- if (!BeginCall) -+ const auto *POS = getIteratorPosition(State, Call.getArgSVal(0)); -+ if (!POS) - return false; - -- const Expr *ContainerExpr = BeginCall->getImplicitObjectArgument(); -- const auto *DRE = dyn_cast<DeclRefExpr>(ContainerExpr->IgnoreImpCasts()); -- if (!DRE) -+ const MemRegion *ContainerRegion = POS->getContainer(); -+ if (!ContainerRegion) - return false; - -- const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()); -- if (!VD) -+ const auto *TypedRegion = -+ dyn_cast_if_present<TypedValueRegion>(ContainerRegion); -+ if (!TypedRegion) - return false; - -- const MemRegion *Region = -- State->getLValue(VD, C.getLocationContext()).getAsRegion(); -- if (!Region) -- return false; -+ QualType ObjTy = TypedRegion->getValueType(); - -- const CXXRecordDecl *RD = ContainerExpr->getType()->getAsCXXRecordDecl(); -+ const auto *RD = ObjTy->getAsCXXRecordDecl(); - if (!RD) - return false; - -- ObjectKind OK = classifyObject(State, Region, RD); -+ ObjectKind OK = classifyObject(State, ContainerRegion, RD); - -+ // FIXME: Also apply getIteratorPosition from IteratorModeling to recover the -+ // destination region instead of doing AST pattern matching. - const auto *BackInsCall = dyn_cast<CallExpr>(CE->getArg(2)->IgnoreImpCasts()); - if (!BackInsCall) - return false; -@@ -573,7 +571,8 @@ bool MoveChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { - /*CausesPointerEscape=*/false); - - if (shouldBeTracked(OK)) -- State = State->set<TrackedContentsMap>(Region, RegionState::getMoved()); -+ State = State->set<TrackedContentsMap>(ContainerRegion, -+ RegionState::getMoved()); - - C.addTransition(State); - return true; -@@ -736,10 +735,19 @@ void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { - - if (const auto *POS = getIteratorPosition(State, Val)) { - const MemRegion *ContainerRegion = POS->getContainer(); -+ if (!ContainerRegion) -+ return; -+ -+ const auto *TypedRegion = -+ dyn_cast_if_present<TypedValueRegion>(ContainerRegion); -+ if (!TypedRegion) -+ return; - -- const auto *TypedRegion = cast<TypedValueRegion>(ContainerRegion); - QualType ObjTy = TypedRegion->getValueType(); - const auto *R = ObjTy->getAsCXXRecordDecl(); -+ if (!R) -+ return; -+ - if (State->get<TrackedContentsMap>(ContainerRegion)) { - ExplodedNode *N = tryToReportBug(ContainerRegion, R, C, MK_FunCall); - if (!N || N->isSink()) -diff --git a/clang/test/Analysis/use-after-move-iterator.cpp b/clang/test/Analysis/use-after-move-iterator.cpp -index 50dd7e57b42e..2357be3a6bb3 100644 ---- a/clang/test/Analysis/use-after-move-iterator.cpp -+++ b/clang/test/Analysis/use-after-move-iterator.cpp -@@ -10,20 +10,20 @@ - // IteratorModeling is enabled. - //===----------------------------------------------------------------------===// - --void iteratorDerefSource() { -+std::string iteratorDeref(int rng) { - std::list<std::string> l1; - l1.push_back("l1"); - std::list<std::string> l2; - -- std::move(l1.begin(), l1.end(), std::back_inserter(l2)); -- *l1.cbegin(); // expected-warning {{Method called on moved-from object 'l1'}} --} -- --void iteratorDerefDest() { -- std::list<std::string> l1; -- l1.push_back("l1"); -- std::list<std::string> l2; -- -- std::move(l1.begin(), l1.end(), std::back_inserter(l2)); -- *l2.cbegin(); // no-warning -+ switch (rng) { -+ case 10: { -+ std::move(l1.begin(), l1.end(), std::back_inserter(l2)); -+ return *l1.cbegin(); // expected-warning {{Method called on moved-from object 'l1'}} -+ } -+ case 20: { -+ std::move(l1.begin(), l1.end(), std::back_inserter(l2)); -+ return *l2.cbegin(); // no-warning: only l1 was invalidated and not l2! -+ } -+ } -+ return 0; - } --- -2.43.0 - >From 9733f253c9b8a218cd8de83ef4a922d8810fd6fb Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Mon, 8 Jun 2026 12:31:13 +0200 Subject: [PATCH 09/10] Addressed mentors' issues. --- .../Checkers/LifetimeAnnotations.cpp | 14 ++++++-------- clang/test/Analysis/lifetime-bound.cpp | 18 ++++-------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp index 12cbf6cfe253f..01dbb0c115bc3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp @@ -10,7 +10,6 @@ using namespace clang; using namespace ento; -using namespace clang::lifetimes; REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SymbolRef, const MemRegion *); @@ -29,7 +28,7 @@ class LifetimeAnnotations : public Checker<check::PostCall, eval::Call> { }; typedef void (LifetimeAnnotations::*FnCheck)(const CallEvent &Call, const CallExpr *, - CheckerContext &) const; + CheckerContext &C) const; CallDescriptionMap<FnCheck> Callbacks = { {{CDM::SimpleFunc, {"clang_analyzer_lifetime_bound"}}, &LifetimeAnnotations::analyzerLifetimeBound}, @@ -39,7 +38,7 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); - const auto *FC = dyn_cast_if_present<AnyFunctionCall>(&Call); + const auto *FC = dyn_cast<AnyFunctionCall>(&Call); if (!FC) return; @@ -67,17 +66,17 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call, if (const MemRegion *ArgValRegion = ArgVal.getAsRegion()) { if (RetValSym) State = State->set<LifetimeBoundMap>(RetValSym, ArgValRegion); - if (const MemRegion *RetValRegion = RetVal.getAsRegion()) + else if (const MemRegion *RetValRegion = RetVal.getAsRegion()) State = State->set<LifetimeBoundMapVal>(RetValRegion, ArgValRegion); } } if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) { - if (implicitObjectParamIsLifetimeBound(FD)) { + if (clang::lifetimes::implicitObjectParamIsLifetimeBound(FD)) { if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) { if (RetValSym) State = State->set<LifetimeBoundMap>(RetValSym, AttrRegion); - if (const MemRegion *RetValRegion = RetVal.getAsRegion()) + else if (const MemRegion *RetValRegion = RetVal.getAsRegion()) State = State->set<LifetimeBoundMapVal>(RetValRegion, AttrRegion); } } @@ -104,7 +103,7 @@ void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State, bool LifetimeAnnotations::evalCall(const CallEvent &Call, CheckerContext &C) const { - const auto *CE = llvm::dyn_cast_if_present<CallExpr>(Call.getOriginExpr()); + const auto *CE = dyn_cast_if_present<CallExpr>(Call.getOriginExpr()); if (!CE) return false; @@ -114,7 +113,6 @@ bool LifetimeAnnotations::evalCall(const CallEvent &Call, CheckerContext &C) con (this->*(*Handler))(Call, CE, C); return true; - C.addTransition(C.getState()); } void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, const CallExpr *CE, CheckerContext &C) const { diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp index 230f6bd47e804..1d9b4eabcee04 100644 --- a/clang/test/Analysis/lifetime-bound.cpp +++ b/clang/test/Analysis/lifetime-bound.cpp @@ -77,7 +77,7 @@ void caller_six() { int odd = 55; int& s = fn(even, odd); - clang_analyzer_lifetime_bound(s); // expected-warning {{bound to odd}} + clang_analyzer_lifetime_bound(s); // expected-warning {{Origin odd bound to odd}} } @@ -92,14 +92,7 @@ void caller_seven() { int* y_ptr = &y; auto* bind = foo(y_ptr); - clang_analyzer_lifetime_bound(bind); // expected-warning {{bound to y}} - // expected-warning@-1 {{contains loan y}} -// FIXME: The full warning does look like this: -// Origin SymRegion{conj_$5{int *, LC1, S847, #1}} bound to n -// Origin conj_$5{int *, LC1, S847, #1} contains loan n -// Since the conj sym number and the ID can change across runs I have decided to just include -// string parts of the error message since that is the only consistent part of the emitted report. -// This does not apply to the test cases above this test case. + clang_analyzer_lifetime_bound(bind); // expected-warning {{contains loan y}} } // Function returns a reference and has an annotated parameter @@ -109,9 +102,7 @@ void caller_eight() { int f = 15; auto& bind = func(f); - clang_analyzer_lifetime_bound(bind); // expected-warning {{bound to f}} - // expected-warning@-1 {{contains loan f}} -// The FIXME about the full warning applies to this text case as well. + clang_analyzer_lifetime_bound(bind); // expected-warning {{contains loan f}} } // Function returns a reference and has two annotated parameters. @@ -122,8 +113,7 @@ void caller_nine() { int second_num = 2; int& numbers = f(first_num, second_num); - clang_analyzer_lifetime_bound(numbers); // expected-warning {{bound to first_num}} - // expected-warning@-1 {{contains loan first_num}} + clang_analyzer_lifetime_bound(numbers); // expected-warning {{contains loan first_num}} // FIXME: Currently the callback only iterates until the first annotated parameter which // means the second annotation never gets read here. That is a clear bug. It should be fixed >From 3617de73fef4e11259320cadcf3bdc38fffca0df Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Mon, 8 Jun 2026 12:55:20 +0200 Subject: [PATCH 10/10] Currently LazyCoumpoundVal is not present in any of the maps. --- clang/test/Analysis/lifetime-bound.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp index 1d9b4eabcee04..1e5afa9159bd4 100644 --- a/clang/test/Analysis/lifetime-bound.cpp +++ b/clang/test/Analysis/lifetime-bound.cpp @@ -119,3 +119,17 @@ void caller_nine() { // means the second annotation never gets read here. That is a clear bug. It should be fixed // in order to analyze all the parameters which are annotated. } + +struct View { + int* p; +}; +View makeView(int& x [[clang::lifetimebound]]); + +void clang_analyzer_lifetime_bound(View); + +void caller_view() { + int v = 42; + View w = makeView(v); + // FIXME: Currently none of the maps cover LazyCompoundVal + clang_analyzer_lifetime_bound(w); // no-warning +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
