https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/214589
>From cdc331a0660b22d1b18e929aa1e93f8277b1b423 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Fri, 7 Aug 2026 00:20:33 +0200 Subject: [PATCH 1/6] [analyzer] Add aggregate value tracking to the LifetimeModeling checker --- .../Checkers/LifetimeModeling.cpp | 49 ++++++++++++ .../Checkers/LifetimeModeling.h | 4 + clang/test/Analysis/lifetime-bound.cpp | 76 +++++++++++++++---- 3 files changed, 113 insertions(+), 16 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp index 2fab20b199f01..bbaa53d9d8473 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp @@ -105,6 +105,44 @@ std::string lifetime_modeling::getRegionName(const MemRegion *Reg) { return "the region"; } +// FIXME: Nested structs are not yet handled. +SmallVector<const MemRegion *, 4> +lifetime_modeling::getRegionsFromAggrVal(SVal Val, CheckerContext &C) { + SmallVector<const MemRegion *, 4> Reg; + + if (auto LCV = Val.getAs<nonloc::LazyCompoundVal>()) { + const TypedValueRegion *LCVRegion = LCV->getRegion(); + QualType T = LCVRegion->getValueType(); + MemRegionManager &MemMgr = C.getSValBuilder().getRegionManager(); + StoreManager &StoreMgr = C.getState()->getStateManager().getStoreManager(); + + if (const RecordType *RT = T->getAsStructureType()) { + const RecordDecl *RD = RT->getDecl()->getDefinition(); + if (!RD) + return Reg; + + for (const auto *I : RD->fields()) { + // Unnamed bitfields in a struct are not relevant for the analysis + // so the checker should skip them and jsut continue. + // CallAndMessageChecker has the same logic. + if (I->isUnnamedBitField()) + continue; + + const FieldRegion *FR = MemMgr.getFieldRegion(I, LCVRegion); + SVal V = StoreMgr.getBinding(LCV->getStore(), loc::MemRegionVal(FR)); + if (const MemRegion *R = V.getAsRegion()) + Reg.push_back(R); + } + } + } else if (auto CV = Val.getAs<nonloc::CompoundVal>()) { + for (SVal CVVal : *CV) { + if (const MemRegion *CVReg = CVVal.getAsRegion()) + Reg.push_back(CVReg); + } + } + return Reg; +} + void LifetimeModeling::checkPostCall(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); @@ -118,6 +156,12 @@ void LifetimeModeling::checkPostCall(const CallEvent &Call, return; SVal RetVal = Call.getReturnValue(); + SmallVector<const MemRegion *, 4> AggrRegs = + lifetime_modeling::getRegionsFromAggrVal(RetVal, C); + + for (const MemRegion *I : AggrRegs) { + State = bindSource(State, RetVal, I); + } for (const ParmVarDecl *PVD : FD->parameters()) { if (PVD->hasAttr<LifetimeBoundAttr>()) { @@ -174,6 +218,11 @@ void LifetimeModeling::checkDeadSymbols(SymbolReaper &SymReaper, S && SymReaper.isLive(S)) continue; + if (llvm::any_of( + lifetime_modeling::getRegionsFromAggrVal(Val, C), + [&](const MemRegion *R) { return SymReaper.isLiveRegion(R); })) + continue; + State = State->remove<LifetimeBoundMap>(Val); } diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h index 8d6c8e4882d1c..5f1950944c878 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h @@ -22,6 +22,10 @@ bool isBoundToLifetimeSource(ProgramStateRef State, SVal Val); /// Returns the descriptive name of the memory region or a placeholder if a /// descriptive name cannot be constructed for it. std::string getRegionName(const MemRegion *Reg); + +/// Returns the MemRegions the fields of an aggregate value (CompoundVal, +/// LazyCompoundVal) point to. +SmallVector<const MemRegion *, 4> getRegionsFromAggrVal(SVal Val, CheckerContext &C); } // namespace clang::ento::lifetime_modeling #endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp index d29c37f639993..8a3ba8b3a6f10 100644 --- a/clang/test/Analysis/lifetime-bound.cpp +++ b/clang/test/Analysis/lifetime-bound.cpp @@ -155,22 +155,6 @@ void caller_nine() { // expected-note-re@-2 {{Origin '&SymRegion{{.*}}' bound to 'first_num', 'second_num'}} } -struct View { - int *p; -}; -View makeView(int &x [[clang::lifetimebound]]); - -void clang_analyzer_dumpLifetimeOriginsOf(View); - -void caller_view() { - int v = 42; - View w = makeView(v); - // FIXME: Currently none of the maps cover LazyCompoundVal. - clang_analyzer_dumpLifetimeOriginsOf(w); // no-warning -} - - - // These are the test cases for testing the correctness of the emitted warning from the UseAfterLifetimeEnd checker. // Return value bound to annotated param cases. @@ -410,3 +394,63 @@ void no_dangling_by_value_argument() { // The returned reference does not dangle. takes_by_value(BoundToSelf()); } + +struct F { + int *p; +}; + +F makeView(int &x [[clang::lifetimebound]]) { return F{&x}; } + +F whole_struct_return_lazycompoundval() { + int x = 5; // expected-note {{'x' initialized here}} + return makeView(x); + // expected-warning@-1 {{Returning value bound to 'x' that will go out of scope}} + // expected-note@-2 {{Returning value bound to 'x' that will go out of scope}} + // expected-warning@-3 {{Address of stack memory associated with local variable 'x' returned to caller}} + // expected-note@-4 {{Address of stack memory associated with local variable 'x' returned to caller}} + // expected-warning@-5 {{address of stack memory associated with local variable 'x' returned}} +} + +struct PtrPair { + int *p; + int *q; +}; + +int global_v = 4; + +PtrPair makePair(int &x [[clang::lifetimebound]]) { + return PtrPair{&x, &global_v}; +} + +PtrPair return_pair_by_value() { + int local = 5; // expected-note {{'local' initialized here}} + return makePair(local); + // expected-warning@-1 {{Returning value bound to 'local' that will go out of scope}} + // expected-note@-2 {{Returning value bound to 'local' that will go out of scope}} + // expected-warning@-3 {{Address of stack memory associated with local variable 'local' returned to caller}} + // expected-note@-4 {{Address of stack memory associated with local variable 'local' returned to caller}} + // expected-warning@-5 {{address of stack memory associated with local variable 'local' returned}} +} + +struct InnerS { + int *p; +}; + +struct OuterS { + InnerS inner; + int *q; +}; + +OuterS makeNested(int &x [[clang::lifetimebound]]) { + return OuterS{InnerS{&x}}; +} + +// FIXME: Nested structs are not yet handled by getRegionsFromAggrVal, +// that is why this dangling pointer is not yet detected. +OuterS nested_struct_return_not_yet_detected() { + int y = 5; + return makeNested(y); + // expected-warning@-1 {{Address of stack memory associated with local variable 'y' returned to caller}} + // expected-note@-2 {{Address of stack memory associated with local variable 'y' returned to caller}} + // expected-warning@-3 {{address of stack memory associated with local variable 'y' returned}} +} >From f5852a8871a5752a655226b8c4774bbe7858fe2b Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Fri, 7 Aug 2026 01:01:05 +0200 Subject: [PATCH 2/6] More descriptive FIXME. --- clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp index bbaa53d9d8473..f447f93207199 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp @@ -105,7 +105,7 @@ std::string lifetime_modeling::getRegionName(const MemRegion *Reg) { return "the region"; } -// FIXME: Nested structs are not yet handled. +// FIXME: Retrieving the MemRegions of nested struct fields is not yet supported. SmallVector<const MemRegion *, 4> lifetime_modeling::getRegionsFromAggrVal(SVal Val, CheckerContext &C) { SmallVector<const MemRegion *, 4> Reg; >From 0b71e66a42ab469a9a8cf34be061a5e55028e6ed Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Fri, 7 Aug 2026 12:08:45 +0200 Subject: [PATCH 3/6] Correct expected-notes. --- clang/test/Analysis/lifetime-bound.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp index 8a3ba8b3a6f10..4624bce2ec204 100644 --- a/clang/test/Analysis/lifetime-bound.cpp +++ b/clang/test/Analysis/lifetime-bound.cpp @@ -402,13 +402,14 @@ struct F { F makeView(int &x [[clang::lifetimebound]]) { return F{&x}; } F whole_struct_return_lazycompoundval() { - int x = 5; // expected-note {{'x' initialized here}} + int x = 5;// expected-note {{'x' initialized here}} return makeView(x); // expected-warning@-1 {{Returning value bound to 'x' that will go out of scope}} - // expected-note@-2 {{Returning value bound to 'x' that will go out of scope}} - // expected-warning@-3 {{Address of stack memory associated with local variable 'x' returned to caller}} - // expected-note@-4 {{Address of stack memory associated with local variable 'x' returned to caller}} - // expected-warning@-5 {{address of stack memory associated with local variable 'x' returned}} + // expected-note@-2 {{Lifetime of 'x' ended here}} + // expected-note@-3 {{Value's lifetime bound to the lifetime of 'x' here}} + // expected-warning@-4 {{Address of stack memory associated with local variable 'x' returned to caller}} + // expected-note@-5 {{Address of stack memory associated with local variable 'x' returned to caller}} + // expected-warning@-6 {{address of stack memory associated with local variable 'x' returned}} } struct PtrPair { @@ -426,10 +427,11 @@ PtrPair return_pair_by_value() { int local = 5; // expected-note {{'local' initialized here}} return makePair(local); // expected-warning@-1 {{Returning value bound to 'local' that will go out of scope}} - // expected-note@-2 {{Returning value bound to 'local' that will go out of scope}} - // expected-warning@-3 {{Address of stack memory associated with local variable 'local' returned to caller}} - // expected-note@-4 {{Address of stack memory associated with local variable 'local' returned to caller}} - // expected-warning@-5 {{address of stack memory associated with local variable 'local' returned}} + // expected-note@-2 {{Lifetime of 'local' ended here}} + // expected-note@-3 {{Value's lifetime bound to the lifetime of 'local' here}} + // expected-warning@-4 {{Address of stack memory associated with local variable 'local' returned to caller}} + // expected-note@-5 {{Address of stack memory associated with local variable 'local' returned to caller}} + // expected-warning@-6 {{address of stack memory associated with local variable 'local' returned}} } struct InnerS { >From ed1d95f95454338d74392ced8b9702dcf104297e Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Fri, 7 Aug 2026 12:12:36 +0200 Subject: [PATCH 4/6] Add space before expected-note. --- clang/test/Analysis/lifetime-bound.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp index 4624bce2ec204..c1f7f9f2ffda5 100644 --- a/clang/test/Analysis/lifetime-bound.cpp +++ b/clang/test/Analysis/lifetime-bound.cpp @@ -402,7 +402,7 @@ struct F { F makeView(int &x [[clang::lifetimebound]]) { return F{&x}; } F whole_struct_return_lazycompoundval() { - int x = 5;// expected-note {{'x' initialized here}} + int x = 5; // expected-note {{'x' initialized here}} return makeView(x); // expected-warning@-1 {{Returning value bound to 'x' that will go out of scope}} // expected-note@-2 {{Lifetime of 'x' ended here}} >From d49cedd2994ebaa2d26e9d1c96fc989fbe659e90 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Fri, 7 Aug 2026 12:37:24 +0200 Subject: [PATCH 5/6] Fix formatting issue. --- clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h index 5f1950944c878..d6cacfd73a4ef 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h @@ -25,7 +25,8 @@ std::string getRegionName(const MemRegion *Reg); /// Returns the MemRegions the fields of an aggregate value (CompoundVal, /// LazyCompoundVal) point to. -SmallVector<const MemRegion *, 4> getRegionsFromAggrVal(SVal Val, CheckerContext &C); +SmallVector<const MemRegion *, 4> getRegionsFromAggrVal(SVal Val, + CheckerContext &C); } // namespace clang::ento::lifetime_modeling #endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H >From d0a1303306a599641c105509b3c8c478b2a1e802 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Fri, 7 Aug 2026 13:00:36 +0200 Subject: [PATCH 6/6] Re-run clang format on the comment as well in LifetimeModeling. --- clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp index f447f93207199..ebbccc2c799c0 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp @@ -105,7 +105,8 @@ std::string lifetime_modeling::getRegionName(const MemRegion *Reg) { return "the region"; } -// FIXME: Retrieving the MemRegions of nested struct fields is not yet supported. +// FIXME: Retrieving the MemRegions of nested struct fields is not yet +// supported. SmallVector<const MemRegion *, 4> lifetime_modeling::getRegionsFromAggrVal(SVal Val, CheckerContext &C) { SmallVector<const MemRegion *, 4> Reg; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
