https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/205521
>From ed9afadd50a70193aa48fa4d6082409b445a91d6 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Wed, 24 Jun 2026 12:05:46 +0200 Subject: [PATCH 1/3] [analyzer] Implement LifetimeAnnotations checker. --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 11 + .../StaticAnalyzer/Checkers/CMakeLists.txt | 2 + .../Checkers/LifetimeAnnotations.cpp | 305 ++++++++++++++++++ clang/test/Analysis/debug-lifetime-bound.cpp | 10 + clang/test/Analysis/lifetime-bound.cpp | 171 ++++++++++ 5 files changed, 499 insertions(+) create mode 100644 clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp create mode 100644 clang/test/Analysis/debug-lifetime-bound.cpp create mode 100644 clang/test/Analysis/lifetime-bound.cpp diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index d02c3195069f3..5ba220ab6d60e 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -788,6 +788,11 @@ def SmartPtrChecker: Checker<"SmartPtr">, Dependencies<[SmartPtrModeling]>, Documentation<HasDocumentation>; +def LifetimeAnnotations : Checker<"LifetimeAnnotations">, + HelpText<"Check for lifetime violations by incorporating lifetime " + "annotations into the analysis">, + Documentation<NotDocumented>; + } // end: "alpha.cplusplus" //===----------------------------------------------------------------------===// @@ -1576,6 +1581,12 @@ 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().">, + WeakDependencies<[LifetimeAnnotations]>, + Documentation<NotDocumented>; + } // end "debug" diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 8a0621077b977..8363f345f4cc8 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 @@ -146,6 +147,7 @@ add_clang_library(clangStaticAnalyzerCheckers clangAST clangASTMatchers clangAnalysis + clangAnalysisLifetimeSafety clangBasic clangLex clangStaticAnalyzerCore diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp new file mode 100644 index 0000000000000..e25d076dd0bd5 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp @@ -0,0 +1,305 @@ +#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" +#include "clang/AST/Attrs.inc" +#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" + +using namespace clang; +using namespace ento; + +REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *) +REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet) + +REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *) + +namespace { +class LifetimeAnnotations + : public Checker<check::PostCall, check::EndFunction, check::Location, + check::DeadSymbols> { +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 reportDanglingSource(const MemRegion *Region, ExplodedNode *N, + CheckerContext &C) const; + void reportUseAfterScope(const MemRegion *Region, ExplodedNode *N, + CheckerContext &C) const; + + void checkReturnedBorrower(SVal Val, ProgramStateRef State, + CheckerContext &C) const; + void reportDanglingBorrower(const LifetimeSourceSet *Sources, + CheckerContext &C) const; + void checkEndFunction(const ReturnStmt *RS, 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"}; +}; + +} // namespace + +static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal, + const MemRegion *Source) { + LifetimeSourceSet::Factory &F = + State->getStateManager().get_context<LifetimeSourceSet>(); + + const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(RetVal); + LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet(); + Set = F.add(Set, Source); + State = State->set<LifetimeBoundMap>(RetVal, Set); + return State; +} + +void LifetimeAnnotations::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + const auto *FC = dyn_cast<AnyFunctionCall>(&Call); + if (!FC) + return; + + const FunctionDecl *FD = FC->getDecl(); + if (!FD) + return; + + SVal RetVal = Call.getReturnValue(); + + 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, RetVal, ArgValRegion); + } + } + + if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) { + if (lifetimes::implicitObjectParamIsLifetimeBound(FD)) { + if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) { + State = bindValues(State, RetVal, AttrRegion); + } + } + } + C.addTransition(State); +} + +static bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State, + CheckerContext &C) { + // 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(); + const StackFrame *CurrentSF = C.getStackFrame(); + if (SF == CurrentSF || !SF->isParentOf(CurrentSF)) + return true; + } + return false; +} + +void LifetimeAnnotations::checkReturnedBorrower(SVal Val, ProgramStateRef State, + CheckerContext &C) const { + if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) { + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { + for (const MemRegion *Region : *SourceSet) { + if (hasDanglingSource(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>(); + + if (LBMap.isEmpty()) + return; + + const Expr *RetExpr = RS->getRetValue(); + if (!RetExpr) + return; + + RetExpr = RetExpr->IgnoreParens(); + SVal RetVal = C.getSVal(RetExpr); + checkReturnedBorrower(RetVal, State, C); +} + +void LifetimeAnnotations::reportDanglingBorrower( + const LifetimeSourceSet *Sources, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return; + + for (const MemRegion *Source : *Sources) { + if (State->contains<DeallocatedSourceSet>(Source)) { + reportUseAfterScope(Source, N, C); + } + } +} + +void LifetimeAnnotations::checkLocation(SVal Loc, bool IsLoad, const Stmt *S, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + auto LBMap = State->get<LifetimeBoundMap>(); + + if (LBMap.isEmpty()) + return; + + // 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 (auto *SourceSet = State->get<LifetimeBoundMap>(Loc)) + reportDanglingBorrower(SourceSet, C); +} + +void LifetimeAnnotations::reportDanglingSource(const MemRegion *Region, + ExplodedNode *N, + CheckerContext &C) const { + auto BR = std::make_unique<PathSensitiveBugReport>( + BugMsg, + (llvm::Twine("Returning value bound to '") + Region->getString() + + "' that will go out of scope") + .str(), + N); + C.emitReport(std::move(BR)); +} + +void LifetimeAnnotations::reportUseAfterScope(const MemRegion *Region, + ExplodedNode *N, + CheckerContext &C) const { + auto BR = std::make_unique<PathSensitiveBugReport>( + BugMsg, + (llvm::Twine("Use of '") + Region->getString() + + "' after its lifetime ended.") + .str(), + N); + C.emitReport(std::move(BR)); +} + +void LifetimeAnnotations::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>(); + + DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>(); + + for (SVal Val : llvm::make_first_range(LBMap)) { + if (const MemRegion *ValRegion = Val.getAsRegion()) { + if (!SymReaper.isLiveRegion(ValRegion)) + State = State->remove<LifetimeBoundMap>(Val); + } else if (SymbolRef ValRef = + Val.getAsSymbol(/*IncludeBaseRegions=*/true)) { + if (!SymReaper.isLive(ValRef)) + State = State->remove<LifetimeBoundMap>(Val); + } + } + + for (const MemRegion *Region : Sources) { + if (!SymReaper.isLiveRegion(Region)) + State = State->remove<DeallocatedSourceSet>(Region); + } + + C.addTransition(State); +} + +void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const { + auto LBMap = State->get<LifetimeBoundMap>(); + + if (LBMap.isEmpty()) + return; + + Out << Sep << "LifetimeBound bindings:" << NL; + for (auto &&[OriginSym, SourceSet] : LBMap) { + for (const auto *Region : SourceSet) + Out << " Origin " << OriginSym << " contains Loan " << Region << NL; + } +} + +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()); + if (!CE) + return false; + + const FnCheck *Handler = Callbacks.lookup(Call); + if (!Handler) + return false; + + (this->*(*Handler))(Call, C); + return true; +} + +void DebugLifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, + CheckerContext &C) const { + + ProgramStateRef State = C.getState(); + unsigned int ArgCount = Call.getNumArgs(); + if (ArgCount != 1) + return; + + SVal ArgSVal = Call.getArgSVal(0); + + if (auto *SourceSet = State->get<LifetimeBoundMap>(ArgSVal)) { + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { + for (const auto *Region : *SourceSet) { + llvm::SmallString<128> Str; + llvm::raw_svector_ostream OS(Str); + OS << " Origin " << ArgSVal << " bound to " << Region; + auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N); + C.emitReport(std::move(BR)); + } + } + } +} + +void ento::registerLifetimeAnnotations(CheckerManager &mgr) { + mgr.registerChecker<LifetimeAnnotations>(); +} + +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/debug-lifetime-bound.cpp b/clang/test/Analysis/debug-lifetime-bound.cpp new file mode 100644 index 0000000000000..e62c51ae6bc53 --- /dev/null +++ b/clang/test/Analysis/debug-lifetime-bound.cpp @@ -0,0 +1,10 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.LifetimeAnnotations,debug.DebugLifetimeAnnotations -verify %s + +// expected-no-diagnostics + +void clang_analyzer_lifetime_bound(int); + +void test() { + int x = 5; + clang_analyzer_lifetime_bound(x); // no-warning: verifies debug checker does not crash standalone +} diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp new file mode 100644 index 0000000000000..ca97419b63e53 --- /dev/null +++ b/clang/test/Analysis/lifetime-bound.cpp @@ -0,0 +1,171 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.LifetimeAnnotations,debug.DebugLifetimeAnnotations \ +// RUN: -analyzer-config cfg-lifetime=true -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.LifetimeAnnotations,debug.DebugLifetimeAnnotations \ +// RUN: -analyzer-config c++-container-inlining=false -analyzer-config cfg-lifetime=true -verify %s + +struct A {}; + +void clang_analyzer_lifetime_bound(int*); +void clang_analyzer_lifetime_bound(int&); +void clang_analyzer_lifetime_bound(A*); +void clang_analyzer_lifetime_bound(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 caller() { + int v = 0; + X obj; + int& r = obj.choose(v); + clang_analyzer_lifetime_bound(r); // expected-warning {{Origin &v bound to v}} +} + +// Obj ref type function return annotated case +struct Y { + A a; + A& getA() [[clang::lifetimebound]] { return 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}} +} + +// Obj ptr type function return annotated case +struct Z { + A a; + A* getA() [[clang::lifetimebound]] { return &a; } +}; + +void caller_three() { + Z z; + A* func = z.getA(); + clang_analyzer_lifetime_bound(func); // expected-warning {{Origin &z.a bound to z}} +} + +// Free function with annotated param and ref return +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 {{Origin &num bound to num}} +} + +// Free function with annotated param and ptr return +int* boo(int* num [[clang::lifetimebound]]) { return num; } + +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}} +} + +// Free function with both annotated and non-annotated parameters. +int& fn(int& f, int& s [[clang::lifetimebound]]) { return s; } + +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}} +} + + + +// 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 caller_seven() { + int y = 15; + int* y_ptr = &y; + auto* bind = foo(y_ptr); + + clang_analyzer_lifetime_bound(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to y}} +} + +// Function returns a reference and has an annotated parameter +int& func(int& some_number [[clang::lifetimebound]]); + +void caller_eight() { + int f = 15; + auto& bind = func(f); + + clang_analyzer_lifetime_bound(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to f}} +} + +// Function returns a reference and has two annotated parameters. +int& f(int& a [[clang::lifetimebound]], int& b [[clang::lifetimebound]]); + +void caller_nine() { + int first_num = 1; + int second_num = 2; + int& numbers = f(first_num, second_num); + + clang_analyzer_lifetime_bound(numbers); + // expected-warning-re@-1 {{Origin &SymRegion{{.*}} bound to first_num}} + // expected-warning-re@-2 {{Origin &SymRegion{{.*}} bound to second_num}} +} + +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 +} + + + +// 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@-1 {{Returning value bound to 'i' that will go out of scope}} + // expected-warning@-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 '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 +} + +int* g() { + int i = 5; + int* p = test_func(&i); + (void)p; + return nullptr; // no-warning +} >From ea22d128673996408833b7a30ecd97513df19591 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Wed, 24 Jun 2026 23:36:37 +0200 Subject: [PATCH 2/3] Changed checker naming and resolved comments. --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 10 +- .../StaticAnalyzer/Checkers/CMakeLists.txt | 2 +- .../Checkers/UseAfterLifetimeEnd.cpp | 317 ++++++++++++++++++ clang/test/Analysis/debug-lifetime-bound.cpp | 7 +- clang/test/Analysis/lifetime-bound.cpp | 57 ++-- 5 files changed, 355 insertions(+), 38 deletions(-) create mode 100644 clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 5ba220ab6d60e..2c59fddc89ca1 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -788,9 +788,9 @@ def SmartPtrChecker: Checker<"SmartPtr">, Dependencies<[SmartPtrModeling]>, Documentation<HasDocumentation>; -def LifetimeAnnotations : Checker<"LifetimeAnnotations">, - HelpText<"Check for lifetime violations by incorporating lifetime " - "annotations into the analysis">, +def UseAfterLifetimeEnd : Checker<"UseAfterLifetimeEnd">, + HelpText<"Check for uses of references or pointers that " + "outlive their bound object">, Documentation<NotDocumented>; } // end: "alpha.cplusplus" @@ -1581,10 +1581,10 @@ def CheckerDocumentationChecker : Checker<"CheckerDocumentation">, HelpText<"Defines an empty checker callback for all possible handlers.">, Documentation<NotDocumented>; -def DebugLifetimeAnnotations : Checker<"DebugLifetimeAnnotations">, +def DebugUseAfterLifetimeEnd : Checker<"DebugUseAfterLifetimeEnd">, HelpText<"Prints the bindings recorded by the LifetimeAnnotations checker. " "Use with clang_analyzer_lifetime_bound().">, - WeakDependencies<[LifetimeAnnotations]>, + WeakDependencies<[UseAfterLifetimeEnd]>, Documentation<NotDocumented>; } // end "debug" diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 8363f345f4cc8..46c0c36fda736 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -55,7 +55,6 @@ add_clang_library(clangStaticAnalyzerCheckers IteratorModeling.cpp IteratorRangeChecker.cpp IvarInvalidationChecker.cpp - LifetimeAnnotations.cpp LLVMConventionsChecker.cpp LocalizationChecker.cpp MacOSKeychainAPIChecker.cpp @@ -127,6 +126,7 @@ add_clang_library(clangStaticAnalyzerCheckers UninitializedObject/UninitializedPointee.cpp UnixAPIChecker.cpp UnreachableCodeChecker.cpp + UseAfterLifetimeEnd.cpp VforkChecker.cpp VLASizeChecker.cpp VAListChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp new file mode 100644 index 0000000000000..81a7aabd6f7a4 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp @@ -0,0 +1,317 @@ +#include "clang/AST/Attr.h" +#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" + +using namespace clang; +using namespace ento; + +REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *) +REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet) + +REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *) + +namespace { +class UseAfterLifetimeEnd + : public Checker<check::PostCall, check::EndFunction, check::Location, + check::DeadSymbols> { +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 reportDanglingSource(const MemRegion *Region, ExplodedNode *N, + CheckerContext &C) const; + void reportUseAfterScope(const MemRegion *Region, ExplodedNode *N, + CheckerContext &C) const; + + void checkReturnedBorrower(SVal Val, ProgramStateRef State, + CheckerContext &C) const; + void reportDanglingBorrower(const LifetimeSourceSet *Sources, + CheckerContext &C) const; + void checkEndFunction(const ReturnStmt *RS, 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, "UseAfterLifetimeEnd", "LifetimeBound"}; +}; + +} // namespace + +static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal, + const MemRegion *Source) { + LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>(); + + const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(RetVal); + LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet(); + Set = F.add(Set, Source); + State = State->set<LifetimeBoundMap>(RetVal, Set); + return State; +} + +void UseAfterLifetimeEnd::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + const auto *FC = dyn_cast<AnyFunctionCall>(&Call); + if (!FC) + return; + + const FunctionDecl *FD = FC->getDecl(); + if (!FD) + return; + + SVal RetVal = Call.getReturnValue(); + + 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, RetVal, ArgValRegion); + } + } + + if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) { + if (lifetimes::implicitObjectParamIsLifetimeBound(FD)) { + if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) { + State = bindValues(State, RetVal, AttrRegion); + } + } + } + C.addTransition(State); +} + +static bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State, + CheckerContext &C) { + // 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(); + const StackFrame *CurrentSF = C.getStackFrame(); + if (SF == CurrentSF || !SF->isParentOf(CurrentSF)) + return true; + } + return false; +} + +void UseAfterLifetimeEnd::checkReturnedBorrower(SVal Val, ProgramStateRef State, + CheckerContext &C) const { + if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) { + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { + for (const MemRegion *Region : *SourceSet) { + if (hasDanglingSource(Region, State, C)) + reportDanglingSource(Region, N, C); + } + } + } +} + +void UseAfterLifetimeEnd::checkEndFunction(const ReturnStmt *RS, + CheckerContext &C) const { + if (!RS) + return; + + ProgramStateRef State = C.getState(); + auto LBMap = State->get<LifetimeBoundMap>(); + + if (LBMap.isEmpty()) + return; + + const Expr *RetExpr = RS->getRetValue(); + if (!RetExpr) + return; + + RetExpr = RetExpr->IgnoreParens(); + SVal RetVal = C.getSVal(RetExpr); + checkReturnedBorrower(RetVal, State, C); +} + +void UseAfterLifetimeEnd::reportDanglingBorrower( + const LifetimeSourceSet *Sources, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return; + + for (const MemRegion *Source : *Sources) { + if (State->contains<DeallocatedSourceSet>(Source)) { + reportUseAfterScope(Source, N, C); + } + } +} + +void UseAfterLifetimeEnd::checkLocation(SVal Loc, bool IsLoad, const Stmt *S, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + auto LBMap = State->get<LifetimeBoundMap>(); + + if (LBMap.isEmpty()) + return; + + // 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 (auto *SourceSet = State->get<LifetimeBoundMap>(Loc)) + reportDanglingBorrower(SourceSet, C); +} + +void UseAfterLifetimeEnd::reportDanglingSource(const MemRegion *Region, + ExplodedNode *N, + CheckerContext &C) const { + auto BR = std::make_unique<PathSensitiveBugReport>( + BugMsg, + (llvm::Twine("Returning value bound to '") + Region->getString() + + "' that will go out of scope"), + N); + C.emitReport(std::move(BR)); +} + +void UseAfterLifetimeEnd::reportUseAfterScope(const MemRegion *Region, + ExplodedNode *N, + CheckerContext &C) const { + auto BR = std::make_unique<PathSensitiveBugReport>( + BugMsg, + (llvm::Twine("Use of '") + Region->getString() + + "' after its lifetime ended."), + N); + C.emitReport(std::move(BR)); +} + +void UseAfterLifetimeEnd::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>(); + + DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>(); + + for (SVal Val : llvm::make_first_range(LBMap)) { + if (const MemRegion *ValRegion = Val.getAsRegion()) { + if (!SymReaper.isLiveRegion(ValRegion)) + State = State->remove<LifetimeBoundMap>(Val); + } else if (SymbolRef ValRef = + Val.getAsSymbol(/*IncludeBaseRegions=*/true)) { + if (!SymReaper.isLive(ValRef)) + State = State->remove<LifetimeBoundMap>(Val); + } + } + + for (const MemRegion *Region : Sources) { + if (!SymReaper.isLiveRegion(Region)) + State = State->remove<DeallocatedSourceSet>(Region); + } + + C.addTransition(State); +} + +void UseAfterLifetimeEnd::printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const { + auto LBMap = State->get<LifetimeBoundMap>(); + + if (LBMap.isEmpty()) + return; + + Out << Sep << "LifetimeBound bindings:" << NL; + for (auto &&[OriginSym, SourceSet] : LBMap) { + for (const auto *Region : SourceSet) + Out << " Origin " << OriginSym << " contains Loan " << Region << NL; + } +} + +namespace { +class DebugUseAfterLifetimeEnd : public Checker<eval::Call> { +public: + bool evalCall(const CallEvent &Call, CheckerContext &C) const; + void analyzerDumpLifetimeOriginsOf(const CallEvent &Call, + CheckerContext &C) const; + + const BugType BugMsg{this, "DebugUseAfterLifetimeEnd", + "DebugUseAfterLifetimeEnd"}; + using FnCheck = void (DebugUseAfterLifetimeEnd::*)(const CallEvent &Call, + CheckerContext &C) const; + + const CallDescriptionMap<FnCheck> Callbacks = { + {{CDM::SimpleFunc, {"clang_analyzer_dumpLifetimeOriginsOf"}}, + &DebugUseAfterLifetimeEnd::analyzerDumpLifetimeOriginsOf}, + }; +}; + +} // namespace + +bool DebugUseAfterLifetimeEnd::evalCall(const CallEvent &Call, + CheckerContext &C) const { + const auto *CE = dyn_cast_if_present<CallExpr>(Call.getOriginExpr()); + if (!CE) + return false; + + const FnCheck *Handler = Callbacks.lookup(Call); + if (!Handler) + return false; + + (this->*(*Handler))(Call, C); + return true; +} + +void DebugUseAfterLifetimeEnd::analyzerDumpLifetimeOriginsOf( + const CallEvent &Call, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + if (Call.getNumArgs() != 1) { + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { + auto BR = std::make_unique<PathSensitiveBugReport>( + BugMsg, + "clang_analyzer_dumpLifetimeOriginsOf requires exactly 1 argument", + N); + C.emitReport(std::move(BR)); + } + return; + } + + SVal ArgSVal = Call.getArgSVal(0); + const LifetimeSourceSet *SourceSet = State->get<LifetimeBoundMap>(ArgSVal); + + llvm::SmallString<128> Str; + llvm::raw_svector_ostream OS(Str); + OS << " Origin " << ArgSVal << " bound to "; + + if (!SourceSet) + return; + + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { + bool First = true; + for (const MemRegion *Region : *SourceSet) { + if (!First) + OS << ", "; + OS << Region; + First = false; + } + C.emitReport(std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N)); + } +} + +void ento::registerUseAfterLifetimeEnd(CheckerManager &Mgr) { + Mgr.registerChecker<UseAfterLifetimeEnd>(); +} + +bool ento::shouldRegisterUseAfterLifetimeEnd(const CheckerManager &Mgr) { + return true; +} + +void ento::registerDebugUseAfterLifetimeEnd(CheckerManager &Mgr) { + Mgr.registerChecker<DebugUseAfterLifetimeEnd>(); +} + +bool ento::shouldRegisterDebugUseAfterLifetimeEnd(const CheckerManager &Mgr) { + return true; +} diff --git a/clang/test/Analysis/debug-lifetime-bound.cpp b/clang/test/Analysis/debug-lifetime-bound.cpp index e62c51ae6bc53..8ef704195dcc6 100644 --- a/clang/test/Analysis/debug-lifetime-bound.cpp +++ b/clang/test/Analysis/debug-lifetime-bound.cpp @@ -1,10 +1,11 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.LifetimeAnnotations,debug.DebugLifetimeAnnotations -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugUseAfterLifetimeEnd -verify %s // expected-no-diagnostics -void clang_analyzer_lifetime_bound(int); +void clang_analyzer_dumpLifetimeOriginsOf(int); void test() { int x = 5; - clang_analyzer_lifetime_bound(x); // no-warning: verifies debug checker does not crash standalone + clang_analyzer_dumpLifetimeOriginsOf(x); // no-warning } + diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp index ca97419b63e53..1f870a94293cf 100644 --- a/clang/test/Analysis/lifetime-bound.cpp +++ b/clang/test/Analysis/lifetime-bound.cpp @@ -1,18 +1,18 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.LifetimeAnnotations,debug.DebugLifetimeAnnotations \ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugUseAfterLifetimeEnd \ // RUN: -analyzer-config cfg-lifetime=true -verify %s -// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.LifetimeAnnotations,debug.DebugLifetimeAnnotations \ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugUseAfterLifetimeEnd \ // RUN: -analyzer-config c++-container-inlining=false -analyzer-config cfg-lifetime=true -verify %s struct A {}; -void clang_analyzer_lifetime_bound(int*); -void clang_analyzer_lifetime_bound(int&); -void clang_analyzer_lifetime_bound(A*); -void clang_analyzer_lifetime_bound(A&); +void clang_analyzer_dumpLifetimeOriginsOf(int*); +void clang_analyzer_dumpLifetimeOriginsOf(int&); +void clang_analyzer_dumpLifetimeOriginsOf(A*); +void clang_analyzer_dumpLifetimeOriginsOf(A&); // These are the cases when the result of function calls are MemRegions. -// Ref type parameter annotated case +// Ref type parameter annotated case. struct X { int& choose(int& a [[clang::lifetimebound]]) { return a; } }; @@ -21,10 +21,10 @@ 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_dumpLifetimeOriginsOf(r); // expected-warning {{Origin &v bound to v}} } -// Obj ref type function return annotated case +// Obj ref type function return annotated case. struct Y { A a; A& getA() [[clang::lifetimebound]] { return a; } @@ -34,10 +34,10 @@ 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_dumpLifetimeOriginsOf(f); // expected-warning {{Origin &y.a bound to y}} } -// Obj ptr type function return annotated case +// Obj ptr type function return annotated case. struct Z { A a; A* getA() [[clang::lifetimebound]] { return &a; } @@ -46,19 +46,19 @@ struct Z { void caller_three() { Z z; A* func = z.getA(); - clang_analyzer_lifetime_bound(func); // expected-warning {{Origin &z.a bound to z}} + clang_analyzer_dumpLifetimeOriginsOf(func); // expected-warning {{Origin &z.a bound to z}} } -// Free function with annotated param and ref return +// Free function with annotated param and ref return. 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 {{Origin &num bound to num}} + clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &num bound to num}} } -// Free function with annotated param and ptr return +// Free function with annotated param and ptr return. int* boo(int* num [[clang::lifetimebound]]) { return num; } void caller_five() { @@ -66,7 +66,7 @@ void caller_five() { int* n_ptr = &n; int* s = boo(n_ptr); - clang_analyzer_lifetime_bound(s); // expected-warning {{Origin &n bound to n}} + clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &n bound to n}} } // Free function with both annotated and non-annotated parameters. @@ -77,14 +77,14 @@ void caller_six() { int odd = 55; int& s = fn(even, odd); - clang_analyzer_lifetime_bound(s); // expected-warning {{Origin &odd bound to odd}} + clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &odd bound to odd}} } // These are the cases when the result of function calls are SymbolRefs. -// Function returns ptr and has an annotated parameter +// Function returns ptr and has an annotated parameter. int* foo(int* n [[clang::lifetimebound]]); void caller_seven() { @@ -92,17 +92,17 @@ void caller_seven() { int* y_ptr = &y; auto* bind = foo(y_ptr); - clang_analyzer_lifetime_bound(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to y}} + clang_analyzer_dumpLifetimeOriginsOf(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to y}} } -// Function returns a reference and has an annotated parameter +// Function returns a reference and has an annotated parameter. int& func(int& some_number [[clang::lifetimebound]]); void caller_eight() { int f = 15; auto& bind = func(f); - clang_analyzer_lifetime_bound(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to f}} + clang_analyzer_dumpLifetimeOriginsOf(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to f}} } // Function returns a reference and has two annotated parameters. @@ -113,9 +113,7 @@ void caller_nine() { int second_num = 2; int& numbers = f(first_num, second_num); - clang_analyzer_lifetime_bound(numbers); - // expected-warning-re@-1 {{Origin &SymRegion{{.*}} bound to first_num}} - // expected-warning-re@-2 {{Origin &SymRegion{{.*}} bound to second_num}} + clang_analyzer_dumpLifetimeOriginsOf(numbers); // expected-warning-re {{Origin &SymRegion{{.*}} bound to first_num, second_num}} } struct View { @@ -123,20 +121,20 @@ struct View { }; View makeView(int& x [[clang::lifetimebound]]); -void clang_analyzer_lifetime_bound(View); +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_lifetime_bound(w); // no-warning + // 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 LifetimeAnnotations checker. +// These are the test cases for testing the correctness of the emitted warning from the UseAfterLifetimeEnd checker. -// Return value bound to annotated param cases +// Return value bound to annotated param cases. int *test_func(int *p [[clang::lifetimebound]]); @@ -169,3 +167,4 @@ int* g() { (void)p; return nullptr; // no-warning } + >From 3d04217388bb20497786031a9c38964b24a25bca Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Thu, 25 Jun 2026 15:25:21 +0200 Subject: [PATCH 3/3] Removed dead codes. --- .../Checkers/LifetimeAnnotations.cpp | 305 ------------------ .../Checkers/UseAfterLifetimeEnd.cpp | 81 +---- 2 files changed, 12 insertions(+), 374 deletions(-) delete mode 100644 clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp deleted file mode 100644 index e25d076dd0bd5..0000000000000 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp +++ /dev/null @@ -1,305 +0,0 @@ -#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" -#include "clang/AST/Attrs.inc" -#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" - -using namespace clang; -using namespace ento; - -REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *) -REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet) - -REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *) - -namespace { -class LifetimeAnnotations - : public Checker<check::PostCall, check::EndFunction, check::Location, - check::DeadSymbols> { -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 reportDanglingSource(const MemRegion *Region, ExplodedNode *N, - CheckerContext &C) const; - void reportUseAfterScope(const MemRegion *Region, ExplodedNode *N, - CheckerContext &C) const; - - void checkReturnedBorrower(SVal Val, ProgramStateRef State, - CheckerContext &C) const; - void reportDanglingBorrower(const LifetimeSourceSet *Sources, - CheckerContext &C) const; - void checkEndFunction(const ReturnStmt *RS, 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"}; -}; - -} // namespace - -static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal, - const MemRegion *Source) { - LifetimeSourceSet::Factory &F = - State->getStateManager().get_context<LifetimeSourceSet>(); - - const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(RetVal); - LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet(); - Set = F.add(Set, Source); - State = State->set<LifetimeBoundMap>(RetVal, Set); - return State; -} - -void LifetimeAnnotations::checkPostCall(const CallEvent &Call, - CheckerContext &C) const { - ProgramStateRef State = C.getState(); - - const auto *FC = dyn_cast<AnyFunctionCall>(&Call); - if (!FC) - return; - - const FunctionDecl *FD = FC->getDecl(); - if (!FD) - return; - - SVal RetVal = Call.getReturnValue(); - - 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, RetVal, ArgValRegion); - } - } - - if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) { - if (lifetimes::implicitObjectParamIsLifetimeBound(FD)) { - if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) { - State = bindValues(State, RetVal, AttrRegion); - } - } - } - C.addTransition(State); -} - -static bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State, - CheckerContext &C) { - // 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(); - const StackFrame *CurrentSF = C.getStackFrame(); - if (SF == CurrentSF || !SF->isParentOf(CurrentSF)) - return true; - } - return false; -} - -void LifetimeAnnotations::checkReturnedBorrower(SVal Val, ProgramStateRef State, - CheckerContext &C) const { - if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) { - if (ExplodedNode *N = C.generateNonFatalErrorNode()) { - for (const MemRegion *Region : *SourceSet) { - if (hasDanglingSource(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>(); - - if (LBMap.isEmpty()) - return; - - const Expr *RetExpr = RS->getRetValue(); - if (!RetExpr) - return; - - RetExpr = RetExpr->IgnoreParens(); - SVal RetVal = C.getSVal(RetExpr); - checkReturnedBorrower(RetVal, State, C); -} - -void LifetimeAnnotations::reportDanglingBorrower( - const LifetimeSourceSet *Sources, CheckerContext &C) const { - ProgramStateRef State = C.getState(); - - ExplodedNode *N = C.generateNonFatalErrorNode(); - if (!N) - return; - - for (const MemRegion *Source : *Sources) { - if (State->contains<DeallocatedSourceSet>(Source)) { - reportUseAfterScope(Source, N, C); - } - } -} - -void LifetimeAnnotations::checkLocation(SVal Loc, bool IsLoad, const Stmt *S, - CheckerContext &C) const { - ProgramStateRef State = C.getState(); - auto LBMap = State->get<LifetimeBoundMap>(); - - if (LBMap.isEmpty()) - return; - - // 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 (auto *SourceSet = State->get<LifetimeBoundMap>(Loc)) - reportDanglingBorrower(SourceSet, C); -} - -void LifetimeAnnotations::reportDanglingSource(const MemRegion *Region, - ExplodedNode *N, - CheckerContext &C) const { - auto BR = std::make_unique<PathSensitiveBugReport>( - BugMsg, - (llvm::Twine("Returning value bound to '") + Region->getString() + - "' that will go out of scope") - .str(), - N); - C.emitReport(std::move(BR)); -} - -void LifetimeAnnotations::reportUseAfterScope(const MemRegion *Region, - ExplodedNode *N, - CheckerContext &C) const { - auto BR = std::make_unique<PathSensitiveBugReport>( - BugMsg, - (llvm::Twine("Use of '") + Region->getString() + - "' after its lifetime ended.") - .str(), - N); - C.emitReport(std::move(BR)); -} - -void LifetimeAnnotations::checkDeadSymbols(SymbolReaper &SymReaper, - CheckerContext &C) const { - ProgramStateRef State = C.getState(); - LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>(); - - DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>(); - - for (SVal Val : llvm::make_first_range(LBMap)) { - if (const MemRegion *ValRegion = Val.getAsRegion()) { - if (!SymReaper.isLiveRegion(ValRegion)) - State = State->remove<LifetimeBoundMap>(Val); - } else if (SymbolRef ValRef = - Val.getAsSymbol(/*IncludeBaseRegions=*/true)) { - if (!SymReaper.isLive(ValRef)) - State = State->remove<LifetimeBoundMap>(Val); - } - } - - for (const MemRegion *Region : Sources) { - if (!SymReaper.isLiveRegion(Region)) - State = State->remove<DeallocatedSourceSet>(Region); - } - - C.addTransition(State); -} - -void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) const { - auto LBMap = State->get<LifetimeBoundMap>(); - - if (LBMap.isEmpty()) - return; - - Out << Sep << "LifetimeBound bindings:" << NL; - for (auto &&[OriginSym, SourceSet] : LBMap) { - for (const auto *Region : SourceSet) - Out << " Origin " << OriginSym << " contains Loan " << Region << NL; - } -} - -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()); - if (!CE) - return false; - - const FnCheck *Handler = Callbacks.lookup(Call); - if (!Handler) - return false; - - (this->*(*Handler))(Call, C); - return true; -} - -void DebugLifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, - CheckerContext &C) const { - - ProgramStateRef State = C.getState(); - unsigned int ArgCount = Call.getNumArgs(); - if (ArgCount != 1) - return; - - SVal ArgSVal = Call.getArgSVal(0); - - if (auto *SourceSet = State->get<LifetimeBoundMap>(ArgSVal)) { - if (ExplodedNode *N = C.generateNonFatalErrorNode()) { - for (const auto *Region : *SourceSet) { - llvm::SmallString<128> Str; - llvm::raw_svector_ostream OS(Str); - OS << " Origin " << ArgSVal << " bound to " << Region; - auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N); - C.emitReport(std::move(BR)); - } - } - } -} - -void ento::registerLifetimeAnnotations(CheckerManager &mgr) { - mgr.registerChecker<LifetimeAnnotations>(); -} - -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/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp index 81a7aabd6f7a4..1f11becd39046 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp @@ -13,30 +13,19 @@ using namespace ento; REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *) REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet) -REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *) - namespace { class UseAfterLifetimeEnd - : public Checker<check::PostCall, check::EndFunction, check::Location, - check::DeadSymbols> { + : public Checker<check::PostCall, check::EndFunction, check::DeadSymbols> { 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 reportDanglingSource(const MemRegion *Region, ExplodedNode *N, CheckerContext &C) const; - void reportUseAfterScope(const MemRegion *Region, ExplodedNode *N, - CheckerContext &C) const; - void checkReturnedBorrower(SVal Val, ProgramStateRef State, CheckerContext &C) const; - void reportDanglingBorrower(const LifetimeSourceSet *Sources, - CheckerContext &C) const; void checkEndFunction(const ReturnStmt *RS, 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, "UseAfterLifetimeEnd", "LifetimeBound"}; }; @@ -106,10 +95,14 @@ static bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State, void UseAfterLifetimeEnd::checkReturnedBorrower(SVal Val, ProgramStateRef State, CheckerContext &C) const { if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) { - if (ExplodedNode *N = C.generateNonFatalErrorNode()) { - for (const MemRegion *Region : *SourceSet) { - if (hasDanglingSource(Region, State, C)) - reportDanglingSource(Region, N, C); + ExplodedNode *N = nullptr; + for (const MemRegion *Region : *SourceSet) { + if (hasDanglingSource(Region, State, C)) { + if (!N) + N = C.generateNonFatalErrorNode(); + if (!N) + return; + reportDanglingSource(Region, N, C); } } } @@ -135,38 +128,6 @@ void UseAfterLifetimeEnd::checkEndFunction(const ReturnStmt *RS, checkReturnedBorrower(RetVal, State, C); } -void UseAfterLifetimeEnd::reportDanglingBorrower( - const LifetimeSourceSet *Sources, CheckerContext &C) const { - ProgramStateRef State = C.getState(); - - ExplodedNode *N = C.generateNonFatalErrorNode(); - if (!N) - return; - - for (const MemRegion *Source : *Sources) { - if (State->contains<DeallocatedSourceSet>(Source)) { - reportUseAfterScope(Source, N, C); - } - } -} - -void UseAfterLifetimeEnd::checkLocation(SVal Loc, bool IsLoad, const Stmt *S, - CheckerContext &C) const { - ProgramStateRef State = C.getState(); - auto LBMap = State->get<LifetimeBoundMap>(); - - if (LBMap.isEmpty()) - return; - - // 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 (auto *SourceSet = State->get<LifetimeBoundMap>(Loc)) - reportDanglingBorrower(SourceSet, C); -} - void UseAfterLifetimeEnd::reportDanglingSource(const MemRegion *Region, ExplodedNode *N, CheckerContext &C) const { @@ -178,24 +139,11 @@ void UseAfterLifetimeEnd::reportDanglingSource(const MemRegion *Region, C.emitReport(std::move(BR)); } -void UseAfterLifetimeEnd::reportUseAfterScope(const MemRegion *Region, - ExplodedNode *N, - CheckerContext &C) const { - auto BR = std::make_unique<PathSensitiveBugReport>( - BugMsg, - (llvm::Twine("Use of '") + Region->getString() + - "' after its lifetime ended."), - N); - C.emitReport(std::move(BR)); -} - void UseAfterLifetimeEnd::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { ProgramStateRef State = C.getState(); LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>(); - DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>(); - for (SVal Val : llvm::make_first_range(LBMap)) { if (const MemRegion *ValRegion = Val.getAsRegion()) { if (!SymReaper.isLiveRegion(ValRegion)) @@ -207,11 +155,6 @@ void UseAfterLifetimeEnd::checkDeadSymbols(SymbolReaper &SymReaper, } } - for (const MemRegion *Region : Sources) { - if (!SymReaper.isLiveRegion(Region)) - State = State->remove<DeallocatedSourceSet>(Region); - } - C.addTransition(State); } @@ -281,13 +224,13 @@ void DebugUseAfterLifetimeEnd::analyzerDumpLifetimeOriginsOf( SVal ArgSVal = Call.getArgSVal(0); const LifetimeSourceSet *SourceSet = State->get<LifetimeBoundMap>(ArgSVal); + if (!SourceSet) + return; + llvm::SmallString<128> Str; llvm::raw_svector_ostream OS(Str); OS << " Origin " << ArgSVal << " bound to "; - if (!SourceSet) - return; - if (ExplodedNode *N = C.generateNonFatalErrorNode()) { bool First = true; for (const MemRegion *Region : *SourceSet) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
