llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang-static-analyzer-1 @llvm/pr-subscribers-clang Author: Benedek Kaibas (benedekaibas) <details> <summary>Changes</summary> Implemented the `LifetimeAnnotations` checker which is responsible for detecting lifetime safety violations involving the `[[clang::lifetimebound]]` annotation. This checker is dependent on the `LifetimeModeling` checker which will be part of a future PR. For detailed history of the work of this checker, please see: #<!-- -->200145 --- Full diff: https://github.com/llvm/llvm-project/pull/205521.diff 5 Files Affected: - (modified) clang/include/clang/StaticAnalyzer/Checkers/Checkers.td (+11) - (modified) clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt (+2) - (added) clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp (+305) - (added) clang/test/Analysis/debug-lifetime-bound.cpp (+10) - (added) clang/test/Analysis/lifetime-bound.cpp (+171) ``````````diff 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 +} `````````` </details> https://github.com/llvm/llvm-project/pull/205521 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
