[clang] [llvm] [analyzer] Implemented a base of detecing lifetimebound annotation (PR #200145)
Benedek Kaibas via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 8 03:35:32 PDT 2026
https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/200145
>From 826dbd7525158bf0946b41ec0f629c235b8238b2 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Thu, 28 May 2026 11:37:49 +0200
Subject: [PATCH 1/9] 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 <kaibas.benedek02 at gmail.com>
Date: Sun, 31 May 2026 00:40:48 +0200
Subject: [PATCH 2/9] 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 <kaibas.benedek02 at gmail.com>
Date: Fri, 5 Jun 2026 13:33:50 +0200
Subject: [PATCH 3/9] 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 <kaibas.benedek02 at gmail.com>
+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 at -1 {{Origin bound to n}}
+ // expected-warning at -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 <kaibas.benedek02 at gmail.com>
Date: Fri, 5 Jun 2026 16:09:28 +0200
Subject: [PATCH 4/9] 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 at -1 {{Origin bound to some_number}}
+ // expected-warning at -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 <kaibas.benedek02 at gmail.com>
Date: Fri, 5 Jun 2026 16:37:42 +0200
Subject: [PATCH 5/9] 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 at -1 {{Origin bound to n}}
- // expected-warning at -1 {{Origin contains loan n}}
+ // expected-warning at -1 {{Origin bound to y}}
+ // expected-warning at -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 at -1 {{Origin bound to some_number}}
- // expected-warning at -1 {{Origin contains loan some_number}}
+ // expected-warning at -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 at -1 {{Origin bound to first_num}}
+ // expected-warning at -1 {{Origin contains loan first_num}}
+ clang_analyzer_dump(numbers);
+}
>From 5b83c6cdfb7f0c395f286d2ce45e2ad188a5b0ec Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Fri, 5 Jun 2026 18:27:00 +0200
Subject: [PATCH 6/9] 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 <kaibas.benedek02 at gmail.com>
Date: Fri, 5 Jun 2026 20:59:28 +0200
Subject: [PATCH 7/9] 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 at -1 {{Origin bound to y}}
- // expected-warning at -1 {{Origin contains loan y}}
- clang_analyzer_dump(bind);
-
+ clang_analyzer_lifetime_bound(bind); // expected-warning {{bound to y}}
+ // expected-warning at -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 at -1 {{Origin bound to some_number}}
- // expected-warning at -1 {{Origin contains loan some_number}}
- clang_analyzer_dump(bind);
-
+ clang_analyzer_lifetime_bound(bind); // expected-warning {{bound to f}}
+ // expected-warning at -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 at -1 {{contains loan first_num}}
- clang_analyzer_lifetime_bound(numbers);
- // expected-warning at -1 {{Origin bound to first_num}}
- // expected-warning at -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 <82393336+benedekaibas at users.noreply.github.com>
Date: Fri, 5 Jun 2026 21:03:21 +0200
Subject: [PATCH 8/9] 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 <kaibas.benedek02 at gmail.com>
-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 <kaibas.benedek02 at gmail.com>
Date: Mon, 8 Jun 2026 12:31:13 +0200
Subject: [PATCH 9/9] 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 at -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 at -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 at -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
More information about the cfe-commits
mailing list