[clang] [llvm] [analyzer] Implemented a base of detecing lifetimebound annotation (PR #200145)
Benedek Kaibas via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 22 10:46:46 PDT 2026
https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/200145
>From ddb2376d5978755ed7a45ce362d1cbaba6efe416 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/38] 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 366c901261ff3b734d07d5399657d3d41f9e5d1a 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/38] 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 9485add27440249983376c89fc3933d8d75a6c32 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/38] 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 f68a1d5b83cb2ed761a7f6e5c9f8d2f82b61ee20 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/38] 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 674c40e3669705a2a796508d520db3526a947eae 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/38] 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 dc7c4fe09ef95e6b6b7bea294043a8a859f3c480 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/38] 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 35d12f2af1e91d33cb4746cc58f1b7c9ad607d50 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/38] 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 756f705ae9cc641da9d26dac00e65c6266b4b701 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/38] 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 927a7401e439f3e5a6370e2c7ac5d9b6522dceb0 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/38] 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 d3b9a6c1d46d515e5a69b9abb85167328c10443f 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/38] 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 a286dd32765c4bfe9e357e472ccc6f23226ed72a 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/38] 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 9a41d92a1dc6a002281bcdb99d97fe0618d4fec9 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/38] [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 8fc9ed72f56ff6732bd53381f9bc7012d5a16d16 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/38] [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 af61426727214c2f5865c602b995cd3204e086ab 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/38] [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 8dccd0b3a9c4c96cccc809d42e9361c578dad9fa 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/38] [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 672ad56fcac843b17dfd27cc560bf14af7ddc476 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/38] [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 b018123507ea31f237d5693ea4f022639e967cec 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/38] [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 f156db2c0769710a14ed55cc51951dba35ff9a4c 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/38] [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 a65e419de9d71173632e46a1e5b46115475bae2b 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/38] [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 c4643ef6071ea1c3593267ff8807d34aa31081ed 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/38] [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 d443b306328b7dfe24dd05a4dc271da608843833 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/38] [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 073b313f3706b0c4d253a2f8e949cf57452bc528 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/38] [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 394e136e30de2fe4acc2e0e149a74f128f7eafe6 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/38] [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,
>From 7393b3c3e9bd741fa4fca11a85e25bd3f0733f7a Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Mon, 15 Jun 2026 18:26:35 +0200
Subject: [PATCH 24/38] [analyzer] Implemented checkDeadSymbols to clean up the
program state.
---
.../Checkers/LifetimeAnnotations.cpp | 31 +++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index edc9bb2253287..ae73823595633 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -37,6 +37,7 @@ class LifetimeAnnotations
ProgramStateRef State, ExplodedNode *N,
CheckerContext &C) const;
void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
+ void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"};
@@ -181,12 +182,38 @@ void LifetimeAnnotations::reportDanglingSource(const MemRegion *Region,
llvm::SmallString<128> Str;
llvm::raw_svector_ostream OS(Str);
- OS << " Returning value bound to a local " << Region
- << " that will go out of scope";
+ 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::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ // Get both maps since both of them needs to be cleaned up
+ LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
+ LifetimeBoundMapValTy LBMapVal = State->get<LifetimeBoundMapVal>();
+
+
+ for (auto &Entry : LBMap) {
+ const SymExpr *Sym = Entry.first;
+ bool IsSymbolLive = SymReaper.isLive(Sym);
+
+ if (!IsSymbolLive)
+ State = State->remove<LifetimeBoundMap>(Sym);
+ }
+
+ for(auto &Entry: LBMapVal) {
+ const MemRegion *Region = Entry.first;
+ bool IsSymbolLive = SymReaper.isLiveRegion(Region);
+
+ if (!IsSymbolLive)
+ State = State->remove<LifetimeBoundMapVal>(Region);
+ }
+ C.addTransition(State);
+}
+
void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
const char *NL, const char *Sep) const {
auto LBMap = State->get<LifetimeBoundMap>();
>From 9ac12179119b835d3b5a8795c66ab6bc97394c96 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Mon, 15 Jun 2026 19:07:04 +0200
Subject: [PATCH 25/38] [analyzer] Implemented checkDeadSymbols.
---
.../Checkers/LifetimeAnnotations.cpp | 38 +++++++++----------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index ae73823595633..6a6c228e72d69 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -23,8 +23,7 @@ class LifetimeAnnotations
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, CheckerContext &C) const;
ProgramStateRef bindValues(ProgramStateRef State, SymbolRef RetValSym,
SVal RetVal, const MemRegion *Source) const;
bool isSourceDangle(const MemRegion *Source, ProgramStateRef State,
@@ -41,9 +40,7 @@ class LifetimeAnnotations
const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"};
- using FnCheck = void (LifetimeAnnotations::*)(const CallEvent &Call,
- const CallExpr *,
- CheckerContext &C) const;
+ using FnCheck = void (LifetimeAnnotations::*)(const CallEvent &Call, CheckerContext &C) const;
const CallDescriptionMap<FnCheck> Callbacks = {
{{CDM::SimpleFunc, {"clang_analyzer_lifetime_bound"}},
@@ -100,7 +97,7 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
}
if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
- if (clang::lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
+ if (lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) {
State = bindValues(State, RetValSym, RetVal, AttrRegion);
}
@@ -150,6 +147,9 @@ void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS,
auto LBMap = State->get<LifetimeBoundMap>();
auto LBMapVal = State->get<LifetimeBoundMapVal>();
+ if (LBMap.isEmpty() && LBMapVal.isEmpty())
+ return;
+
const Expr *RetExpr = RS->getRetValue();
if (!RetExpr)
return;
@@ -157,9 +157,6 @@ void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS,
RetExpr = RetExpr->IgnoreParens();
SVal RetVal = C.getSVal(RetExpr);
- if (LBMap.isEmpty() && LBMapVal.isEmpty())
- return;
-
SymbolRef RetValSym = RetVal.getAsSymbol(/*IncludeBaseRegions=*/true);
const MemRegion *RetValRegion = RetVal.getAsRegion();
if (!RetValSym && !RetValRegion)
@@ -194,24 +191,29 @@ void LifetimeAnnotations::checkDeadSymbols(SymbolReaper &SymReaper, CheckerConte
// Get both maps since both of them needs to be cleaned up
LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
LifetimeBoundMapValTy LBMapVal = State->get<LifetimeBoundMapVal>();
-
+ bool StateChanged = false;
for (auto &Entry : LBMap) {
const SymExpr *Sym = Entry.first;
bool IsSymbolLive = SymReaper.isLive(Sym);
- if (!IsSymbolLive)
+ if (!IsSymbolLive) {
State = State->remove<LifetimeBoundMap>(Sym);
+ StateChanged = true;
+ }
}
for(auto &Entry: LBMapVal) {
const MemRegion *Region = Entry.first;
- bool IsSymbolLive = SymReaper.isLiveRegion(Region);
+ bool IsRegionLive = SymReaper.isLiveRegion(Region);
- if (!IsSymbolLive)
+ if (!IsRegionLive) {
State = State->remove<LifetimeBoundMapVal>(Region);
+ StateChanged = true;
+ }
}
- C.addTransition(State);
+ if (StateChanged)
+ C.addTransition(State);
}
void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
@@ -244,16 +246,14 @@ bool LifetimeAnnotations::evalCall(const CallEvent &Call,
if (!Handler)
return false;
- (this->*(*Handler))(Call, CE, C);
+ (this->*(*Handler))(Call, C);
return true;
}
-void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call,
- const CallExpr *CE,
- CheckerContext &C) const {
+void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, CheckerContext &C) const {
ProgramStateRef State = C.getState();
- unsigned int ArgCount = CE->getNumArgs();
+ unsigned int ArgCount = Call.getNumArgs();
if (ArgCount != 1)
return;
>From 029c4476220419d6f3ebf2ea2b4c0ca3dcf54fb8 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Mon, 15 Jun 2026 23:28:33 +0200
Subject: [PATCH 26/38] [analyzer] Error message formatted correctly.
---
clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp | 2 +-
clang/test/Analysis/lifetime-bound.cpp | 6 ++++++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 6a6c228e72d69..030bbdb3281d9 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -180,7 +180,7 @@ void LifetimeAnnotations::reportDanglingSource(const MemRegion *Region,
llvm::raw_svector_ostream OS(Str);
OS << "Returning value bound to a local " << Region
- << "that will go out of scope";
+ << " that will go out of scope";
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 f9389c88854e9..8a4e069123b12 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -131,3 +131,9 @@ void caller_view() {
// FIXME: Currently none of the maps cover LazyCompoundVal
clang_analyzer_lifetime_bound(w); // no-warning
}
+
+
+
+// These are the test cases for testing the correctness of the emitted warning from the LifetimeAnnotations checker.
+
+
>From 232ab3b677a166cf49fea2e2452afe721557e7e1 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Tue, 16 Jun 2026 00:53:09 +0200
Subject: [PATCH 27/38] [analyzer] Added test cases for the checker.
---
.../Checkers/LifetimeAnnotations.cpp | 11 +++++----
clang/test/Analysis/lifetime-bound.cpp | 24 +++++++++++++++++++
2 files changed, 31 insertions(+), 4 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 030bbdb3281d9..fbfc4f41eae0f 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -40,7 +40,8 @@ class LifetimeAnnotations
const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"};
- using FnCheck = void (LifetimeAnnotations::*)(const CallEvent &Call, CheckerContext &C) const;
+ using FnCheck = void (LifetimeAnnotations::*)(const CallEvent &Call,
+ CheckerContext &C) const;
const CallDescriptionMap<FnCheck> Callbacks = {
{{CDM::SimpleFunc, {"clang_analyzer_lifetime_bound"}},
@@ -185,7 +186,8 @@ void LifetimeAnnotations::reportDanglingSource(const MemRegion *Region,
C.emitReport(std::move(BR));
}
-void LifetimeAnnotations::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const {
+void LifetimeAnnotations::checkDeadSymbols(SymbolReaper &SymReaper,
+ CheckerContext &C) const {
ProgramStateRef State = C.getState();
// Get both maps since both of them needs to be cleaned up
@@ -203,7 +205,7 @@ void LifetimeAnnotations::checkDeadSymbols(SymbolReaper &SymReaper, CheckerConte
}
}
- for(auto &Entry: LBMapVal) {
+ for (auto &Entry : LBMapVal) {
const MemRegion *Region = Entry.first;
bool IsRegionLive = SymReaper.isLiveRegion(Region);
@@ -250,7 +252,8 @@ bool LifetimeAnnotations::evalCall(const CallEvent &Call,
return true;
}
-void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, CheckerContext &C) const {
+void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call,
+ CheckerContext &C) const {
ProgramStateRef State = C.getState();
unsigned int ArgCount = Call.getNumArgs();
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index 8a4e069123b12..ef03f78b3e228 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -136,4 +136,28 @@ void caller_view() {
// These are the test cases for testing the correctness of the emitted warning from the LifetimeAnnotations checker.
+int *test_func(int *p [[clang::lifetimebound]]);
+
+int *direct_return() {
+ int i = 5;
+ return test_func(&i);
+ // expected-warning at -1 {{Returning value bound to a local i that will go out of scope}}
+ // expected-warning at -2 {{address of stack memory associated with local variable 'i' returned}}
+}
+
+int *variable_return() {
+ int y = 5;
+ int *p = test_func(&y);
+ return p; // expected-warning {{Returning value bound to a local y that will go out of scope}}
+}
+
+int *borrow_from_caller(int *b [[clang::lifetimebound]]) {
+ return test_func(b); // no-warning
+}
+
+void no_return() {
+ int i = 5;
+ int *p = test_func(&i);
+ (void)p; // no-warning
+}
>From 291ec2618d6a6f054dfc9f16670129e5f42d0aee Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Tue, 16 Jun 2026 12:43:58 +0200
Subject: [PATCH 28/38] [analyzer] Removed SymbolRef from bindValues.
---
.../StaticAnalyzer/Checkers/LifetimeAnnotations.cpp | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index fbfc4f41eae0f..4e622e7829d60 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -24,8 +24,7 @@ class LifetimeAnnotations
const char *Sep) const override;
bool evalCall(const CallEvent &Call, CheckerContext &C) const;
void analyzerLifetimeBound(const CallEvent &Call, CheckerContext &C) const;
- ProgramStateRef bindValues(ProgramStateRef State, SymbolRef RetValSym,
- SVal RetVal, const MemRegion *Source) const;
+ ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal, const MemRegion *Source) const;
bool isSourceDangle(const MemRegion *Source, ProgramStateRef State,
CheckerContext &C) const;
void reportDanglingSource(const MemRegion *Region, ExplodedNode *N,
@@ -52,13 +51,12 @@ class LifetimeAnnotations
} // namespace
ProgramStateRef LifetimeAnnotations::bindValues(ProgramStateRef State,
- SymbolRef RetValSym,
SVal RetVal,
const MemRegion *Source) const {
LifetimeSourceSet::Factory &F =
State->getStateManager().get_context<LifetimeSourceSet>();
- if (RetValSym) {
+ if (SymbolRef RetValSym = RetVal.getAsSymbol(/*IncludeBaseRegions=*/true)) {
const LifetimeSourceSet *LBSet = State->get<LifetimeBoundMap>(RetValSym);
LifetimeSourceSet Set = LBSet ? *LBSet : F.getEmptySet();
Set = F.add(Set, Source);
@@ -86,21 +84,20 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
return;
SVal RetVal = Call.getReturnValue();
- SymbolRef RetValSym = RetVal.getAsSymbol(/*IncludeBaseRegions=*/true);
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())
- State = bindValues(State, RetValSym, RetVal, ArgValRegion);
+ State = bindValues(State, RetVal, ArgValRegion);
}
}
if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
if (lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) {
- State = bindValues(State, RetValSym, RetVal, AttrRegion);
+ State = bindValues(State, RetVal, AttrRegion);
}
}
}
>From 6289948e439ad607323cc63420effa771a4e7cbc Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Tue, 16 Jun 2026 13:47:36 +0200
Subject: [PATCH 29/38] [analyzer] Nits applied.
---
.../StaticAnalyzer/Checkers/LifetimeAnnotations.cpp | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 4e622e7829d60..7ad4abf702bb9 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -25,7 +25,7 @@ class LifetimeAnnotations
bool evalCall(const CallEvent &Call, CheckerContext &C) const;
void analyzerLifetimeBound(const CallEvent &Call, CheckerContext &C) const;
ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal, const MemRegion *Source) const;
- bool isSourceDangle(const MemRegion *Source, ProgramStateRef State,
+ bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State,
CheckerContext &C) const;
void reportDanglingSource(const MemRegion *Region, ExplodedNode *N,
CheckerContext &C) const;
@@ -104,7 +104,7 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
C.addTransition(State);
}
-bool LifetimeAnnotations::isSourceDangle(const MemRegion *Source,
+bool LifetimeAnnotations::hasDanglingSource(const MemRegion *Source,
ProgramStateRef State,
CheckerContext &C) const {
// FIXME: Currently the checker only focuses on stack MemRegions only since
@@ -129,7 +129,7 @@ void LifetimeAnnotations::checkReturnedBorrower(const MapTy &Map,
for (auto &&[Origin, SourceSet] : Map) {
if (Origin == RetKey) {
for (const MemRegion *Region : SourceSet) {
- if (isSourceDangle(Region, State, C))
+ if (hasDanglingSource(Region, State, C))
reportDanglingSource(Region, N, C);
}
}
@@ -194,9 +194,8 @@ void LifetimeAnnotations::checkDeadSymbols(SymbolReaper &SymReaper,
for (auto &Entry : LBMap) {
const SymExpr *Sym = Entry.first;
- bool IsSymbolLive = SymReaper.isLive(Sym);
- if (!IsSymbolLive) {
+ if (!SymReaper.isLive(Sym)) {
State = State->remove<LifetimeBoundMap>(Sym);
StateChanged = true;
}
@@ -204,9 +203,8 @@ void LifetimeAnnotations::checkDeadSymbols(SymbolReaper &SymReaper,
for (auto &Entry : LBMapVal) {
const MemRegion *Region = Entry.first;
- bool IsRegionLive = SymReaper.isLiveRegion(Region);
- if (!IsRegionLive) {
+ if (!SymReaper.isLiveRegion(Region)) {
State = State->remove<LifetimeBoundMapVal>(Region);
StateChanged = true;
}
>From 1e780d08cdca36eca738f57c610142bb8a4ae343 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Wed, 17 Jun 2026 16:39:41 +0200
Subject: [PATCH 30/38] [analyzer] Implemented out-of-scope dangling ptr
dereference detection, but many false positives on existing lit test.
---
FETCH_HEAD | 0
.../clang/StaticAnalyzer/Checkers/Checkers.td | 4 +
.../Checkers/LifetimeAnnotations.cpp | 115 ++++++++++++------
clang/test/Analysis/lifetime-bound.cpp | 23 +++-
4 files changed, 101 insertions(+), 41 deletions(-)
create mode 100644 FETCH_HEAD
diff --git a/FETCH_HEAD b/FETCH_HEAD
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 85d59fc139728..432970f8d6c35 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -1580,6 +1580,10 @@ def CheckerDocumentationChecker : Checker<"CheckerDocumentation">,
HelpText<"Defines an empty checker callback for all possible handlers.">,
Documentation<NotDocumented>;
+def DebugLifetimeAnnotations : Checker<"DebugLifetimeAnnotations">,
+ HelpText<"Prints the bindings recorded by the LifetimeAnnotations checker. Use with clang_analyzer_lifetime_bound().">,
+ Documentation<NotDocumented>;
+
} // end "debug"
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 7ad4abf702bb9..e6e22d2aa0660 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -17,35 +17,27 @@ REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMapVal, const MemRegion *,
namespace {
class LifetimeAnnotations
- : public Checker<check::PostCall, check::EndFunction, eval::Call> {
+ : public Checker<check::PostCall, check::EndFunction, check::LifetimeEnd> {
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, CheckerContext &C) const;
ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal, const MemRegion *Source) const;
bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State,
- CheckerContext &C) const;
+ CheckerContext &C) const;
void reportDanglingSource(const MemRegion *Region, ExplodedNode *N,
CheckerContext &C) const;
+ void reportUseAfterScope(const VarDecl *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;
+ void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"};
-
- using FnCheck = void (LifetimeAnnotations::*)(const CallEvent &Call,
- CheckerContext &C) const;
-
- const CallDescriptionMap<FnCheck> Callbacks = {
- {{CDM::SimpleFunc, {"clang_analyzer_lifetime_bound"}},
- &LifetimeAnnotations::analyzerLifetimeBound},
- };
};
} // namespace
@@ -105,8 +97,8 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
}
bool LifetimeAnnotations::hasDanglingSource(const MemRegion *Source,
- ProgramStateRef State,
- CheckerContext &C) const {
+ 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.
@@ -171,15 +163,52 @@ void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS,
checkReturnedBorrower(LBMapVal, RetValRegion, State, N, C);
}
+void LifetimeAnnotations::checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const {
+ if (!VD)
+ return;
+
+ ProgramStateRef State = C.getState();
+
+ auto LBMap = State->get<LifetimeBoundMap>();
+ auto LBMapVal = State->get<LifetimeBoundMapVal>();
+
+ if (LBMap.isEmpty() && LBMapVal.isEmpty())
+ return;
+
+ for (auto &&[Origin, SourceSet] : LBMapVal) {
+ for (const MemRegion *Region : SourceSet) {
+ if (const VarDecl *VDRegion = dyn_cast_if_present<VarRegion>(Region)->getDecl()) {
+ if (VDRegion == VD) {
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ reportUseAfterScope(VDRegion, N, C);
+ }
+ }
+ }
+ }
+
+ for (auto &&[Origin, SourceSet] : LBMap) {
+ for (const MemRegion *Region : SourceSet) {
+ if (const VarDecl *Variable = dyn_cast_if_present<VarRegion>(Region)->getDecl()) {
+ if (Variable == VD) {
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ reportUseAfterScope(Variable, N, C);
+ }
+ }
+ }
+ }
+}
+
void LifetimeAnnotations::reportDanglingSource(const MemRegion *Region,
ExplodedNode *N,
CheckerContext &C) const {
- llvm::SmallString<128> Str;
- llvm::raw_svector_ostream OS(Str);
+ std::string ErrorMessage = (llvm::Twine("Returning value bound to a local '") + Region->getString() + "' that will go out of scope").str();
+ auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, ErrorMessage, N);
+ C.emitReport(std::move(BR));
+}
- OS << "Returning value bound to a local " << Region
- << " that will go out of scope";
- auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
+void LifetimeAnnotations::reportUseAfterScope(const VarDecl *Region, ExplodedNode *N, CheckerContext &C) const {
+ std::string ErrorMessage = (llvm::Twine("Used variable after its scope ended '") + Region->getNameAsString() + "' error.").str();
+ auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, ErrorMessage, N);
C.emitReport(std::move(BR));
}
@@ -190,26 +219,17 @@ void LifetimeAnnotations::checkDeadSymbols(SymbolReaper &SymReaper,
// Get both maps since both of them needs to be cleaned up
LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
LifetimeBoundMapValTy LBMapVal = State->get<LifetimeBoundMapVal>();
- bool StateChanged = false;
- for (auto &Entry : LBMap) {
- const SymExpr *Sym = Entry.first;
-
- if (!SymReaper.isLive(Sym)) {
+ for (const SymExpr *Sym : llvm::make_first_range(LBMap)) {
+ if (!SymReaper.isLive(Sym))
State = State->remove<LifetimeBoundMap>(Sym);
- StateChanged = true;
- }
}
- for (auto &Entry : LBMapVal) {
- const MemRegion *Region = Entry.first;
-
- if (!SymReaper.isLiveRegion(Region)) {
+ for (const MemRegion *Region : llvm::make_first_range(LBMapVal)) {
+ if (!SymReaper.isLiveRegion(Region))
State = State->remove<LifetimeBoundMapVal>(Region);
- StateChanged = true;
- }
}
- if (StateChanged)
+ if (State != C.getState())
C.addTransition(State);
}
@@ -232,7 +252,24 @@ void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
}
}
-bool LifetimeAnnotations::evalCall(const CallEvent &Call,
+namespace {
+class DebugLifetimeAnnotations : public Checker<eval::Call> {
+public:
+ bool evalCall(const CallEvent &Call, CheckerContext &C) const;
+ void analyzerLifetimeBound(const CallEvent &Call, CheckerContext &C) const;
+
+ const BugType BugMsg{this, "DebugLifetimeAnnotations", "DebugLifetimeBound"};
+ using FnCheck = void (DebugLifetimeAnnotations::*)(const CallEvent &Call, CheckerContext &C) const;
+
+ const CallDescriptionMap<FnCheck> Callbacks = {
+ {{CDM::SimpleFunc, {"clang_analyzer_lifetime_bound"}},
+ &DebugLifetimeAnnotations::analyzerLifetimeBound},
+ };
+};
+
+} // namespace
+
+bool DebugLifetimeAnnotations::evalCall(const CallEvent &Call,
CheckerContext &C) const {
const auto *CE = dyn_cast_if_present<CallExpr>(Call.getOriginExpr());
@@ -247,7 +284,7 @@ bool LifetimeAnnotations::evalCall(const CallEvent &Call,
return true;
}
-void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call,
+void DebugLifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
@@ -296,3 +333,11 @@ void ento::registerLifetimeAnnotations(CheckerManager &mgr) {
bool ento::shouldRegisterLifetimeAnnotations(const CheckerManager &mgr) {
return true;
}
+
+void ento::registerDebugLifetimeAnnotations(CheckerManager &mgr) {
+ mgr.registerChecker<DebugLifetimeAnnotations>();
+}
+
+bool ento::shouldRegisterDebugLifetimeAnnotations(const CheckerManager &mgr) {
+ return true;
+}
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index ef03f78b3e228..8f1d30b615a02 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -1,7 +1,7 @@
-// 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
+// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.cplusplus.LifetimeAnnotations,debug.DebugLifetimeAnnotations \
+// RUN: -analyzer-config cfg-lifetime=true -verify %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.cplusplus.LifetimeAnnotations,debug.DebugLifetimeAnnotations \
+// RUN: -analyzer-config c++-container-inlining=false -analyzer-config cfg-lifetime=true -verify %s
struct A {};
@@ -136,20 +136,21 @@ void caller_view() {
// These are the test cases for testing the correctness of the emitted warning from the LifetimeAnnotations checker.
+// Return value bound to annotated param cases
int *test_func(int *p [[clang::lifetimebound]]);
int *direct_return() {
int i = 5;
return test_func(&i);
- // expected-warning at -1 {{Returning value bound to a local i that will go out of scope}}
+ // expected-warning at -1 {{Returning value bound to a local 'i' that will go out of scope}}
// expected-warning at -2 {{address of stack memory associated with local variable 'i' returned}}
}
int *variable_return() {
int y = 5;
int *p = test_func(&y);
- return p; // expected-warning {{Returning value bound to a local y that will go out of scope}}
+ return p; // expected-warning {{Returning value bound to a local 'y' that will go out of scope}}
}
int *borrow_from_caller(int *b [[clang::lifetimebound]]) {
@@ -161,3 +162,13 @@ void no_return() {
int *p = test_func(&i);
(void)p; // no-warning
}
+
+// Use-after-scope dangling pointer dereference
+void caller_ten() {
+ int* p = nullptr;
+ {
+ int x = 1;
+ p = test_func(&x);
+ } // expected-warning {{Used variable after its scope ended 'n' error}}
+ *p = 2;
+}
>From 10d4204c05d5a0b71491e67f648eae7d8c797fc9 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Wed, 17 Jun 2026 16:55:31 +0200
Subject: [PATCH 31/38] [analyzer] Detect use-after-scope lit test warning
message corrected, but false positives still present.
---
clang/test/Analysis/lifetime-bound.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index 8f1d30b615a02..529a5dbf34d70 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -166,9 +166,9 @@ void no_return() {
// Use-after-scope dangling pointer dereference
void caller_ten() {
int* p = nullptr;
- {
+ { // expected-warning {{Used variable after its scope ended 'x' error}}
int x = 1;
p = test_func(&x);
- } // expected-warning {{Used variable after its scope ended 'n' error}}
+ }
*p = 2;
}
>From 65be9c24664a28b69640afc09f36836de194b2b7 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Wed, 17 Jun 2026 22:57:27 +0200
Subject: [PATCH 32/38] [analyzer] DeteRemoved helper since stack region does
not help for this problem.
---
clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index e6e22d2aa0660..11f967b3cd39b 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -188,10 +188,10 @@ void LifetimeAnnotations::checkLifetimeEnd(const VarDecl *VD, CheckerContext &C)
for (auto &&[Origin, SourceSet] : LBMap) {
for (const MemRegion *Region : SourceSet) {
- if (const VarDecl *Variable = dyn_cast_if_present<VarRegion>(Region)->getDecl()) {
- if (Variable == VD) {
+ if (const VarDecl *VDRegion = dyn_cast_if_present<VarRegion>(Region)->getDecl()) {
+ if (VDRegion == VD) {
ExplodedNode *N = C.generateNonFatalErrorNode();
- reportUseAfterScope(Variable, N, C);
+ reportUseAfterScope(VDRegion, N, C);
}
}
}
>From 70c4f7d94eca7b5e75568ce2581cf8f458bd3484 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Thu, 18 Jun 2026 17:22:41 +0200
Subject: [PATCH 33/38] [analyzer] Current approach does not differentiate
between inner and outer scope of a function.
---
clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 11f967b3cd39b..4661ab7a0d199 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -14,6 +14,7 @@ REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SymbolRef, LifetimeSourceSet)
REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMapVal, const MemRegion *,
LifetimeSourceSet)
+REGISTER_SET_WITH_PROGRAMSTATE(DeadSourceSet, const MemRegion *)
namespace {
class LifetimeAnnotations
>From 979306c5a4b27fd9b312381c2362b748b8ad93ae Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Thu, 18 Jun 2026 21:58:45 +0200
Subject: [PATCH 34/38] [analyzer] Finished implementing checkLifetimeEnd and
checkLocation callbacks.
---
.../Checkers/LifetimeAnnotations.cpp | 85 +++++++++++++------
clang/test/Analysis/lifetime-bound.cpp | 24 +++++-
2 files changed, 82 insertions(+), 27 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 4661ab7a0d199..58d1808f16364 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -18,17 +18,20 @@ REGISTER_SET_WITH_PROGRAMSTATE(DeadSourceSet, const MemRegion *)
namespace {
class LifetimeAnnotations
- : public Checker<check::PostCall, check::EndFunction, check::LifetimeEnd> {
+ : public Checker<check::PostCall, check::EndFunction, check::LifetimeEnd,
+ check::Location> {
public:
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
const char *Sep) const override;
- ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal, const MemRegion *Source) const;
+ ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
+ const MemRegion *Source) const;
bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State,
CheckerContext &C) const;
void reportDanglingSource(const MemRegion *Region, ExplodedNode *N,
CheckerContext &C) const;
- void reportUseAfterScope(const VarDecl *Region, ExplodedNode *N, CheckerContext &C) const;
+ void reportUseAfterScope(const MemRegion *Region, ExplodedNode *N,
+ CheckerContext &C) const;
template <typename MapTy, typename KeyTy>
void checkReturnedBorrower(const MapTy &Map, const KeyTy RetKey,
@@ -36,6 +39,8 @@ class LifetimeAnnotations
CheckerContext &C) const;
void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const;
+ void checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
+ CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"};
@@ -164,36 +169,58 @@ void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS,
checkReturnedBorrower(LBMapVal, RetValRegion, State, N, C);
}
-void LifetimeAnnotations::checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const {
+void LifetimeAnnotations::checkLifetimeEnd(const VarDecl *VD,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
if (!VD)
return;
- ProgramStateRef State = C.getState();
+ SVal SourceVal = State->getLValue(VD, C.getStackFrame());
+ if (const MemRegion *SourceRegion = SourceVal.getAsRegion()) {
+ State = State->add<DeadSourceSet>(SourceRegion);
+ C.addTransition(State);
+ }
+}
+void LifetimeAnnotations::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
auto LBMap = State->get<LifetimeBoundMap>();
auto LBMapVal = State->get<LifetimeBoundMapVal>();
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
+
+ // FIXME: Because of the CFG::LifetimeEnd elements now the analyzer can
+ // reason about out-of-scope dangling pointer deref even if there is
+ // no annotations in the source code.
+ if (const MemRegion *R = Loc.getAsRegion()) {
+ if (R && State->contains<DeadSourceSet>(R))
+ reportUseAfterScope(R, N, C);
+ }
+
if (LBMap.isEmpty() && LBMapVal.isEmpty())
return;
- for (auto &&[Origin, SourceSet] : LBMapVal) {
- for (const MemRegion *Region : SourceSet) {
- if (const VarDecl *VDRegion = dyn_cast_if_present<VarRegion>(Region)->getDecl()) {
- if (VDRegion == VD) {
- ExplodedNode *N = C.generateNonFatalErrorNode();
- reportUseAfterScope(VDRegion, N, C);
- }
+ // FIXME: If a borrower has multiple bound sources the callback warns
+ // if any source has died. The callback should track which source the
+ // borrower actually points.
+
+ if (SymbolRef LocSym = Loc.getAsSymbol(true)) {
+ if (auto *SourceSet = State->get<LifetimeBoundMap>(LocSym)) {
+ for (const MemRegion *Source : *SourceSet) {
+ if (State->contains<DeadSourceSet>(Source))
+ reportUseAfterScope(Source, N, C);
}
}
}
- for (auto &&[Origin, SourceSet] : LBMap) {
- for (const MemRegion *Region : SourceSet) {
- if (const VarDecl *VDRegion = dyn_cast_if_present<VarRegion>(Region)->getDecl()) {
- if (VDRegion == VD) {
- ExplodedNode *N = C.generateNonFatalErrorNode();
- reportUseAfterScope(VDRegion, N, C);
- }
+ if (const MemRegion *LocRegion = Loc.getAsRegion()) {
+ if (auto *SourceSet = State->get<LifetimeBoundMapVal>(LocRegion)) {
+ for (const MemRegion *Source : *SourceSet) {
+ if (State->contains<DeadSourceSet>(Source))
+ reportUseAfterScope(Source, N, C);
}
}
}
@@ -202,13 +229,20 @@ void LifetimeAnnotations::checkLifetimeEnd(const VarDecl *VD, CheckerContext &C)
void LifetimeAnnotations::reportDanglingSource(const MemRegion *Region,
ExplodedNode *N,
CheckerContext &C) const {
- std::string ErrorMessage = (llvm::Twine("Returning value bound to a local '") + Region->getString() + "' that will go out of scope").str();
+ std::string ErrorMessage =
+ (llvm::Twine("Returning value bound to a local '") + Region->getString() +
+ "' that will go out of scope")
+ .str();
auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, ErrorMessage, N);
C.emitReport(std::move(BR));
}
-void LifetimeAnnotations::reportUseAfterScope(const VarDecl *Region, ExplodedNode *N, CheckerContext &C) const {
- std::string ErrorMessage = (llvm::Twine("Used variable after its scope ended '") + Region->getNameAsString() + "' error.").str();
+void LifetimeAnnotations::reportUseAfterScope(const MemRegion *Region,
+ ExplodedNode *N,
+ CheckerContext &C) const {
+ std::string ErrorMessage = (llvm::Twine("Use of '") + Region->getString() +
+ "' after its lifetime ended.")
+ .str();
auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, ErrorMessage, N);
C.emitReport(std::move(BR));
}
@@ -260,7 +294,8 @@ class DebugLifetimeAnnotations : public Checker<eval::Call> {
void analyzerLifetimeBound(const CallEvent &Call, CheckerContext &C) const;
const BugType BugMsg{this, "DebugLifetimeAnnotations", "DebugLifetimeBound"};
- using FnCheck = void (DebugLifetimeAnnotations::*)(const CallEvent &Call, CheckerContext &C) const;
+ using FnCheck = void (DebugLifetimeAnnotations::*)(const CallEvent &Call,
+ CheckerContext &C) const;
const CallDescriptionMap<FnCheck> Callbacks = {
{{CDM::SimpleFunc, {"clang_analyzer_lifetime_bound"}},
@@ -271,7 +306,7 @@ class DebugLifetimeAnnotations : public Checker<eval::Call> {
} // namespace
bool DebugLifetimeAnnotations::evalCall(const CallEvent &Call,
- CheckerContext &C) const {
+ CheckerContext &C) const {
const auto *CE = dyn_cast_if_present<CallExpr>(Call.getOriginExpr());
if (!CE)
@@ -286,7 +321,7 @@ bool DebugLifetimeAnnotations::evalCall(const CallEvent &Call,
}
void DebugLifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call,
- CheckerContext &C) const {
+ CheckerContext &C) const {
ProgramStateRef State = C.getState();
unsigned int ArgCount = Call.getNumArgs();
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index 529a5dbf34d70..e166e76a059e8 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -166,9 +166,29 @@ void no_return() {
// Use-after-scope dangling pointer dereference
void caller_ten() {
int* p = nullptr;
- { // expected-warning {{Used variable after its scope ended 'x' error}}
+ {
int x = 1;
p = test_func(&x);
}
- *p = 2;
+ *p = 2; // expected-warning {{Use of 'x' after its lifetime ended}}
+}
+
+void out_of_scope_ptr() {
+ int *ptr = nullptr;
+ {
+ int n = 5;
+ ptr = &n;
+ }
+ *ptr = 3; // expected-warning {{Use of 'n' after its lifetime ended}}
+}
+
+void f() {
+ int* p;
+ {
+ int x = 1;
+ p = &x;
+ }
+ int y = 2;
+ p = &y;
+ *p = 3; // no-warning
}
>From 6d6a21dfeea40933ee1722d3e3414e43e07aff76 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Thu, 18 Jun 2026 22:33:28 +0200
Subject: [PATCH 35/38] [analyzer] Finished implementing checkLifetimeEnd and
checkLocation callbacks.
---
.../Checkers/LifetimeAnnotations.cpp | 25 +++++++++++--------
1 file changed, 14 insertions(+), 11 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 58d1808f16364..71b0f89f4e433 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -182,22 +182,21 @@ void LifetimeAnnotations::checkLifetimeEnd(const VarDecl *VD,
}
}
+// FIXME: Use helper functions for checkLocation
void LifetimeAnnotations::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
auto LBMap = State->get<LifetimeBoundMap>();
auto LBMapVal = State->get<LifetimeBoundMapVal>();
- ExplodedNode *N = C.generateNonFatalErrorNode();
- if (!N)
- return;
-
// FIXME: Because of the CFG::LifetimeEnd elements now the analyzer can
// reason about out-of-scope dangling pointer deref even if there is
// no annotations in the source code.
if (const MemRegion *R = Loc.getAsRegion()) {
- if (R && State->contains<DeadSourceSet>(R))
- reportUseAfterScope(R, N, C);
+ if (State->contains<DeadSourceSet>(R)) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode())
+ reportUseAfterScope(R, N, C);
+ }
}
if (LBMap.isEmpty() && LBMapVal.isEmpty())
@@ -206,12 +205,13 @@ void LifetimeAnnotations::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
// FIXME: If a borrower has multiple bound sources the callback warns
// if any source has died. The callback should track which source the
// borrower actually points.
-
if (SymbolRef LocSym = Loc.getAsSymbol(true)) {
if (auto *SourceSet = State->get<LifetimeBoundMap>(LocSym)) {
for (const MemRegion *Source : *SourceSet) {
- if (State->contains<DeadSourceSet>(Source))
- reportUseAfterScope(Source, N, C);
+ if (State->contains<DeadSourceSet>(Source)) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode())
+ reportUseAfterScope(Source, N, C);
+ }
}
}
}
@@ -219,8 +219,11 @@ void LifetimeAnnotations::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
if (const MemRegion *LocRegion = Loc.getAsRegion()) {
if (auto *SourceSet = State->get<LifetimeBoundMapVal>(LocRegion)) {
for (const MemRegion *Source : *SourceSet) {
- if (State->contains<DeadSourceSet>(Source))
- reportUseAfterScope(Source, N, C);
+ if (State->contains<DeadSourceSet>(Source)) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
+ reportUseAfterScope(Source, N, C);
+ }
+ }
}
}
}
>From daeec1aede47252c87540cec3f2e2c99c1c23acd Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Mon, 22 Jun 2026 01:33:49 +0200
Subject: [PATCH 36/38] Applied changes to the code base.
---
.../clang/StaticAnalyzer/Checkers/Checkers.td | 4 +-
.../Checkers/LifetimeAnnotations.cpp | 54 +++++++++----------
clang/test/Analysis/debug-lifetime-bound.cpp | 12 +++++
clang/test/Analysis/lifetime-bound.cpp | 11 +++-
4 files changed, 48 insertions(+), 33 deletions(-)
create mode 100644 clang/test/Analysis/debug-lifetime-bound.cpp
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 432970f8d6c35..45ce97e5e4424 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -789,7 +789,8 @@ def SmartPtrChecker: Checker<"SmartPtr">,
Documentation<HasDocumentation>;
def LifetimeAnnotations : Checker<"LifetimeAnnotations">,
- HelpText<"Check for lifetime violations using lifetime annotations">,
+ HelpText<"Check for lifetime violations by incorporating lifetime "
+ "annotations into the analysis">,
Documentation<NotDocumented>;
} // end: "alpha.cplusplus"
@@ -1582,6 +1583,7 @@ def CheckerDocumentationChecker : Checker<"CheckerDocumentation">,
def DebugLifetimeAnnotations : Checker<"DebugLifetimeAnnotations">,
HelpText<"Prints the bindings recorded by the LifetimeAnnotations checker. Use with clang_analyzer_lifetime_bound().">,
+ WeakDependencies<[LifetimeAnnotations]>,
Documentation<NotDocumented>;
} // end "debug"
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 71b0f89f4e433..07af24f963ace 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -35,8 +35,8 @@ class LifetimeAnnotations
template <typename MapTy, typename KeyTy>
void checkReturnedBorrower(const MapTy &Map, const KeyTy RetKey,
- ProgramStateRef State, ExplodedNode *N,
- CheckerContext &C) const;
+ ProgramStateRef State, CheckerContext &C) const;
+ void reportDanglingBorrower(const LifetimeSourceSet *Sources, CheckerContext &C) const;
void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const;
void checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
@@ -122,13 +122,13 @@ 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 (hasDanglingSource(Region, State, C))
- reportDanglingSource(Region, N, C);
+ if (ExplodedNode *N = C.generateNonFatalErrorNode())
+ reportDanglingSource(Region, N, C);
}
}
}
@@ -158,15 +158,11 @@ void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS,
if (!RetValSym && !RetValRegion)
return;
- ExplodedNode *N = C.generateNonFatalErrorNode();
- if (!N)
- return;
-
if (RetValSym)
- checkReturnedBorrower(LBMap, RetValSym, State, N, C);
+ checkReturnedBorrower(LBMap, RetValSym, State, C);
if (RetValRegion)
- checkReturnedBorrower(LBMapVal, RetValRegion, State, N, C);
+ checkReturnedBorrower(LBMapVal, RetValRegion, State, C);
}
void LifetimeAnnotations::checkLifetimeEnd(const VarDecl *VD,
@@ -182,7 +178,17 @@ void LifetimeAnnotations::checkLifetimeEnd(const VarDecl *VD,
}
}
-// FIXME: Use helper functions for checkLocation
+void LifetimeAnnotations::reportDanglingBorrower(const LifetimeSourceSet *Sources, CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ for (const MemRegion *Source : *Sources) {
+ if (State->contains<DeadSourceSet>(Source)) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode())
+ reportUseAfterScope(Source, N, C);
+ }
+ }
+}
+
void LifetimeAnnotations::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
@@ -191,7 +197,8 @@ void LifetimeAnnotations::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
// FIXME: Because of the CFG::LifetimeEnd elements now the analyzer can
// reason about out-of-scope dangling pointer deref even if there is
- // no annotations in the source code.
+ // no annotations in the source code. This detection part of the detection
+ // should live in a separate checker.
if (const MemRegion *R = Loc.getAsRegion()) {
if (State->contains<DeadSourceSet>(R)) {
if (ExplodedNode *N = C.generateNonFatalErrorNode())
@@ -206,26 +213,13 @@ void LifetimeAnnotations::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
// if any source has died. The callback should track which source the
// borrower actually points.
if (SymbolRef LocSym = Loc.getAsSymbol(true)) {
- if (auto *SourceSet = State->get<LifetimeBoundMap>(LocSym)) {
- for (const MemRegion *Source : *SourceSet) {
- if (State->contains<DeadSourceSet>(Source)) {
- if (ExplodedNode *N = C.generateNonFatalErrorNode())
- reportUseAfterScope(Source, N, C);
- }
- }
- }
+ if (auto *SourceSet = State->get<LifetimeBoundMap>(LocSym))
+ reportDanglingBorrower(SourceSet, C);
}
if (const MemRegion *LocRegion = Loc.getAsRegion()) {
- if (auto *SourceSet = State->get<LifetimeBoundMapVal>(LocRegion)) {
- for (const MemRegion *Source : *SourceSet) {
- if (State->contains<DeadSourceSet>(Source)) {
- if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
- reportUseAfterScope(Source, N, C);
- }
- }
- }
- }
+ if (auto *SourceSet = State->get<LifetimeBoundMapVal>(LocRegion))
+ reportDanglingBorrower(SourceSet, C);
}
}
@@ -233,7 +227,7 @@ void LifetimeAnnotations::reportDanglingSource(const MemRegion *Region,
ExplodedNode *N,
CheckerContext &C) const {
std::string ErrorMessage =
- (llvm::Twine("Returning value bound to a local '") + Region->getString() +
+ (llvm::Twine("Returning value bound to '") + Region->getString() +
"' that will go out of scope")
.str();
auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, ErrorMessage, N);
diff --git a/clang/test/Analysis/debug-lifetime-bound.cpp b/clang/test/Analysis/debug-lifetime-bound.cpp
new file mode 100644
index 0000000000000..3325d7bb08404
--- /dev/null
+++ b/clang/test/Analysis/debug-lifetime-bound.cpp
@@ -0,0 +1,12 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DebugLifetimeAnnotations \
+// RUN: -verify %s
+
+void clang_analyzer_lifetime_bound(int);
+
+// Verify that the DebugLifetimeAnnotations checkre can be used without
+// the LifetimeAnnotations checker being enabled and it does not cause
+// crash.
+void test() {
+ int x = 5;
+ clang_analyzer_lifetime_bound(x); // expected-no-diagnostics
+}
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index e166e76a059e8..252209d2a034e 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -143,14 +143,14 @@ int *test_func(int *p [[clang::lifetimebound]]);
int *direct_return() {
int i = 5;
return test_func(&i);
- // expected-warning at -1 {{Returning value bound to a local 'i' that will go out of scope}}
+ // expected-warning at -1 {{Returning value bound to 'i' that will go out of scope}}
// expected-warning at -2 {{address of stack memory associated with local variable 'i' returned}}
}
int *variable_return() {
int y = 5;
int *p = test_func(&y);
- return p; // expected-warning {{Returning value bound to a local 'y' that will go out of scope}}
+ return p; // expected-warning {{Returning value bound to 'y' that will go out of scope}}
}
int *borrow_from_caller(int *b [[clang::lifetimebound]]) {
@@ -192,3 +192,10 @@ void f() {
p = &y;
*p = 3; // no-warning
}
+
+int* g() {
+ int i = 5;
+ int* p = test_func(&i);
+ (void)p;
+ return nullptr; // no-warning
+}
>From d39148e997edeafa54da23abee3e102c1c83ca5d Mon Sep 17 00:00:00 2001
From: Benedek Kaibas <82393336+benedekaibas at users.noreply.github.com>
Date: Mon, 22 Jun 2026 01:35:43 +0200
Subject: [PATCH 37/38] Delete non-related file.
---
FETCH_HEAD | 0
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 FETCH_HEAD
diff --git a/FETCH_HEAD b/FETCH_HEAD
deleted file mode 100644
index e69de29bb2d1d..0000000000000
>From 309b2a54df5da849f12148210c891b76e2634836 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Mon, 22 Jun 2026 19:42:08 +0200
Subject: [PATCH 38/38] Removed the logic for detecting use-after-scope
dangling ptrs when there is no annotation.
---
.../Checkers/LifetimeAnnotations.cpp | 62 +++++++------------
clang/test/Analysis/lifetime-bound.cpp | 30 ---------
2 files changed, 21 insertions(+), 71 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 07af24f963ace..584d665da8f6b 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -14,12 +14,11 @@ REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SymbolRef, LifetimeSourceSet)
REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMapVal, const MemRegion *,
LifetimeSourceSet)
-REGISTER_SET_WITH_PROGRAMSTATE(DeadSourceSet, const MemRegion *)
+REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *)
namespace {
class LifetimeAnnotations
- : public Checker<check::PostCall, check::EndFunction, check::LifetimeEnd,
- check::Location> {
+ : public Checker<check::PostCall, check::EndFunction, check::Location> {
public:
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
@@ -36,9 +35,9 @@ class LifetimeAnnotations
template <typename MapTy, typename KeyTy>
void checkReturnedBorrower(const MapTy &Map, const KeyTy RetKey,
ProgramStateRef State, CheckerContext &C) const;
- void reportDanglingBorrower(const LifetimeSourceSet *Sources, CheckerContext &C) const;
+ void reportDanglingBorrower(const LifetimeSourceSet *Sources,
+ CheckerContext &C) const;
void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
- void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const;
void checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
@@ -105,9 +104,11 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
bool LifetimeAnnotations::hasDanglingSource(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.
+ // FIXME: The checker currently handles stack-region sources. Other
+ // region kinds require separate methodology. For example, heap
+ // regions do not go out of scope at the end of a stack frame, so
+ // in order to detect those type of dangling sources the function
+ // needs to be expanded to an event-driven approach as well.
if (const auto *StackSpace =
Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
const StackFrame *SF = StackSpace->getStackFrame();
@@ -165,28 +166,16 @@ void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS,
checkReturnedBorrower(LBMapVal, RetValRegion, State, C);
}
-void LifetimeAnnotations::checkLifetimeEnd(const VarDecl *VD,
- CheckerContext &C) const {
+void LifetimeAnnotations::reportDanglingBorrower(
+ const LifetimeSourceSet *Sources, CheckerContext &C) const {
ProgramStateRef State = C.getState();
- if (!VD)
- return;
-
- SVal SourceVal = State->getLValue(VD, C.getStackFrame());
- if (const MemRegion *SourceRegion = SourceVal.getAsRegion()) {
- State = State->add<DeadSourceSet>(SourceRegion);
- C.addTransition(State);
- }
-}
-
-void LifetimeAnnotations::reportDanglingBorrower(const LifetimeSourceSet *Sources, CheckerContext &C) const {
- ProgramStateRef State = C.getState();
- for (const MemRegion *Source : *Sources) {
- if (State->contains<DeadSourceSet>(Source)) {
- if (ExplodedNode *N = C.generateNonFatalErrorNode())
- reportUseAfterScope(Source, N, C);
- }
+ for (const MemRegion *Source : *Sources) {
+ if (State->contains<DeallocatedSourceSet>(Source)) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode())
+ reportUseAfterScope(Source, N, C);
}
+ }
}
void LifetimeAnnotations::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
@@ -195,23 +184,14 @@ void LifetimeAnnotations::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
auto LBMap = State->get<LifetimeBoundMap>();
auto LBMapVal = State->get<LifetimeBoundMapVal>();
- // FIXME: Because of the CFG::LifetimeEnd elements now the analyzer can
- // reason about out-of-scope dangling pointer deref even if there is
- // no annotations in the source code. This detection part of the detection
- // should live in a separate checker.
- if (const MemRegion *R = Loc.getAsRegion()) {
- if (State->contains<DeadSourceSet>(R)) {
- if (ExplodedNode *N = C.generateNonFatalErrorNode())
- reportUseAfterScope(R, N, C);
- }
- }
-
if (LBMap.isEmpty() && LBMapVal.isEmpty())
return;
- // FIXME: If a borrower has multiple bound sources the callback warns
- // if any source has died. The callback should track which source the
- // borrower actually points.
+ // FIXME: If a borrower has multiple bound sources the callback
+ // warns if any of the sources have died. PathDiagnosticVisitor
+ // should be used to trace and identify which annotated parameter
+ // recorded the binding. Attaching this information as path notes
+ // would make the diagnostics more useful to the user.
if (SymbolRef LocSym = Loc.getAsSymbol(true)) {
if (auto *SourceSet = State->get<LifetimeBoundMap>(LocSym))
reportDanglingBorrower(SourceSet, C);
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index 252209d2a034e..f7b0155caa769 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -163,36 +163,6 @@ void no_return() {
(void)p; // no-warning
}
-// Use-after-scope dangling pointer dereference
-void caller_ten() {
- int* p = nullptr;
- {
- int x = 1;
- p = test_func(&x);
- }
- *p = 2; // expected-warning {{Use of 'x' after its lifetime ended}}
-}
-
-void out_of_scope_ptr() {
- int *ptr = nullptr;
- {
- int n = 5;
- ptr = &n;
- }
- *ptr = 3; // expected-warning {{Use of 'n' after its lifetime ended}}
-}
-
-void f() {
- int* p;
- {
- int x = 1;
- p = &x;
- }
- int y = 2;
- p = &y;
- *p = 3; // no-warning
-}
-
int* g() {
int i = 5;
int* p = test_func(&i);
More information about the cfe-commits
mailing list