[clang] [llvm] [analyzer] Implemented a base of detecing lifetimebound annotation (PR #200145)
Benedek Kaibas via cfe-commits
cfe-commits at lists.llvm.org
Fri Jun 12 16:48:05 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 01/23] 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 02/23] 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 03/23] 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 04/23] 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 05/23] 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 06/23] 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 07/23] 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 08/23] 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 09/23] 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
>From 3617de73fef4e11259320cadcf3bdc38fffca0df Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Mon, 8 Jun 2026 12:55:20 +0200
Subject: [PATCH 10/23] 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
+}
>From d91f1e5568bfaf3bdf7eff3416d5d45f3829545f Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Tue, 9 Jun 2026 14:13:05 +0200
Subject: [PATCH 11/23] The checker correctly reads multiple annotated
parameters.
---
.../Checkers/LifetimeAnnotations.cpp | 117 +++++++++++-------
clang/test/Analysis/lifetime-bound.cpp | 22 ++--
2 files changed, 81 insertions(+), 58 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 01dbb0c115bc3..28f67208c338c 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -1,39 +1,63 @@
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#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 "llvm/Support/raw_ostream.h"
-#include "AllocationState.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
-
using namespace clang;
using namespace ento;
-REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SymbolRef,
- const MemRegion *);
-REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMapVal, const MemRegion *, const MemRegion *);
+struct LifetimeMap {
+ SymbolRef SymRefType;
+ const MemRegion *MemRegType;
+
+ bool operator==(const LifetimeMap &Type) const {
+ return std::tie(SymRefType, MemRegType) ==
+ std::tie(Type.SymRefType, Type.MemRegType);
+ }
+
+ bool operator<(const LifetimeMap &Type) const {
+ return std::tie(SymRefType, MemRegType) <
+ std::tie(Type.SymRefType, Type.MemRegType);
+ }
+
+ void Profile(llvm::FoldingSetNodeID &ID) const {
+ ID.AddPointer(SymRefType);
+ ID.AddPointer(MemRegType);
+ }
+};
+
+REGISTER_SET_WITH_PROGRAMSTATE(LifetimeBoundSet, LifetimeMap)
+REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMapVal, const MemRegion *,
+ const MemRegion *)
+namespace {
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;
+ 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 &C) const;
-CallDescriptionMap<FnCheck> Callbacks = {
- {{CDM::SimpleFunc, {"clang_analyzer_lifetime_bound"}},
- &LifetimeAnnotations::analyzerLifetimeBound},
+ using FnCheck = void (LifetimeAnnotations::*)(const CallEvent &Call,
+ const CallExpr *,
+ CheckerContext &C) const;
+
+ const CallDescriptionMap<FnCheck> Callbacks = {
+ {{CDM::SimpleFunc, {"clang_analyzer_lifetime_bound"}},
+ &LifetimeAnnotations::analyzerLifetimeBound},
+ };
};
+} // namespace
+
void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
@@ -48,26 +72,19 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
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
- // 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 != FD->getNumParams()) {
- SVal ArgVal = Call.getArgSVal(LBParamIdx);
- if (const MemRegion *ArgValRegion = ArgVal.getAsRegion()) {
- if (RetValSym)
- State = State->set<LifetimeBoundMap>(RetValSym, ArgValRegion);
- else if (const MemRegion *RetValRegion = RetVal.getAsRegion())
- State = State->set<LifetimeBoundMapVal>(RetValRegion, ArgValRegion);
+ for (const ParmVarDecl *PVD : FD->parameters()) {
+ if (PVD->hasAttr<LifetimeBoundAttr>()) {
+ unsigned Idx = PVD->getFunctionScopeIndex();
+ SVal Arg = Call.getArgSVal(Idx);
+
+ if (const MemRegion *ArgValRegion = Arg.getAsRegion()) {
+ if (RetValSym)
+ State = State->add<LifetimeBoundSet>(
+ LifetimeMap{RetValSym, ArgValRegion});
+ else if (const MemRegion *RetValRegion = RetVal.getAsRegion())
+ State = State->set<LifetimeBoundMapVal>(RetValRegion, ArgValRegion);
+ }
}
}
@@ -75,7 +92,8 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
if (clang::lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) {
if (RetValSym)
- State = State->set<LifetimeBoundMap>(RetValSym, AttrRegion);
+ State =
+ State->add<LifetimeBoundSet>(LifetimeMap{RetValSym, AttrRegion});
else if (const MemRegion *RetValRegion = RetVal.getAsRegion())
State = State->set<LifetimeBoundMapVal>(RetValRegion, AttrRegion);
}
@@ -86,22 +104,23 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
const char *NL, const char *Sep) const {
- auto LBMap = State->get<LifetimeBoundMap>();
+ auto LBMap = State->get<LifetimeBoundSet>();
auto LBMapVal = State->get<LifetimeBoundMapVal>();
if (LBMap.isEmpty() && LBMapVal.isEmpty())
return;
Out << Sep << "LifetimeBound bindings:" << NL;
- for (auto&& [RetValSym, ArgValRegion] : LBMap) {
+ for (auto &&[RetValSym, ArgValRegion] : LBMap) {
Out << " Origin " << RetValSym << " contains Loan " << ArgValRegion << NL;
}
- for (auto&& [RetVal, ArgValRegion]: LBMapVal) {
+ for (auto &&[RetVal, ArgValRegion] : LBMapVal) {
Out << " Origin " << RetVal << " contains Loan " << ArgValRegion << NL;
}
}
-bool LifetimeAnnotations::evalCall(const CallEvent &Call, CheckerContext &C) const {
+bool LifetimeAnnotations::evalCall(const CallEvent &Call,
+ CheckerContext &C) const {
const auto *CE = dyn_cast_if_present<CallExpr>(Call.getOriginExpr());
if (!CE)
@@ -115,11 +134,13 @@ bool LifetimeAnnotations::evalCall(const CallEvent &Call, CheckerContext &C) con
return true;
}
-void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, const CallExpr *CE, CheckerContext &C) const {
+void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call,
+ const CallExpr *CE,
+ CheckerContext &C) const {
ProgramStateRef State = C.getState();
- unsigned int ArgExpr = CE->getNumArgs();
- if (ArgExpr != 1)
+ unsigned int ArgCount = CE->getNumArgs();
+ if (ArgCount != 1)
return;
SVal ArgSVal = Call.getArgSVal(0);
@@ -134,16 +155,20 @@ void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, const Cal
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();
+ auto LBSet = State->get<LifetimeBoundSet>();
+ for (const LifetimeMap &Entry : LBSet) {
+ if (Entry.SymRefType == ArgSValSym) {
+ OS << " Origin " << ArgSValSym << " bound to " << Entry.MemRegType;
+ 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)) {
+ 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));
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index 1e5afa9159bd4..f9389c88854e9 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -21,7 +21,7 @@ void caller() {
int v = 0;
X obj;
int& r = obj.choose(v);
- clang_analyzer_lifetime_bound(r); // expected-warning {{bound to v}}
+ clang_analyzer_lifetime_bound(r); // expected-warning {{Origin v bound to v}}
}
// Obj ref type function return annotated case
@@ -34,7 +34,7 @@ void caller_two() {
// Return statement is annotated case.
Y y;
A& f = y.getA();
- clang_analyzer_lifetime_bound(f); // expected-warning {{bound to y}}
+ clang_analyzer_lifetime_bound(f); // expected-warning {{Origin y.a bound to y}}
}
// Obj ptr type function return annotated case
@@ -46,7 +46,7 @@ struct Z {
void caller_three() {
Z z;
A* func = z.getA();
- clang_analyzer_lifetime_bound(func); // expected-warning {{bound to z}}
+ clang_analyzer_lifetime_bound(func); // expected-warning {{Origin z.a bound to z}}
}
// Free function with annotated param and ref return
@@ -55,7 +55,7 @@ int& foo(int& num [[clang::lifetimebound]]) { return num; }
void caller_four() {
int num = 5;
int& s = foo(num);
- clang_analyzer_lifetime_bound(s); // expected-warning {{bound to num}}
+ clang_analyzer_lifetime_bound(s); // expected-warning {{Origin num bound to num}}
}
// Free function with annotated param and ptr return
@@ -66,7 +66,7 @@ void caller_five() {
int* n_ptr = &n;
int* s = boo(n_ptr);
- clang_analyzer_lifetime_bound(s); // expected-warning {{bound to n}}
+ clang_analyzer_lifetime_bound(s); // expected-warning {{Origin n bound to n}}
}
// Free function with both annotated and non-annotated parameters.
@@ -92,7 +92,7 @@ void caller_seven() {
int* y_ptr = &y;
auto* bind = foo(y_ptr);
- clang_analyzer_lifetime_bound(bind); // expected-warning {{contains loan y}}
+ clang_analyzer_lifetime_bound(bind); // expected-warning-re {{Origin conj_${{[0-9]+}}{int *, LC{{[0-9]+}}, S{{[0-9]+}}, #{{[0-9]+}}} bound to y}}
}
// Function returns a reference and has an annotated parameter
@@ -102,7 +102,7 @@ void caller_eight() {
int f = 15;
auto& bind = func(f);
- clang_analyzer_lifetime_bound(bind); // expected-warning {{contains loan f}}
+ clang_analyzer_lifetime_bound(bind); // expected-warning-re {{Origin conj_${{[0-9]+}}{int &, LC{{[0-9]+}}, S{{[0-9]+}}, #{{[0-9]+}}} bound to f}}
}
// Function returns a reference and has two annotated parameters.
@@ -113,11 +113,9 @@ void caller_nine() {
int second_num = 2;
int& numbers = f(first_num, second_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
-// in order to analyze all the parameters which are annotated.
+ clang_analyzer_lifetime_bound(numbers);
+ // expected-warning-re at -1 {{Origin conj_${{[0-9]+}}{int &, LC{{[0-9]+}}, S{{[0-9]+}}, #{{[0-9]+}}} bound to first_num}}
+ // expected-warning-re at -2 {{Origin conj_${{[0-9]+}}{int &, LC{{[0-9]+}}, S{{[0-9]+}}, #{{[0-9]+}}} bound to second_num}}
}
struct View {
>From ab58e94d2867835c3c5dcc9b4678cd33e7850f87 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Wed, 10 Jun 2026 12:06:23 +0200
Subject: [PATCH 12/23] [analyzer] Cleaned up the code to format correctly.
---
.../Checkers/LifetimeAnnotations.cpp | 29 ++++++++++---------
1 file changed, 16 insertions(+), 13 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 28f67208c338c..e8deb2e0d6f55 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -43,6 +43,8 @@ class LifetimeAnnotations : public Checker<check::PostCall, eval::Call> {
bool evalCall(const CallEvent &Call, CheckerContext &C) const;
void analyzerLifetimeBound(const CallEvent &Call, const CallExpr *,
CheckerContext &C) const;
+ ProgramStateRef bindValues(ProgramStateRef State, SymbolRef RetValSym, SVal RetVal, const MemRegion *Source) const;
+
const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"};
@@ -58,6 +60,17 @@ class LifetimeAnnotations : public Checker<check::PostCall, eval::Call> {
} // namespace
+ProgramStateRef LifetimeAnnotations::bindValues(ProgramStateRef State, SymbolRef RetValSym, SVal RetVal, const MemRegion *Source) const {
+
+ if (Source) {
+ if (RetValSym)
+ State = State->add<LifetimeBoundSet>(LifetimeMap{RetValSym, Source});
+ else if (const MemRegion *RetValRegion = RetVal.getAsRegion())
+ State = State->set<LifetimeBoundMapVal>(RetValRegion, Source);
+ }
+ return State;
+}
+
void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
@@ -77,25 +90,15 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
if (PVD->hasAttr<LifetimeBoundAttr>()) {
unsigned Idx = PVD->getFunctionScopeIndex();
SVal Arg = Call.getArgSVal(Idx);
-
- if (const MemRegion *ArgValRegion = Arg.getAsRegion()) {
- if (RetValSym)
- State = State->add<LifetimeBoundSet>(
- LifetimeMap{RetValSym, ArgValRegion});
- else if (const MemRegion *RetValRegion = RetVal.getAsRegion())
- State = State->set<LifetimeBoundMapVal>(RetValRegion, ArgValRegion);
+ if (const MemRegion *ArgValRegion = Arg.getAsRegion())
+ State = bindValues(State, RetValSym, RetVal, ArgValRegion);
}
}
- }
if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
if (clang::lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) {
- if (RetValSym)
- State =
- State->add<LifetimeBoundSet>(LifetimeMap{RetValSym, AttrRegion});
- else if (const MemRegion *RetValRegion = RetVal.getAsRegion())
- State = State->set<LifetimeBoundMapVal>(RetValRegion, AttrRegion);
+ State = bindValues(State, RetValSym, RetVal, AttrRegion);
}
}
}
>From 7acb413f67a38ed658969281775d01c517f8799c Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Wed, 10 Jun 2026 18:50:35 +0200
Subject: [PATCH 13/23] [analyzer] Rewrote maps and binding.
---
.../Checkers/LifetimeAnnotations.cpp | 52 +++++++++++--------
1 file changed, 31 insertions(+), 21 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index e8deb2e0d6f55..408d869d14e7b 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -29,10 +29,10 @@ struct LifetimeMap {
}
};
-REGISTER_SET_WITH_PROGRAMSTATE(LifetimeBoundSet, LifetimeMap)
+REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
+REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SymbolRef, LifetimeSourceSet)
-REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMapVal, const MemRegion *,
- const MemRegion *)
+REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMapVal, const MemRegion *, LifetimeSourceSet)
namespace {
class LifetimeAnnotations : public Checker<check::PostCall, eval::Call> {
@@ -61,16 +61,24 @@ class LifetimeAnnotations : public Checker<check::PostCall, eval::Call> {
} // namespace
ProgramStateRef LifetimeAnnotations::bindValues(ProgramStateRef State, SymbolRef RetValSym, SVal RetVal, const MemRegion *Source) const {
+ LifetimeSourceSet::Factory &F = State->getStateManager().get_context<LifetimeSourceSet>();
- if (Source) {
- if (RetValSym)
- State = State->add<LifetimeBoundSet>(LifetimeMap{RetValSym, Source});
- else if (const MemRegion *RetValRegion = RetVal.getAsRegion())
- State = State->set<LifetimeBoundMapVal>(RetValRegion, Source);
+ if (RetValSym) {
+ const LifetimeSourceSet *LBSet = State->get<LifetimeBoundMap>(RetValSym);
+ LifetimeSourceSet Set = LBSet ? *LBSet : F.getEmptySet();
+ Set = F.add(Set, Source);
+ State = State->set<LifetimeBoundMap>(RetValSym, Set);
+ }
+ else if (const MemRegion *RetValRegion = RetVal.getAsRegion()) {
+ const LifetimeSourceSet *LBValSet = State->get<LifetimeBoundMapVal>(RetValRegion);
+ LifetimeSourceSet Set = LBValSet ? *LBValSet : F.getEmptySet();
+ Set = F.add(Set, Source);
+ State = State->set<LifetimeBoundMapVal>(RetValRegion, Set);
}
return State;
}
+
void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
@@ -107,7 +115,7 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
const char *NL, const char *Sep) const {
- auto LBMap = State->get<LifetimeBoundSet>();
+ auto LBMap = State->get<LifetimeBoundMap>();
auto LBMapVal = State->get<LifetimeBoundMapVal>();
if (LBMap.isEmpty() && LBMapVal.isEmpty())
@@ -115,10 +123,12 @@ void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
Out << Sep << "LifetimeBound bindings:" << NL;
for (auto &&[RetValSym, ArgValRegion] : LBMap) {
- Out << " Origin " << RetValSym << " contains Loan " << ArgValRegion << NL;
+ for (const auto *Region : ArgValRegion)
+ Out << " Origin " << RetValSym << " contains Loan " << Region << NL;
}
for (auto &&[RetVal, ArgValRegion] : LBMapVal) {
- Out << " Origin " << RetVal << " contains Loan " << ArgValRegion << NL;
+ for (const auto *Region : ArgValRegion)
+ Out << " Origin " << RetVal << " contains Loan " << Region << NL;
}
}
@@ -158,10 +168,9 @@ void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call,
return;
if (ArgSValSym) {
- auto LBSet = State->get<LifetimeBoundSet>();
- for (const LifetimeMap &Entry : LBSet) {
- if (Entry.SymRefType == ArgSValSym) {
- OS << " Origin " << ArgSValSym << " bound to " << Entry.MemRegType;
+ if (auto *SourceSet = State->get<LifetimeBoundMap>(ArgSValSym)) {
+ for (const auto *Region : *SourceSet) {
+ OS << " Origin " << ArgSValSym << " bound to " << Region;
auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
C.emitReport(std::move(BR));
Str.clear();
@@ -170,12 +179,13 @@ void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call,
}
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();
+ if (auto *SourceSet = State->get<LifetimeBoundMapVal>(ArgValRegion)) {
+ for (const auto *Region : *SourceSet) {
+ OS << " Origin " << ArgValRegion << " bound to " << Region;
+ auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
+ C.emitReport(std::move(BR));
+ Str.clear();
+ }
}
}
}
>From 2280f3cbdeab599c349a9b88819bb2dc43c437b3 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Wed, 10 Jun 2026 18:59:09 +0200
Subject: [PATCH 14/23] [analyzer] Removed dead code and applied more accurate
naming for binding function.
---
.../Checkers/LifetimeAnnotations.cpp | 32 ++++---------------
1 file changed, 6 insertions(+), 26 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 408d869d14e7b..fa80922f5a6f1 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -9,26 +9,6 @@
using namespace clang;
using namespace ento;
-struct LifetimeMap {
- SymbolRef SymRefType;
- const MemRegion *MemRegType;
-
- bool operator==(const LifetimeMap &Type) const {
- return std::tie(SymRefType, MemRegType) ==
- std::tie(Type.SymRefType, Type.MemRegType);
- }
-
- bool operator<(const LifetimeMap &Type) const {
- return std::tie(SymRefType, MemRegType) <
- std::tie(Type.SymRefType, Type.MemRegType);
- }
-
- void Profile(llvm::FoldingSetNodeID &ID) const {
- ID.AddPointer(SymRefType);
- ID.AddPointer(MemRegType);
- }
-};
-
REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SymbolRef, LifetimeSourceSet)
@@ -122,13 +102,13 @@ void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
return;
Out << Sep << "LifetimeBound bindings:" << NL;
- for (auto &&[RetValSym, ArgValRegion] : LBMap) {
- for (const auto *Region : ArgValRegion)
- Out << " Origin " << RetValSym << " contains Loan " << Region << NL;
+ for (auto &&[OriginSym, SourceSet] : LBMap) {
+ for (const auto *Region : SourceSet)
+ Out << " Origin " << OriginSym << " contains Loan " << Region << NL;
}
- for (auto &&[RetVal, ArgValRegion] : LBMapVal) {
- for (const auto *Region : ArgValRegion)
- Out << " Origin " << RetVal << " contains Loan " << Region << NL;
+ for (auto &&[OriginRegion, SourceSet] : LBMapVal) {
+ for (const auto *Region : SourceSet)
+ Out << " Origin " << OriginRegion << " contains Loan " << Region << NL;
}
}
>From cf23690d609bfbba675db3f49539cd1ccb2197df Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Thu, 11 Jun 2026 12:11:27 +0200
Subject: [PATCH 15/23] [analyzer] Started implementing checking and marking
dead symbols.
---
clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index fa80922f5a6f1..11c53a1bfa3e7 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -24,6 +24,7 @@ class LifetimeAnnotations : public Checker<check::PostCall, eval::Call> {
void analyzerLifetimeBound(const CallEvent &Call, const CallExpr *,
CheckerContext &C) const;
ProgramStateRef bindValues(ProgramStateRef State, SymbolRef RetValSym, SVal RetVal, const MemRegion *Source) const;
+ void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"};
@@ -93,6 +94,12 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
C.addTransition(State);
}
+void LifetimeAnnotations::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ LifetimeBoundMapTy TrackedRegion = State->get<LifetimeBoundMap>();
+}
+
void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
const char *NL, const char *Sep) const {
auto LBMap = State->get<LifetimeBoundMap>();
>From 03727378b285dd25d8e061e8b465e3224479185e Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Thu, 11 Jun 2026 18:18:07 +0200
Subject: [PATCH 16/23] [analyzer] Implemented dangling borrower detection.
---
.../Checkers/LifetimeAnnotations.cpp | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 11c53a1bfa3e7..65dbb51440992 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -24,8 +24,7 @@ class LifetimeAnnotations : public Checker<check::PostCall, eval::Call> {
void analyzerLifetimeBound(const CallEvent &Call, const CallExpr *,
CheckerContext &C) const;
ProgramStateRef bindValues(ProgramStateRef State, SymbolRef RetValSym, SVal RetVal, const MemRegion *Source) const;
- void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
-
+ bool isSourceDangle(const MemRegion *Source, ProgramStateRef State, CheckerContext &C) const;
const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"};
@@ -94,10 +93,19 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
C.addTransition(State);
}
-void LifetimeAnnotations::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const {
- ProgramStateRef State = C.getState();
+bool LifetimeAnnotations::isSourceDangle(const MemRegion *Source, ProgramStateRef State, CheckerContext &C) const {
+ // Q1: Am I sure I need ProgramStateRef State as a parameter?
+
+ if (const auto *StackSpace = Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
+ const StackFrame *SF = StackSpace->getStackFrame();
+ const StackFrame *CurrentSF = C.getStackFrame();
+ if (SF == CurrentSF || SF->isParentOf(CurrentSF))
+ return false;
+ return false;
+ }
- LifetimeBoundMapTy TrackedRegion = State->get<LifetimeBoundMap>();
+ // Currently return false, but this has to be replaced when the source is a SymRegion instead of a MemRegion
+ return true;
}
void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
>From 048acb511f2644231726fa9a18f8abb725a9bc29 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Thu, 11 Jun 2026 18:29:48 +0200
Subject: [PATCH 17/23] [analyzer] Start implementing the checkEndFunction.
---
clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 65dbb51440992..d0ea7dccfbaf9 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -15,7 +15,7 @@ REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SymbolRef, LifetimeSourceSet)
REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMapVal, const MemRegion *, LifetimeSourceSet)
namespace {
-class LifetimeAnnotations : public Checker<check::PostCall, eval::Call> {
+class LifetimeAnnotations : public Checker<check::PostCall, check::EndFunction, eval::Call> {
public:
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
@@ -25,6 +25,7 @@ class LifetimeAnnotations : public Checker<check::PostCall, eval::Call> {
CheckerContext &C) const;
ProgramStateRef bindValues(ProgramStateRef State, SymbolRef RetValSym, SVal RetVal, const MemRegion *Source) const;
bool isSourceDangle(const MemRegion *Source, ProgramStateRef State, CheckerContext &C) const;
+ void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"};
>From 8cc9fa1b8ba1701fc2cdf420a5dc4e834f3b6dfe Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Fri, 12 Jun 2026 11:58:31 +0200
Subject: [PATCH 18/23] [analyzer] Dangling source detection works for
MemRegions.
---
.../Checkers/LifetimeAnnotations.cpp | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index d0ea7dccfbaf9..b18a90598213e 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -109,6 +109,20 @@ bool LifetimeAnnotations::isSourceDangle(const MemRegion *Source, ProgramStateRe
return true;
}
+void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ auto LBMapVal = State->get<LifetimeBoundMapVal>();
+ if (LBMapVal.isEmpty())
+ return;
+
+ for (auto&& [OriginRegion, SourceSet] : LBMapVal) {
+ for (const auto *Region : SourceSet) {
+ if (isSourceDangle(Region, State, C) == true)
+ return;
+ }
+ }
+}
+
void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
const char *NL, const char *Sep) const {
auto LBMap = State->get<LifetimeBoundMap>();
>From a9b3bcf132dd63176d5f3868c865357fb01687dd Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Fri, 12 Jun 2026 12:55:38 +0200
Subject: [PATCH 19/23] [analyzer] Debug prints for debugging checkEndFunction.
---
.../Checkers/LifetimeAnnotations.cpp | 37 +++++++++++++++----
1 file changed, 30 insertions(+), 7 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index b18a90598213e..8d4b7b0f0b2aa 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -95,30 +95,53 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
}
bool LifetimeAnnotations::isSourceDangle(const MemRegion *Source, ProgramStateRef State, CheckerContext &C) const {
- // Q1: Am I sure I need ProgramStateRef State as a parameter?
-
+ llvm::errs() << "isSourceDangle called";
if (const auto *StackSpace = Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
+ llvm::errs() << "isSourceDanle non null";
const StackFrame *SF = StackSpace->getStackFrame();
const StackFrame *CurrentSF = C.getStackFrame();
- if (SF == CurrentSF || SF->isParentOf(CurrentSF))
+ if (SF == CurrentSF || SF->isParentOf(CurrentSF)) {
+ llvm::errs() << "isSourceDangle about to fire";
return false;
+ }
return false;
}
- // Currently return false, but this has to be replaced when the source is a SymRegion instead of a MemRegion
+ // Currently return true, but this has to be replaced when the source is a SymRegion instead of a MemRegion
return true;
}
void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const {
ProgramStateRef State = C.getState();
+ auto LBMap = State->get<LifetimeBoundMap>();
auto LBMapVal = State->get<LifetimeBoundMapVal>();
- if (LBMapVal.isEmpty())
+
+ llvm::SmallString<128> Str;
+ llvm::raw_svector_ostream OS(Str);
+ ExplodedNode *N = C.generateErrorNode();
+
+ if (LBMapVal.isEmpty() && LBMapVal.isEmpty())
return;
+ for (auto&& [OriginSym, SourceSet] : LBMapVal) {
+ for (const auto *Region : SourceSet) {
+ if (isSourceDangle(Region, State, C)) {
+ OS << " Returning value bound to a local " << Region << " that will go out of scope.";
+ auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
+ C.emitReport(std::move(BR));
+ Str.clear();
+ }
+ }
+ }
+
for (auto&& [OriginRegion, SourceSet] : LBMapVal) {
for (const auto *Region : SourceSet) {
- if (isSourceDangle(Region, State, C) == true)
- return;
+ if (isSourceDangle(Region, State, C)) {
+ OS << " Returning value bound to a local " << Region << " that will go out of scope.";
+ auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
+ C.emitReport(std::move(BR));
+ Str.clear();
+ }
}
}
}
>From 06de87ed8af0cc9e8955dea0c8ecbd9678695cfe Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Fri, 12 Jun 2026 13:31:58 +0200
Subject: [PATCH 20/23] [analyzer] Removed duplicate call on the
LifetimeBoundMap.
---
.../Checkers/LifetimeAnnotations.cpp | 23 +++++++++++++------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 8d4b7b0f0b2aa..12d9b676589fb 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -62,6 +62,7 @@ ProgramStateRef LifetimeAnnotations::bindValues(ProgramStateRef State, SymbolRef
void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
+ llvm::errs() << "checkPostCall called" << "\n";
ProgramStateRef State = C.getState();
const auto *FC = dyn_cast<AnyFunctionCall>(&Call);
@@ -95,23 +96,23 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
}
bool LifetimeAnnotations::isSourceDangle(const MemRegion *Source, ProgramStateRef State, CheckerContext &C) const {
- llvm::errs() << "isSourceDangle called";
+ llvm::errs() << "isSourceDangle called" << "\n";
if (const auto *StackSpace = Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
- llvm::errs() << "isSourceDanle non null";
+ llvm::errs() << "isSourceDangle non null" << "\n";
const StackFrame *SF = StackSpace->getStackFrame();
const StackFrame *CurrentSF = C.getStackFrame();
if (SF == CurrentSF || SF->isParentOf(CurrentSF)) {
- llvm::errs() << "isSourceDangle about to fire";
+ llvm::errs() << "isSourceDangle about to fire" << "\n";
return false;
}
return false;
}
-
// Currently return true, but this has to be replaced when the source is a SymRegion instead of a MemRegion
return true;
}
void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const {
+ llvm::errs() << "checkEndFunction called." << "\n";
ProgramStateRef State = C.getState();
auto LBMap = State->get<LifetimeBoundMap>();
auto LBMapVal = State->get<LifetimeBoundMapVal>();
@@ -119,11 +120,14 @@ void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS, CheckerContext
llvm::SmallString<128> Str;
llvm::raw_svector_ostream OS(Str);
ExplodedNode *N = C.generateErrorNode();
-
- if (LBMapVal.isEmpty() && LBMapVal.isEmpty())
+ llvm::errs() << "maps are not empty before check" << "\n";
+ if (LBMap.isEmpty() && LBMapVal.isEmpty())
return;
+
+ llvm::errs() << "maps are not empty after null check" << "\n";
- for (auto&& [OriginSym, SourceSet] : LBMapVal) {
+ for (auto&& [OriginSym, SourceSet] : LBMap) {
+ llvm::errs() << "LBMapVal isEmpty: " << LBMap.isEmpty() << "\n";
for (const auto *Region : SourceSet) {
if (isSourceDangle(Region, State, C)) {
OS << " Returning value bound to a local " << Region << " that will go out of scope.";
@@ -131,10 +135,13 @@ void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS, CheckerContext
C.emitReport(std::move(BR));
Str.clear();
}
+ llvm::errs() << "Checking source: " << Region << " isDangling: "
+ << isSourceDangle(Region, State, C) << "\n";
}
}
for (auto&& [OriginRegion, SourceSet] : LBMapVal) {
+ llvm::errs() << "LBMapVal isEmpty: " << LBMapVal.isEmpty() << "\n";
for (const auto *Region : SourceSet) {
if (isSourceDangle(Region, State, C)) {
OS << " Returning value bound to a local " << Region << " that will go out of scope.";
@@ -142,6 +149,8 @@ void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS, CheckerContext
C.emitReport(std::move(BR));
Str.clear();
}
+ llvm::errs() << "Checking source: " << Region << " isDangling: "
+ << isSourceDangle(Region, State, C) << "\n";
}
}
}
>From 32904f192db9afb2189733434ef2450469204ed4 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Fri, 12 Jun 2026 20:54:33 +0200
Subject: [PATCH 21/23] [analyzer] Resolved teh type mismatch in looking up the
maps.
---
.../Checkers/LifetimeAnnotations.cpp | 58 ++++++++++---------
1 file changed, 32 insertions(+), 26 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 12d9b676589fb..4543f84e07dc8 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -96,38 +96,43 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
}
bool LifetimeAnnotations::isSourceDangle(const MemRegion *Source, ProgramStateRef State, CheckerContext &C) const {
- llvm::errs() << "isSourceDangle called" << "\n";
+ // This check works for checkEndFunction
if (const auto *StackSpace = Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
- llvm::errs() << "isSourceDangle non null" << "\n";
const StackFrame *SF = StackSpace->getStackFrame();
const StackFrame *CurrentSF = C.getStackFrame();
- if (SF == CurrentSF || SF->isParentOf(CurrentSF)) {
- llvm::errs() << "isSourceDangle about to fire" << "\n";
- return false;
- }
- return false;
+ if (SF == CurrentSF)
+ return true;
}
- // Currently return true, but this has to be replaced when the source is a SymRegion instead of a MemRegion
- return true;
+ // Currently return false, but this has to be replaced when the source is a SymRegion instead of a MemRegion
+ return false;
}
void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const {
- llvm::errs() << "checkEndFunction called." << "\n";
ProgramStateRef State = C.getState();
auto LBMap = State->get<LifetimeBoundMap>();
auto LBMapVal = State->get<LifetimeBoundMapVal>();
+
+ const Expr *RetExpr = RS->getRetValue();
+ if (!RetExpr)
+ return;
+
+ RetExpr = RetExpr->IgnoreParens();
+ SVal RetVal = C.getSVal(RetExpr);
+ const MemRegion *RetValRegion = RetVal.getAsRegion();
+ if (!RetValRegion)
+ return;
llvm::SmallString<128> Str;
llvm::raw_svector_ostream OS(Str);
- ExplodedNode *N = C.generateErrorNode();
- llvm::errs() << "maps are not empty before check" << "\n";
+
if (LBMap.isEmpty() && LBMapVal.isEmpty())
- return;
-
- llvm::errs() << "maps are not empty after null check" << "\n";
+ return;
for (auto&& [OriginSym, SourceSet] : LBMap) {
- llvm::errs() << "LBMapVal isEmpty: " << LBMap.isEmpty() << "\n";
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
+
for (const auto *Region : SourceSet) {
if (isSourceDangle(Region, State, C)) {
OS << " Returning value bound to a local " << Region << " that will go out of scope.";
@@ -135,22 +140,23 @@ void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS, CheckerContext
C.emitReport(std::move(BR));
Str.clear();
}
- llvm::errs() << "Checking source: " << Region << " isDangling: "
- << isSourceDangle(Region, State, C) << "\n";
}
}
for (auto&& [OriginRegion, SourceSet] : LBMapVal) {
- llvm::errs() << "LBMapVal isEmpty: " << LBMapVal.isEmpty() << "\n";
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
+
for (const auto *Region : SourceSet) {
- if (isSourceDangle(Region, State, C)) {
- OS << " Returning value bound to a local " << Region << " that will go out of scope.";
- auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
- C.emitReport(std::move(BR));
- Str.clear();
+ if (OriginRegion == RetValRegion) {
+ if (isSourceDangle(Region, State, C)) {
+ OS << " Returning value bound to a local " << Region << " that will go out of scope.";
+ auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
+ C.emitReport(std::move(BR));
+ Str.clear();
+ }
}
- llvm::errs() << "Checking source: " << Region << " isDangling: "
- << isSourceDangle(Region, State, C) << "\n";
}
}
}
>From 360e7634b2b9eff552d90037e28939f30dae4913 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Fri, 12 Jun 2026 22:02:46 +0200
Subject: [PATCH 22/23] [analyzer] Dangling detection works, but the code is
ugly, format it into helper functions before pushing to the PR.
---
.../Checkers/LifetimeAnnotations.cpp | 60 +++++++++++--------
1 file changed, 34 insertions(+), 26 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 4543f84e07dc8..c9ba3e15d5745 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -62,7 +62,6 @@ ProgramStateRef LifetimeAnnotations::bindValues(ProgramStateRef State, SymbolRef
void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
- llvm::errs() << "checkPostCall called" << "\n";
ProgramStateRef State = C.getState();
const auto *FC = dyn_cast<AnyFunctionCall>(&Call);
@@ -108,6 +107,9 @@ bool LifetimeAnnotations::isSourceDangle(const MemRegion *Source, ProgramStateRe
}
void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const {
+ if (!RS)
+ return;
+
ProgramStateRef State = C.getState();
auto LBMap = State->get<LifetimeBoundMap>();
auto LBMapVal = State->get<LifetimeBoundMapVal>();
@@ -118,9 +120,6 @@ void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS, CheckerContext
RetExpr = RetExpr->IgnoreParens();
SVal RetVal = C.getSVal(RetExpr);
- const MemRegion *RetValRegion = RetVal.getAsRegion();
- if (!RetValRegion)
- return;
llvm::SmallString<128> Str;
llvm::raw_svector_ostream OS(Str);
@@ -128,33 +127,42 @@ void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS, CheckerContext
if (LBMap.isEmpty() && LBMapVal.isEmpty())
return;
- for (auto&& [OriginSym, SourceSet] : LBMap) {
- ExplodedNode *N = C.generateNonFatalErrorNode();
- if (!N)
- return;
+ SymbolRef RetValSym = RetVal.getAsSymbol(/*IncludeBaseRegions=*/true);
+ const MemRegion *RetValRegion = RetVal.getAsRegion();
+ if (!RetValSym && !RetValRegion)
+ return;
- for (const auto *Region : SourceSet) {
- if (isSourceDangle(Region, State, C)) {
- OS << " Returning value bound to a local " << Region << " that will go out of scope.";
- auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
- C.emitReport(std::move(BR));
- Str.clear();
+ if (RetValSym) {
+ for (auto&& [OriginSym, SourceSet] : LBMap) {
+ for (const auto *Region : SourceSet) {
+ if (OriginSym == RetValSym) {
+ if (isSourceDangle(Region, State, C)) {
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
+ OS << " Returning value bound to a local " << Region << " that will go out of scope.";
+ auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
+ C.emitReport(std::move(BR));
+ Str.clear();
+ }
+ }
}
}
}
- for (auto&& [OriginRegion, SourceSet] : LBMapVal) {
- ExplodedNode *N = C.generateNonFatalErrorNode();
- if (!N)
- return;
-
- for (const auto *Region : SourceSet) {
- if (OriginRegion == RetValRegion) {
- if (isSourceDangle(Region, State, C)) {
- OS << " Returning value bound to a local " << Region << " that will go out of scope.";
- auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
- C.emitReport(std::move(BR));
- Str.clear();
+ if (RetValRegion) {
+ for (auto&& [OriginRegion, SourceSet] : LBMapVal) {
+ for (const auto *Region : SourceSet) {
+ if (OriginRegion == RetValRegion) {
+ if (isSourceDangle(Region, State, C)) {
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
+ OS << " Returning value bound to a local " << Region << " that will go out of scope.";
+ auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
+ C.emitReport(std::move(BR));
+ Str.clear();
+ }
}
}
}
>From 0c7611d41135c2213c71c5b9c4c6749b5035734e Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Sat, 13 Jun 2026 01:47:01 +0200
Subject: [PATCH 23/23] [analyzer] Core implementation of the checkEndFunction
done.
---
.../Checkers/LifetimeAnnotations.cpp | 130 ++++++++++--------
1 file changed, 74 insertions(+), 56 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index c9ba3e15d5745..edc9bb2253287 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -12,10 +12,12 @@ using namespace ento;
REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SymbolRef, LifetimeSourceSet)
-REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMapVal, const MemRegion *, LifetimeSourceSet)
+REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMapVal, const MemRegion *,
+ LifetimeSourceSet)
namespace {
-class LifetimeAnnotations : public Checker<check::PostCall, check::EndFunction, eval::Call> {
+class LifetimeAnnotations
+ : public Checker<check::PostCall, check::EndFunction, eval::Call> {
public:
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
@@ -23,8 +25,17 @@ class LifetimeAnnotations : public Checker<check::PostCall, check::EndFunction,
bool evalCall(const CallEvent &Call, CheckerContext &C) const;
void analyzerLifetimeBound(const CallEvent &Call, const CallExpr *,
CheckerContext &C) const;
- ProgramStateRef bindValues(ProgramStateRef State, SymbolRef RetValSym, SVal RetVal, const MemRegion *Source) const;
- bool isSourceDangle(const MemRegion *Source, ProgramStateRef State, CheckerContext &C) const;
+ ProgramStateRef bindValues(ProgramStateRef State, SymbolRef RetValSym,
+ SVal RetVal, const MemRegion *Source) const;
+ bool isSourceDangle(const MemRegion *Source, ProgramStateRef State,
+ CheckerContext &C) const;
+ void reportDanglingSource(const MemRegion *Region, ExplodedNode *N,
+ CheckerContext &C) const;
+
+ template <typename MapTy, typename KeyTy>
+ void checkReturnedBorrower(const MapTy &Map, const KeyTy RetKey,
+ ProgramStateRef State, ExplodedNode *N,
+ CheckerContext &C) const;
void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"};
@@ -41,17 +52,21 @@ class LifetimeAnnotations : public Checker<check::PostCall, check::EndFunction,
} // namespace
-ProgramStateRef LifetimeAnnotations::bindValues(ProgramStateRef State, SymbolRef RetValSym, SVal RetVal, const MemRegion *Source) const {
- LifetimeSourceSet::Factory &F = State->getStateManager().get_context<LifetimeSourceSet>();
+ProgramStateRef LifetimeAnnotations::bindValues(ProgramStateRef State,
+ SymbolRef RetValSym,
+ SVal RetVal,
+ const MemRegion *Source) const {
+ LifetimeSourceSet::Factory &F =
+ State->getStateManager().get_context<LifetimeSourceSet>();
if (RetValSym) {
const LifetimeSourceSet *LBSet = State->get<LifetimeBoundMap>(RetValSym);
LifetimeSourceSet Set = LBSet ? *LBSet : F.getEmptySet();
Set = F.add(Set, Source);
State = State->set<LifetimeBoundMap>(RetValSym, Set);
- }
- else if (const MemRegion *RetValRegion = RetVal.getAsRegion()) {
- const LifetimeSourceSet *LBValSet = State->get<LifetimeBoundMapVal>(RetValRegion);
+ } else if (const MemRegion *RetValRegion = RetVal.getAsRegion()) {
+ const LifetimeSourceSet *LBValSet =
+ State->get<LifetimeBoundMapVal>(RetValRegion);
LifetimeSourceSet Set = LBValSet ? *LBValSet : F.getEmptySet();
Set = F.add(Set, Source);
State = State->set<LifetimeBoundMapVal>(RetValRegion, Set);
@@ -59,7 +74,6 @@ ProgramStateRef LifetimeAnnotations::bindValues(ProgramStateRef State, SymbolRef
return State;
}
-
void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
@@ -81,8 +95,8 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
SVal Arg = Call.getArgSVal(Idx);
if (const MemRegion *ArgValRegion = Arg.getAsRegion())
State = bindValues(State, RetValSym, RetVal, ArgValRegion);
- }
}
+ }
if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
if (clang::lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
@@ -94,26 +108,47 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
C.addTransition(State);
}
-bool LifetimeAnnotations::isSourceDangle(const MemRegion *Source, ProgramStateRef State, CheckerContext &C) const {
- // This check works for checkEndFunction
- if (const auto *StackSpace = Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
+bool LifetimeAnnotations::isSourceDangle(const MemRegion *Source,
+ ProgramStateRef State,
+ CheckerContext &C) const {
+ // FIXME: Currently the checker only focuses on stack MemRegions only since
+ // that is the scope of week 3. Sources without a stack region are not
+ // covered, but should be implemented as well next step.
+ if (const auto *StackSpace =
+ Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
const StackFrame *SF = StackSpace->getStackFrame();
const StackFrame *CurrentSF = C.getStackFrame();
- if (SF == CurrentSF)
+ if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
return true;
}
- // Currently return false, but this has to be replaced when the source is a SymRegion instead of a MemRegion
return false;
}
-void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const {
+template <typename MapTy, typename KeyTy>
+void LifetimeAnnotations::checkReturnedBorrower(const MapTy &Map,
+ const KeyTy RetKey,
+ ProgramStateRef State,
+ ExplodedNode *N,
+ CheckerContext &C) const {
+ for (auto &&[Origin, SourceSet] : Map) {
+ if (Origin == RetKey) {
+ for (const MemRegion *Region : SourceSet) {
+ if (isSourceDangle(Region, State, C))
+ reportDanglingSource(Region, N, C);
+ }
+ }
+ }
+}
+
+void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS,
+ CheckerContext &C) const {
if (!RS)
return;
ProgramStateRef State = C.getState();
auto LBMap = State->get<LifetimeBoundMap>();
auto LBMapVal = State->get<LifetimeBoundMapVal>();
-
+
const Expr *RetExpr = RS->getRetValue();
if (!RetExpr)
return;
@@ -121,52 +156,35 @@ void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS, CheckerContext
RetExpr = RetExpr->IgnoreParens();
SVal RetVal = C.getSVal(RetExpr);
- llvm::SmallString<128> Str;
- llvm::raw_svector_ostream OS(Str);
-
if (LBMap.isEmpty() && LBMapVal.isEmpty())
- return;
+ return;
SymbolRef RetValSym = RetVal.getAsSymbol(/*IncludeBaseRegions=*/true);
const MemRegion *RetValRegion = RetVal.getAsRegion();
if (!RetValSym && !RetValRegion)
return;
- if (RetValSym) {
- for (auto&& [OriginSym, SourceSet] : LBMap) {
- for (const auto *Region : SourceSet) {
- if (OriginSym == RetValSym) {
- if (isSourceDangle(Region, State, C)) {
- ExplodedNode *N = C.generateNonFatalErrorNode();
- if (!N)
- return;
- OS << " Returning value bound to a local " << Region << " that will go out of scope.";
- auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
- C.emitReport(std::move(BR));
- Str.clear();
- }
- }
- }
- }
- }
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
- if (RetValRegion) {
- for (auto&& [OriginRegion, SourceSet] : LBMapVal) {
- for (const auto *Region : SourceSet) {
- if (OriginRegion == RetValRegion) {
- if (isSourceDangle(Region, State, C)) {
- ExplodedNode *N = C.generateNonFatalErrorNode();
- if (!N)
- return;
- OS << " Returning value bound to a local " << Region << " that will go out of scope.";
- auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
- C.emitReport(std::move(BR));
- Str.clear();
- }
- }
- }
- }
- }
+ if (RetValSym)
+ checkReturnedBorrower(LBMap, RetValSym, State, N, C);
+
+ if (RetValRegion)
+ checkReturnedBorrower(LBMapVal, RetValRegion, State, N, C);
+}
+
+void LifetimeAnnotations::reportDanglingSource(const MemRegion *Region,
+ ExplodedNode *N,
+ CheckerContext &C) const {
+ llvm::SmallString<128> Str;
+ llvm::raw_svector_ostream OS(Str);
+
+ OS << " Returning value bound to a local " << Region
+ << " that will go out of scope";
+ auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
+ C.emitReport(std::move(BR));
}
void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
More information about the cfe-commits
mailing list