[clang] [analyzer] Implemented a base of detecing lifetimebound annotation (PR #200145)
Benedek Kaibas via cfe-commits
cfe-commits at lists.llvm.org
Sat May 30 15:41:13 PDT 2026
https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/200145
>From 826dbd7525158bf0946b41ec0f629c235b8238b2 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Thu, 28 May 2026 11:37:49 +0200
Subject: [PATCH 1/2] Started implementing the lifetime annotation checker.
---
.../clang/StaticAnalyzer/Checkers/Checkers.td | 4 +
.../StaticAnalyzer/Checkers/CMakeLists.txt | 1 +
.../Checkers/LifetimeAnnotations.cpp | 73 +++++++++++++++++++
3 files changed, 78 insertions(+)
create mode 100644 clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index eca2afbe340a9..85d59fc139728 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -788,6 +788,10 @@ def SmartPtrChecker: Checker<"SmartPtr">,
Dependencies<[SmartPtrModeling]>,
Documentation<HasDocumentation>;
+def LifetimeAnnotations : Checker<"LifetimeAnnotations">,
+ HelpText<"Check for lifetime violations using lifetime annotations">,
+ Documentation<NotDocumented>;
+
} // end: "alpha.cplusplus"
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index 8a0621077b977..3f426186189fa 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -55,6 +55,7 @@ add_clang_library(clangStaticAnalyzerCheckers
IteratorModeling.cpp
IteratorRangeChecker.cpp
IvarInvalidationChecker.cpp
+ LifetimeAnnotations.cpp
LLVMConventionsChecker.cpp
LocalizationChecker.cpp
MacOSKeychainAPIChecker.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
new file mode 100644
index 0000000000000..54e98b945c7b3
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -0,0 +1,73 @@
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include <AllocationState.h>
+
+using namespace clang;
+using namespace ento;
+
+REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, const MemRegion *,
+ const MemRegion *);
+
+class LifetimeAnnotations : public Checker<check::PostCall> {
+public:
+ void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+ void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
+ const char *Sep) const override;
+};
+
+void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ const auto *MethodDecl = dyn_cast_if_present<CXXMethodDecl>(Call.getDecl());
+
+ if (!MethodDecl)
+ return;
+
+ unsigned LBParamIdx = MethodDecl->getNumParams();
+ for (unsigned i = 0; i < MethodDecl->getNumParams(); i++) {
+ if (MethodDecl->getParamDecl(i)->hasAttr<LifetimeBoundAttr>()) {
+ LBParamIdx = i;
+ break;
+ }
+ }
+ if (LBParamIdx == MethodDecl->getNumParams())
+ return;
+
+ SVal RetVal = Call.getReturnValue();
+ const MemRegion *RetValRegion = RetVal.getAsRegion();
+ if (!RetValRegion)
+ return;
+
+ SVal ArgVal = Call.getArgSVal(LBParamIdx);
+ const MemRegion *ArgValRegion = ArgVal.getAsRegion();
+ if (!ArgValRegion)
+ return;
+
+ State = State->set<LifetimeBoundMap>(RetValRegion, ArgValRegion);
+ C.addTransition(State);
+}
+
+void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
+ const char *NL, const char *Sep) const {
+ auto LBTy = State->get<LifetimeBoundMap>();
+
+ if (!LBTy.isEmpty()) {
+ Out << Sep << "LifetimeBound objects: ";
+
+ for (auto I : LBTy) {
+ Out << I.first << " bound to " << I.second << NL;
+ }
+ }
+}
+
+void ento::registerLifetimeAnnotations(CheckerManager &mgr) {
+ mgr.registerChecker<LifetimeAnnotations>();
+}
+
+bool ento::shouldRegisterLifetimeAnnotations(const CheckerManager &mgr) {
+ return true;
+}
>From fa2b6c5e8c9de65307f9dfec47019530b005465c Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Sun, 31 May 2026 00:40:48 +0200
Subject: [PATCH 2/2] 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;
}
}
More information about the cfe-commits
mailing list