https://github.com/necto created https://github.com/llvm/llvm-project/pull/105648
Note, I prepared this PR to be rebased and merged with three commits that are self-sufficient and build on each other. Fix some false negatives of StackAddrEscapeChecker: - Output parameters ``` void top(int **out) { int local = 42; *out = &local; // Noncompliant } ``` - Indirect global pointers ``` int **global; void top() { int local = 42; *global = &local; // Noncompliant } ``` Note that now StackAddrEscapeChecker produces a diagnostic if a function with an output parameter is analyzed as top-level or as a callee. I took special care to make sure the reports point to the same primary location and, in many cases, feature the same primary message. That is the motivation to modify Core/BugReporter.cpp and Core/ExplodedGraph.cpp To avoid false positive reports when a global indirect pointer is assigned a local address, invalidated, and then reset, I rely on the fact that the invalidation symbol will be a DerivedSymbol of a ConjuredSymbol that refers to the same memory region. The checker still has a false negative for non-trivial escaping via a returned value. It requires an active https://sonarsource.atlassian.net/browse/CPP-4734 >From 9219884befe8e3c7b4ac8bcbfce9ecf9063713ec Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <necto...@gmail.com> Date: Tue, 20 Aug 2024 10:26:38 +0200 Subject: [PATCH 1/3] [analyzer] [NFC] Add tests for and refactor StackAddrEscapeChecker These tests and refactoring are preparatory for the upcoming changes: detection of the indirect leak via global variables and output parameters. CPP-4734 --- .../Checkers/StackAddrEscapeChecker.cpp | 71 ++- clang/test/Analysis/stack-addr-ps.c | 31 + clang/test/Analysis/stack-addr-ps.cpp | 596 ++++++++++++++++++ 3 files changed, 665 insertions(+), 33 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp index ea09c43cc5ce90..2bd4ca4528de8b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp @@ -288,12 +288,37 @@ void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS, EmitStackError(C, R, RetE); } +std::optional<std::string> printReferrer(const MemRegion *Referrer) { + assert(Referrer); + const StringRef ReferrerMemorySpace = [](const MemSpaceRegion *Space) { + if (isa<StaticGlobalSpaceRegion>(Space)) + return "static"; + if (isa<GlobalsSpaceRegion>(Space)) + return "global"; + assert(isa<StackSpaceRegion>(Space)); + return "stack"; + }(Referrer->getMemorySpace()); + + // We should really only have VarRegions here. + // Anything else is really surprising, and we should get notified if such + // ever happens. + const auto *ReferrerVar = dyn_cast<VarRegion>(Referrer); + if (!ReferrerVar) { + assert(false && "We should have a VarRegion here"); + return std::nullopt; // Defensively skip this one. + } + const std::string ReferrerVarName = + ReferrerVar->getDecl()->getDeclName().getAsString(); + + return (ReferrerMemorySpace + " variable '" + ReferrerVarName + "'").str(); +} + void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, CheckerContext &Ctx) const { if (!ChecksEnabled[CK_StackAddrEscapeChecker]) return; - ProgramStateRef State = Ctx.getState(); + ExplodedNode *Node = Ctx.getPredecessor(); // Iterate over all bindings to global variables and see if it contains // a memory region in the stack space. @@ -315,15 +340,10 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, if (!ReferrerMemSpace || !ReferredMemSpace) return false; - const auto *ReferrerFrame = ReferrerMemSpace->getStackFrame(); - const auto *ReferredFrame = ReferredMemSpace->getStackFrame(); - - if (ReferrerMemSpace && ReferredMemSpace) { - if (ReferredFrame == PoppedFrame && - ReferrerFrame->isParentOf(PoppedFrame)) { - V.emplace_back(Referrer, Referred); - return true; - } + if (ReferredMemSpace->getStackFrame() == PoppedFrame && + ReferrerMemSpace->getStackFrame()->isParentOf(PoppedFrame)) { + V.emplace_back(Referrer, Referred); + return true; } return false; } @@ -352,6 +372,7 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, }; CallBack Cb(Ctx); + ProgramStateRef State = Node->getState(); State->getStateManager().getStoreManager().iterBindings(State->getStore(), Cb); @@ -359,7 +380,7 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, return; // Generate an error node. - ExplodedNode *N = Ctx.generateNonFatalErrorNode(State); + ExplodedNode *N = Ctx.generateNonFatalErrorNode(State, Node); if (!N) return; @@ -374,13 +395,13 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, // Generate a report for this bug. const StringRef CommonSuffix = - "upon returning to the caller. This will be a dangling reference"; + " upon returning to the caller. This will be a dangling reference"; SmallString<128> Buf; llvm::raw_svector_ostream Out(Buf); const SourceRange Range = genName(Out, Referred, Ctx.getASTContext()); if (isa<CXXTempObjectRegion, CXXLifetimeExtendedObjectRegion>(Referrer)) { - Out << " is still referred to by a temporary object on the stack " + Out << " is still referred to by a temporary object on the stack" << CommonSuffix; auto Report = std::make_unique<PathSensitiveBugReport>(*BT_stackleak, Out.str(), N); @@ -390,28 +411,12 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, return; } - const StringRef ReferrerMemorySpace = [](const MemSpaceRegion *Space) { - if (isa<StaticGlobalSpaceRegion>(Space)) - return "static"; - if (isa<GlobalsSpaceRegion>(Space)) - return "global"; - assert(isa<StackSpaceRegion>(Space)); - return "stack"; - }(Referrer->getMemorySpace()); - - // We should really only have VarRegions here. - // Anything else is really surprising, and we should get notified if such - // ever happens. - const auto *ReferrerVar = dyn_cast<VarRegion>(Referrer); - if (!ReferrerVar) { - assert(false && "We should have a VarRegion here"); - continue; // Defensively skip this one. + auto ReferrerVariable = printReferrer(Referrer); + if (!ReferrerVariable) { + continue; } - const std::string ReferrerVarName = - ReferrerVar->getDecl()->getDeclName().getAsString(); - Out << " is still referred to by the " << ReferrerMemorySpace - << " variable '" << ReferrerVarName << "' " << CommonSuffix; + Out << " is still referred to by the " << *ReferrerVariable << CommonSuffix; auto Report = std::make_unique<PathSensitiveBugReport>(*BT_stackleak, Out.str(), N); if (Range.isValid()) diff --git a/clang/test/Analysis/stack-addr-ps.c b/clang/test/Analysis/stack-addr-ps.c index e69ab4189b524f..2e14b7820be136 100644 --- a/clang/test/Analysis/stack-addr-ps.c +++ b/clang/test/Analysis/stack-addr-ps.c @@ -95,3 +95,34 @@ void callTestRegister(void) { char buf[20]; testRegister(buf); // no-warning } + +void top_level_leaking(int **out) { + int local = 42; + *out = &local; // no-warning FIXME +} + +void callee_leaking_via_param(int **out) { + int local = 1; + *out = &local; + // expected-warning@-1{{Address of stack memory associated with local variable 'local' is still referred to by the stack variable 'ptr'}} +} + +void caller_for_leaking_callee() { + int *ptr = 0; + callee_leaking_via_param(&ptr); +} + +void callee_nested_leaking(int **out) { + int local = 1; + *out = &local; + // expected-warning@-1{{Address of stack memory associated with local variable 'local' is still referred to by the stack variable 'ptr'}} +} + +void caller_mid_for_nested_leaking(int **mid) { + callee_nested_leaking(mid); +} + +void caller_for_nested_leaking() { + int *ptr = 0; + caller_mid_for_nested_leaking(&ptr); +} diff --git a/clang/test/Analysis/stack-addr-ps.cpp b/clang/test/Analysis/stack-addr-ps.cpp index bd856be2b8d690..68ccc322bf2ef2 100644 --- a/clang/test/Analysis/stack-addr-ps.cpp +++ b/clang/test/Analysis/stack-addr-ps.cpp @@ -161,3 +161,599 @@ C make1() { void test_copy_elision() { C c1 = make1(); } + +namespace leaking_via_direct_pointer { +void* returned_direct_pointer_top() { + int local = 42; + int* p = &local; + return p; // expected-warning{{associated with local variable 'local' returned}} +} + +int* returned_direct_pointer_callee() { + int local = 42; + int* p = &local; + return p; // expected-warning{{associated with local variable 'local' returned}} +} + +void returned_direct_pointer_caller() { + int* loc_ptr = nullptr; + loc_ptr = returned_direct_pointer_callee(); + (void)loc_ptr; +} + +void* global_ptr; + +void global_direct_pointer() { + int local = 42; + global_ptr = &local; +} // expected-warning{{local variable 'local' is still referred to by the global variable 'global_ptr'}} + +void static_direct_pointer_top() { + int local = 42; + static int* p = &local; + (void)p; +} // expected-warning{{local variable 'local' is still referred to by the static variable 'p'}} + +void static_direct_pointer_callee() { + int local = 42; + static int* p = &local; + (void)p; // expected-warning{{local variable 'local' is still referred to by the static variable 'p'}} +} + +void static_direct_pointer_caller() { + static_direct_pointer_callee(); +} + +void lambda_to_global_direct_pointer() { + auto lambda = [&] { + int local = 42; + global_ptr = &local; // expected-warning{{local variable 'local' is still referred to by the global variable 'global_ptr'}} + }; + lambda(); +} + +void lambda_to_context_direct_pointer() { + int *p = nullptr; + auto lambda = [&] { + int local = 42; + p = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'p'}} + }; + lambda(); + (void)p; +} + +template<typename Callable> +class MyFunction { + Callable* fptr; + public: + MyFunction(Callable* callable) :fptr(callable) {} +}; + +void* lambda_to_context_direct_pointer_uncalled() { + int *p = nullptr; + auto lambda = [&] { + int local = 42; + p = &local; // no-warning: analyzed only as top-level, ignored explicitly by the checker + }; + return new MyFunction(&lambda); +} + +void lambda_to_context_direct_pointer_lifetime_extended() { + int *p = nullptr; + auto lambda = [&] { + int&& local = 42; + p = &local; // expected-warning{{'int' lifetime extended by local variable 'local' is still referred to by the stack variable 'p'}} + }; + lambda(); + (void)p; +} + +template<typename Callback> +void lambda_param_capture_direct_pointer_callee(Callback& callee) { + int local = 42; + callee(local); // expected-warning{{'local' is still referred to by the stack variable 'p'}} +} + +void lambda_param_capture_direct_pointer_caller() { + int* p = nullptr; + auto capt = [&p](int& param) { + p = ¶m; + }; + lambda_param_capture_direct_pointer_callee(capt); +} +} // namespace leaking_via_direct_pointer + +namespace leaking_via_ptr_to_ptr { +void** returned_ptr_to_ptr_top() { + int local = 42; + int* p = &local; + void** pp = (void**)&p; + return pp; // expected-warning{{associated with local variable 'p' returned}} +} + +void** global_pp; + +void global_ptr_local_to_ptr() { + int local = 42; + int* p = &local; + global_pp = (void**)&p; +} // expected-warning{{local variable 'p' is still referred to by the global variable 'global_pp'}} + +void global_ptr_to_ptr() { + int local = 42; + *global_pp = &local; // no-warning FIXME +} + +void *** global_ppp; + +void global_ptr_to_ptr_to_ptr() { + int local = 42; + **global_ppp = &local; // no-warning FIXME +} + +void** get_some_pp(); + +void static_ptr_to_ptr() { + int local = 42; + static void** pp = get_some_pp(); + *pp = &local; +} // no-warning False Negative, requires relating multiple bindings to cross the invented pointer. + +void param_ptr_to_ptr_top(void** pp) { + int local = 42; + *pp = &local; // no-warning FIXME +} + +void param_ptr_to_ptr_callee(void** pp) { + int local = 42; + *pp = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'p'}} +} + +void param_ptr_to_ptr_caller() { + void* p = nullptr; + param_ptr_to_ptr_callee((void**)&p); +} + +void param_ptr_to_ptr_to_ptr_top(void*** ppp) { + int local = 42; + **ppp = &local; // no-warning FIXME +} + +void param_ptr_to_ptr_to_ptr_callee(void*** ppp) { + int local = 42; + **ppp = &local; // no-warning FIXME +} + +void param_ptr_to_ptr_to_ptr_caller(void** pp) { + param_ptr_to_ptr_to_ptr_callee(&pp); +} + +void lambda_to_context_ptr_to_ptr(int **pp) { + auto lambda = [&] { + int local = 42; + *pp = &local; // no-warning FIXME + }; + lambda(); + (void)*pp; +} + +void param_ptr_to_ptr_fptr(int **pp) { + int local = 42; + *pp = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'p'}} +} + +void param_ptr_to_ptr_fptr_caller(void (*fptr)(int**)) { + int* p = nullptr; + fptr(&p); +} + +void param_ptr_to_ptr_caller_caller() { + void (*fptr)(int**) = param_ptr_to_ptr_fptr; + param_ptr_to_ptr_fptr_caller(fptr); +} +} // namespace leaking_via_ptr_to_ptr + +namespace leaking_via_ref_to_ptr { +void** make_ptr_to_ptr(); +void*& global_rtp = *make_ptr_to_ptr(); + +void global_ref_to_ptr() { + int local = 42; + int* p = &local; + global_rtp = p; // no-warning FIXME +} + +void static_ref_to_ptr() { + int local = 42; + static void*& p = *make_ptr_to_ptr(); + p = &local; + (void)p; +} // no-warning False Negative, requires relating multiple bindings to cross the invented pointer. + +void param_ref_to_ptr_top(void*& rp) { + int local = 42; + int* p = &local; + rp = p; // no-warning FIXME +} + +void param_ref_to_ptr_callee(void*& rp) { + int local = 42; + int* p = &local; + rp = p; // expected-warning{{local variable 'local' is still referred to by the stack variable 'p'}} +} + +void param_ref_to_ptr_caller() { + void* p = nullptr; + param_ref_to_ptr_callee(p); +} +} // namespace leaking_via_ref_to_ptr + +namespace leaking_via_arr_of_ptr_static_idx { +void** returned_arr_of_ptr_top() { + int local = 42; + int* p = &local; + void** arr = new void*[2]; + arr[1] = p; + return arr; +} // no-warning False Negative + +void** returned_arr_of_ptr_callee() { + int local = 42; + int* p = &local; + void** arr = new void*[2]; + arr[1] = p; + return arr; +} // no-warning False Negative + +void returned_arr_of_ptr_caller() { + void** arr = returned_arr_of_ptr_callee(); + (void)arr[1]; +} + +void* global_aop[2]; + +void global_arr_of_ptr() { + int local = 42; + int* p = &local; + global_aop[1] = p; +} // expected-warning{{local variable 'local' is still referred to by the global variable 'global_aop'}} + +void static_arr_of_ptr() { + int local = 42; + static void* arr[2]; + arr[1] = &local; + (void)arr[1]; +} // expected-warning{{local variable 'local' is still referred to by the static variable 'arr'}} + +void param_arr_of_ptr_top(void* arr[2]) { + int local = 42; + int* p = &local; + arr[1] = p; // no-warning FIXME +} + +void param_arr_of_ptr_callee(void* arr[2]) { + int local = 42; + int* p = &local; + arr[1] = p; // expected-warning{{local variable 'local' is still referred to by the stack variable 'arrStack'}} +} + +void param_arr_of_ptr_caller() { + void* arrStack[2]; + param_arr_of_ptr_callee(arrStack); + (void)arrStack[1]; +} +} // namespace leaking_via_arr_of_ptr_static_idx + +namespace leaking_via_arr_of_ptr_dynamic_idx { +void** returned_arr_of_ptr_top(int idx) { + int local = 42; + int* p = &local; + void** arr = new void*[2]; + arr[idx] = p; + return arr; +} // no-warning False Negative + +void** returned_arr_of_ptr_callee(int idx) { + int local = 42; + int* p = &local; + void** arr = new void*[2]; + arr[idx] = p; + return arr; +} // no-warning False Negative + +void returned_arr_of_ptr_caller(int idx) { + void** arr = returned_arr_of_ptr_callee(idx); + (void)arr[idx]; +} + +void* global_aop[2]; + +void global_arr_of_ptr(int idx) { + int local = 42; + int* p = &local; + global_aop[idx] = p; +} // expected-warning{{local variable 'local' is still referred to by the global variable 'global_aop'}} + +void static_arr_of_ptr(int idx) { + int local = 42; + static void* arr[2]; + arr[idx] = &local; + (void)arr[idx]; +} // expected-warning{{local variable 'local' is still referred to by the static variable 'arr'}} + +void param_arr_of_ptr_top(void* arr[2], int idx) { + int local = 42; + int* p = &local; + arr[idx] = p; // no-warning FIXME +} + +void param_arr_of_ptr_callee(void* arr[2], int idx) { + int local = 42; + int* p = &local; + arr[idx] = p; // expected-warning{{local variable 'local' is still referred to by the stack variable 'arrStack'}} +} + +void param_arr_of_ptr_caller(int idx) { + void* arrStack[2]; + param_arr_of_ptr_callee(arrStack, idx); + (void)arrStack[idx]; +} +} // namespace leaking_via_arr_of_ptr_dynamic_idx + +namespace leaking_via_struct_with_ptr { +struct S { + int* p; +}; + +S returned_struct_with_ptr_top() { + int local = 42; + S s; + s.p = &local; + return s; +} // no-warning False Negative, requires traversing returned LazyCompoundVals + +S returned_struct_with_ptr_callee() { + int local = 42; + S s; + s.p = &local; + return s; // expected-warning{{'local' is still referred to by the stack variable 's'}} +} + +void returned_struct_with_ptr_caller() { + S s = returned_struct_with_ptr_callee(); + (void)s.p; +} + +S global_s; + +void global_struct_with_ptr() { + int local = 42; + global_s.p = &local; +} // expected-warning{{'local' is still referred to by the global variable 'global_s'}} + +void static_struct_with_ptr() { + int local = 42; + static S s; + s.p = &local; + (void)s.p; +} // expected-warning{{'local' is still referred to by the static variable 's'}} +} // namespace leaking_via_struct_with_ptr + +namespace leaking_via_ref_to_struct_with_ptr { +struct S { + int* p; +}; + +S &global_s = *(new S); + +void global_ref_to_struct_with_ptr() { + int local = 42; + global_s.p = &local; // no-warning FIXME +} + +void static_ref_to_struct_with_ptr() { + int local = 42; + static S &s = *(new S); + s.p = &local; + (void)s.p; +} // no-warning False Negative, requires relating multiple bindings to cross a heap region. + +void param_ref_to_struct_with_ptr_top(S &s) { + int local = 42; + s.p = &local; // no-warning FIXME +} + +void param_ref_to_struct_with_ptr_callee(S &s) { + int local = 42; + s.p = &local; // expected-warning{{'local' is still referred to by the stack variable 'sStack'}} +} + +void param_ref_to_struct_with_ptr_caller() { + S sStack; + param_ref_to_struct_with_ptr_callee(sStack); +} + +template<typename Callable> +void lambda_param_capture_callee(Callable& callee) { + int local = 42; + callee(local); // expected-warning{{'local' is still referred to by the stack variable 'p'}} +} + +void lambda_param_capture_caller() { + int* p = nullptr; + auto capt = [&p](int& param) { + p = ¶m; + }; + lambda_param_capture_callee(capt); +} +} // namespace leaking_via_ref_to_struct_with_ptr + +namespace leaking_via_ptr_to_struct_with_ptr { +struct S { + int* p; +}; + +S* returned_ptr_to_struct_with_ptr_top() { + int local = 42; + S* s = new S; + s->p = &local; + return s; +} // no-warning False Negative + +S* returned_ptr_to_struct_with_ptr_callee() { + int local = 42; + S* s = new S; + s->p = &local; + return s; +} // no-warning False Negative + +void returned_ptr_to_struct_with_ptr_caller() { + S* s = returned_ptr_to_struct_with_ptr_callee(); + (void)s->p; +} + +S* global_s; + +void global_ptr_to_struct_with_ptr() { + int local = 42; + global_s->p = &local; // no-warning FIXME +} + +void static_ptr_to_struct_with_ptr_new() { + int local = 42; + static S* s = new S; + s->p = &local; + (void)s->p; +} // no-warning False Negative, requires relating multiple bindings to cross a heap region. + +S* get_some_s(); + +void static_ptr_to_struct_with_ptr_generated() { + int local = 42; + static S* s = get_some_s(); + s->p = &local; +} // no-warning False Negative, requires relating multiple bindings to cross the invented pointer. + +void param_ptr_to_struct_with_ptr_top(S* s) { + int local = 42; + s->p = &local; // no-warning FIXME +} + +void param_ptr_to_struct_with_ptr_callee(S* s) { + int local = 42; + s->p = &local; // expected-warning{{'local' is still referred to by the stack variable 's'}} +} + +void param_ptr_to_struct_with_ptr_caller() { + S s; + param_ptr_to_struct_with_ptr_callee(&s); + (void)s.p; +} +} // namespace leaking_via_ptr_to_struct_with_ptr + +namespace leaking_via_arr_of_struct_with_ptr { +struct S { + int* p; +}; + +S* returned_ptr_to_struct_with_ptr_top() { + int local = 42; + S* s = new S[2]; + s[1].p = &local; + return s; +} // no-warning False Negative + +S* returned_ptr_to_struct_with_ptr_callee() { + int local = 42; + S* s = new S[2]; + s[1].p = &local; + return s; +} // no-warning False Negative + +void returned_ptr_to_struct_with_ptr_caller() { + S* s = returned_ptr_to_struct_with_ptr_callee(); + (void)s[1].p; +} + +S global_s[2]; + +void global_ptr_to_struct_with_ptr() { + int local = 42; + global_s[1].p = &local; +} // expected-warning{{'local' is still referred to by the global variable 'global_s'}} + +void static_ptr_to_struct_with_ptr_new() { + int local = 42; + static S* s = new S[2]; + s[1].p = &local; + (void)s[1].p; +} + +S* get_some_s(); + +void static_ptr_to_struct_with_ptr_generated() { + int local = 42; + static S* s = get_some_s(); + s[1].p = &local; +} // no-warning False Negative, requires relating multiple bindings to cross the invented pointer. + +void param_ptr_to_struct_with_ptr_top(S s[2]) { + int local = 42; + s[1].p = &local; // no-warning FIXME +} + +void param_ptr_to_struct_with_ptr_callee(S s[2]) { + int local = 42; + s[1].p = &local; // expected-warning{{'local' is still referred to by the stack variable 's'}} +} + +void param_ptr_to_struct_with_ptr_caller() { + S s[2]; + param_ptr_to_struct_with_ptr_callee(s); + (void)s[1].p; +} +} // namespace leaking_via_arr_of_struct_with_ptr + +namespace leaking_via_nested_and_indirect { +struct NestedAndTransitive { + int** p; + NestedAndTransitive* next[3]; +}; + +NestedAndTransitive global_nat; + +void global_nested_and_transitive() { + int local = 42; + *global_nat.next[2]->next[1]->p = &local; // no-warning FIXME +} + +void param_nested_and_transitive_top(NestedAndTransitive* nat) { + int local = 42; + *nat->next[2]->next[1]->p = &local; // no-warning FIXME +} + +void param_nested_and_transitive_callee(NestedAndTransitive* nat) { + int local = 42; + *nat->next[2]->next[1]->p = &local; // no-warning FIXME +} + +void param_nested_and_transitive_caller(NestedAndTransitive natCaller) { + param_nested_and_transitive_callee(&natCaller); +} + +} // namespace leaking_via_nested_and_indirect + +namespace leaking_as_member { +class CRef { + int& ref; // expected-note{{reference member declared here}} + CRef(int x) : ref(x) {} + // expected-warning@-1 {{binding reference member 'ref' to stack allocated parameter 'x'}} +}; + +class CPtr { + int* ptr; + void memFun(int x) { + ptr = &x; + } +}; +} // namespace leaking_as_member >From 46379fcb9a3b6c8fc8a0705b4e4a18a0440f920f Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <necto...@gmail.com> Date: Tue, 20 Aug 2024 10:53:25 +0200 Subject: [PATCH 2/3] [analyzer] Detect leak of a stack address through output arguments At this point only functions called from other functions (i.e., not top-level) are covered. Top-level functions have a different exit sequence, and will be handled by a subsequent change. CPP-4734 --- .../Checkers/StackAddrEscapeChecker.cpp | 62 ++++++++++++++----- clang/test/Analysis/stack-addr-ps.cpp | 6 +- 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp index 2bd4ca4528de8b..a704c4ff2eeb02 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp @@ -288,6 +288,23 @@ void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS, EmitStackError(C, R, RetE); } +static const MemSpaceRegion *getStackOrGlobalSpaceRegion(const MemRegion *R) { + assert(R); + if (const auto *MemSpace = R->getMemorySpace()) { + if (const auto *SSR = MemSpace->getAs<StackSpaceRegion>()) + return SSR; + if (const auto *GSR = MemSpace->getAs<GlobalsSpaceRegion>()) + return GSR; + } + // If R describes a lambda capture, it will be a symbolic region + // referring to a field region of another symbolic region. + if (const auto *SymReg = R->getBaseRegion()->getAs<SymbolicRegion>()) { + if (const auto *OriginReg = SymReg->getSymbol()->getOriginRegion()) + return getStackOrGlobalSpaceRegion(OriginReg); + } + return nullptr; +} + std::optional<std::string> printReferrer(const MemRegion *Referrer) { assert(Referrer); const StringRef ReferrerMemorySpace = [](const MemSpaceRegion *Space) { @@ -297,20 +314,29 @@ std::optional<std::string> printReferrer(const MemRegion *Referrer) { return "global"; assert(isa<StackSpaceRegion>(Space)); return "stack"; - }(Referrer->getMemorySpace()); - - // We should really only have VarRegions here. - // Anything else is really surprising, and we should get notified if such - // ever happens. - const auto *ReferrerVar = dyn_cast<VarRegion>(Referrer); - if (!ReferrerVar) { - assert(false && "We should have a VarRegion here"); - return std::nullopt; // Defensively skip this one. + }(getStackOrGlobalSpaceRegion(Referrer)); + + while (!Referrer->canPrintPretty()) { + if (const auto *SymReg = dyn_cast<SymbolicRegion>(Referrer)) { + Referrer = SymReg->getSymbol()->getOriginRegion()->getBaseRegion(); + } else if (isa<CXXThisRegion>(Referrer)) { + // Skip members of a class, it is handled by + // warn_bind_ref_member_to_parameter_addr + return std::nullopt; + } else { + Referrer->dump(); + assert(false && "Unexpected referrer region type."); + return std::nullopt; + } } - const std::string ReferrerVarName = - ReferrerVar->getDecl()->getDeclName().getAsString(); + assert(Referrer); + assert(Referrer->canPrintPretty()); - return (ReferrerMemorySpace + " variable '" + ReferrerVarName + "'").str(); + std::string buf; + llvm::raw_string_ostream os(buf); + os << ReferrerMemorySpace << " variable "; + Referrer->printPretty(os); + return buf; } void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, @@ -332,16 +358,20 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, /// referred by an other stack variable from different stack frame. bool checkForDanglingStackVariable(const MemRegion *Referrer, const MemRegion *Referred) { - const auto *ReferrerMemSpace = - Referrer->getMemorySpace()->getAs<StackSpaceRegion>(); + const auto *ReferrerMemSpace = getStackOrGlobalSpaceRegion(Referrer); const auto *ReferredMemSpace = Referred->getMemorySpace()->getAs<StackSpaceRegion>(); if (!ReferrerMemSpace || !ReferredMemSpace) return false; + const auto *ReferrerStackSpace = + ReferrerMemSpace->getAs<StackSpaceRegion>(); + if (!ReferrerStackSpace) + return false; + if (ReferredMemSpace->getStackFrame() == PoppedFrame && - ReferrerMemSpace->getStackFrame()->isParentOf(PoppedFrame)) { + ReferrerStackSpace->getStackFrame()->isParentOf(PoppedFrame)) { V.emplace_back(Referrer, Referred); return true; } @@ -387,7 +417,7 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, if (!BT_stackleak) BT_stackleak = std::make_unique<BugType>(CheckNames[CK_StackAddrEscapeChecker], - "Stack address stored into global variable"); + "Stack address leaks outside of stack frame"); for (const auto &P : Cb.V) { const MemRegion *Referrer = P.first->getBaseRegion(); diff --git a/clang/test/Analysis/stack-addr-ps.cpp b/clang/test/Analysis/stack-addr-ps.cpp index 68ccc322bf2ef2..b8bd70ff5d98f5 100644 --- a/clang/test/Analysis/stack-addr-ps.cpp +++ b/clang/test/Analysis/stack-addr-ps.cpp @@ -321,7 +321,7 @@ void param_ptr_to_ptr_to_ptr_top(void*** ppp) { void param_ptr_to_ptr_to_ptr_callee(void*** ppp) { int local = 42; - **ppp = &local; // no-warning FIXME + **ppp = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'pp'}} } void param_ptr_to_ptr_to_ptr_caller(void** pp) { @@ -331,7 +331,7 @@ void param_ptr_to_ptr_to_ptr_caller(void** pp) { void lambda_to_context_ptr_to_ptr(int **pp) { auto lambda = [&] { int local = 42; - *pp = &local; // no-warning FIXME + *pp = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'pp'}} }; lambda(); (void)*pp; @@ -734,7 +734,7 @@ void param_nested_and_transitive_top(NestedAndTransitive* nat) { void param_nested_and_transitive_callee(NestedAndTransitive* nat) { int local = 42; - *nat->next[2]->next[1]->p = &local; // no-warning FIXME + *nat->next[2]->next[1]->p = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'natCaller'}} } void param_nested_and_transitive_caller(NestedAndTransitive natCaller) { >From 6635877dbd1b6e73d98552a21860efd9519385e3 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <necto...@gmail.com> Date: Tue, 20 Aug 2024 11:16:10 +0200 Subject: [PATCH 3/3] [analyzer] Detect leaks on top-level via output params, indirect globals Fix some false negatives of StackAddrEscapeChecker: - Output parameters ``` void top(int **out) { int local = 42; *out = &local; // Noncompliant } ``` - Indirect global pointers ``` int **global; void top() { int local = 42; *global = &local; // Noncompliant } ``` Note that now StackAddrEscapeChecker produces a diagnostic if a function with an output parameter is analyzed as top-level or as a callee. I took special care to make sure the reports point to the same primary location and, in many cases, feature the same primary message. That is the motivation to modify Core/BugReporter.cpp and Core/ExplodedGraph.cpp To avoid false positive reports when a global indirect pointer is assigned a local address, invalidated, and then reset, I rely on the fact that the invalidation symbol will be a DerivedSymbol of a ConjuredSymbol that refers to the same memory region. The checker still has a false negative for non-trivial escaping via a returned value. It requires a more sophisticated traversal akin to scanReachableSymbols, which out of the scope of this change. CPP-4734 --- .../Checkers/StackAddrEscapeChecker.cpp | 69 +++++++++-- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 13 +- .../lib/StaticAnalyzer/Core/ExplodedGraph.cpp | 2 +- clang/test/Analysis/copy-elision.cpp | 20 +-- .../test/Analysis/incorrect-checker-names.cpp | 4 +- clang/test/Analysis/loop-block-counts.c | 2 +- clang/test/Analysis/stack-addr-ps.c | 6 +- clang/test/Analysis/stack-addr-ps.cpp | 115 ++++++++++-------- .../Analysis/stack-capture-leak-no-arc.mm | 4 +- clang/test/Analysis/stackaddrleak.c | 8 +- 10 files changed, 160 insertions(+), 83 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp index a704c4ff2eeb02..ef477a808fac17 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp @@ -305,6 +305,14 @@ static const MemSpaceRegion *getStackOrGlobalSpaceRegion(const MemRegion *R) { return nullptr; } +const MemRegion *getOriginBaseRegion(const MemRegion *Referrer) { + Referrer = Referrer->getBaseRegion(); + while (const auto *SymReg = dyn_cast<SymbolicRegion>(Referrer)) { + Referrer = SymReg->getSymbol()->getOriginRegion()->getBaseRegion(); + } + return Referrer; +} + std::optional<std::string> printReferrer(const MemRegion *Referrer) { assert(Referrer); const StringRef ReferrerMemorySpace = [](const MemSpaceRegion *Space) { @@ -312,8 +320,12 @@ std::optional<std::string> printReferrer(const MemRegion *Referrer) { return "static"; if (isa<GlobalsSpaceRegion>(Space)) return "global"; - assert(isa<StackSpaceRegion>(Space)); - return "stack"; + // UnknownSpaceRegion usually refers to pointers-to-pointers, + // might be in top frame or not with pointers-to-pointers-to-pointers + assert((isa<UnknownSpaceRegion, StackSpaceRegion>(Space))); + // These two cases are deliberately combined to keep the message identical + // between the top-level and inlined analysis of the same function + return "caller"; }(getStackOrGlobalSpaceRegion(Referrer)); while (!Referrer->canPrintPretty()) { @@ -346,12 +358,27 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, ExplodedNode *Node = Ctx.getPredecessor(); + bool ExitingTopFrame = + Ctx.getPredecessor()->getLocationContext()->inTopFrame(); + + if (ExitingTopFrame && Node->getLocation().getTag() && + Node->getLocation().getTag()->getTagDescription() == + "ExprEngine : Clean Node" && + Node->getFirstPred()) { + // When finishing analysis of a top-level function, engine proactively + // removes dead symbols thus preventing this checker from looking through + // the output parameters. Take 1 step back, to the node where these symbols + // and their bindings are still present + Node = Node->getFirstPred(); + } + // Iterate over all bindings to global variables and see if it contains // a memory region in the stack space. class CallBack : public StoreManager::BindingsHandler { private: CheckerContext &Ctx; const StackFrameContext *PoppedFrame; + const bool TopFrame; /// Look for stack variables referring to popped stack variables. /// Returns true only if it found some dangling stack variables @@ -367,24 +394,48 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, const auto *ReferrerStackSpace = ReferrerMemSpace->getAs<StackSpaceRegion>(); + if (!ReferrerStackSpace) return false; - if (ReferredMemSpace->getStackFrame() == PoppedFrame && - ReferrerStackSpace->getStackFrame()->isParentOf(PoppedFrame)) { + if (const auto *ReferredFrame = ReferredMemSpace->getStackFrame(); + ReferredFrame != PoppedFrame) { + return false; + } + + if (ReferrerStackSpace->getStackFrame()->isParentOf(PoppedFrame)) { + V.emplace_back(Referrer, Referred); + return true; + } + if (isa<StackArgumentsSpaceRegion>(ReferrerMemSpace) && + ReferrerStackSpace->getStackFrame() == PoppedFrame && TopFrame) { + // Output parameter of a top-level function V.emplace_back(Referrer, Referred); return true; } return false; } + void updateInvalidatedRegions(const MemRegion *Region) { + if (const auto *SymReg = Region->getAs<SymbolicRegion>()) { + SymbolRef Symbol = SymReg->getSymbol(); + if (const auto *DerS = dyn_cast<SymbolDerived>(Symbol); + DerS && isa_and_nonnull<SymbolConjured>(DerS->getParentSymbol())) { + InvalidatedRegions.insert(Symbol->getOriginRegion()->getBaseRegion()); + } + } + } + public: SmallVector<std::pair<const MemRegion *, const MemRegion *>, 10> V; + llvm::SmallPtrSet<const MemRegion *, 4> InvalidatedRegions; - CallBack(CheckerContext &CC) : Ctx(CC), PoppedFrame(CC.getStackFrame()) {} + CallBack(CheckerContext &CC, bool TopFrame) + : Ctx(CC), PoppedFrame(CC.getStackFrame()), TopFrame(TopFrame) {} bool HandleBinding(StoreManager &SMgr, Store S, const MemRegion *Region, SVal Val) override { + updateInvalidatedRegions(Region); const MemRegion *VR = Val.getAsRegion(); if (!VR) return true; @@ -393,7 +444,8 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, return true; // Check the globals for the same. - if (!isa<GlobalsSpaceRegion>(Region->getMemorySpace())) + if (!isa_and_nonnull<GlobalsSpaceRegion>( + getStackOrGlobalSpaceRegion(Region))) return true; if (VR && VR->hasStackStorage() && !isNotInCurrentFrame(VR, Ctx)) V.emplace_back(Region, VR); @@ -401,7 +453,7 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, } }; - CallBack Cb(Ctx); + CallBack Cb(Ctx, ExitingTopFrame); ProgramStateRef State = Node->getState(); State->getStateManager().getStoreManager().iterBindings(State->getStore(), Cb); @@ -422,6 +474,9 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, for (const auto &P : Cb.V) { const MemRegion *Referrer = P.first->getBaseRegion(); const MemRegion *Referred = P.second; + if (Cb.InvalidatedRegions.contains(getOriginBaseRegion(Referrer))) { + continue; + } // Generate a report for this bug. const StringRef CommonSuffix = diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index d73dc40cf03fbb..ea916c3585cadc 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -2436,8 +2436,19 @@ PathSensitiveBugReport::getLocation() const { if (auto FE = P.getAs<FunctionExitPoint>()) { if (const ReturnStmt *RS = FE->getStmt()) return PathDiagnosticLocation::createBegin(RS, SM, LC); + + // If we are exiting a destructor call, it is more useful to point to the + // next stmt which is usually the temporary declaration. + // For non-destructor and non-top-level calls, the next stmt will still + // refer to the last executed stmt of the body. + S = ErrorNode->getNextStmtForDiagnostics(); + // If next stmt is not found, it is likely the end of a top-level function + // analysis. find the last execution statement then. + if (!S) + S = ErrorNode->getPreviousStmtForDiagnostics(); } - S = ErrorNode->getNextStmtForDiagnostics(); + if (!S) + S = ErrorNode->getNextStmtForDiagnostics(); } if (S) { diff --git a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp index f84da769d182f8..11cef00ada330b 100644 --- a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp @@ -376,7 +376,7 @@ const Stmt *ExplodedNode::getNextStmtForDiagnostics() const { const Stmt *ExplodedNode::getPreviousStmtForDiagnostics() const { for (const ExplodedNode *N = getFirstPred(); N; N = N->getFirstPred()) - if (const Stmt *S = N->getStmtForDiagnostics()) + if (const Stmt *S = N->getStmtForDiagnostics(); S && !isa<CompoundStmt>(S)) return S; return nullptr; diff --git a/clang/test/Analysis/copy-elision.cpp b/clang/test/Analysis/copy-elision.cpp index 991f325c05853d..423c4519f5bfc6 100644 --- a/clang/test/Analysis/copy-elision.cpp +++ b/clang/test/Analysis/copy-elision.cpp @@ -158,19 +158,19 @@ ClassWithoutDestructor make1(AddressVector<ClassWithoutDestructor> &v) { return ClassWithoutDestructor(v); // no-elide-warning@-1 {{Address of stack memory associated with temporary \ object of type 'ClassWithoutDestructor' is still \ -referred to by the stack variable 'v' upon returning to the caller}} +referred to by the caller variable 'v' upon returning to the caller}} } ClassWithoutDestructor make2(AddressVector<ClassWithoutDestructor> &v) { return make1(v); // no-elide-warning@-1 {{Address of stack memory associated with temporary \ object of type 'ClassWithoutDestructor' is still \ -referred to by the stack variable 'v' upon returning to the caller}} +referred to by the caller variable 'v' upon returning to the caller}} } ClassWithoutDestructor make3(AddressVector<ClassWithoutDestructor> &v) { return make2(v); // no-elide-warning@-1 {{Address of stack memory associated with temporary \ object of type 'ClassWithoutDestructor' is still \ -referred to by the stack variable 'v' upon returning to the caller}} +referred to by the caller variable 'v' upon returning to the caller}} } void testMultipleReturns() { @@ -193,7 +193,7 @@ void testMultipleReturns() { void consume(ClassWithoutDestructor c) { c.push(); // expected-warning@-1 {{Address of stack memory associated with local \ -variable 'c' is still referred to by the stack variable 'v' upon returning \ +variable 'c' is still referred to by the caller variable 'v' upon returning \ to the caller}} } @@ -267,7 +267,7 @@ struct TestCtorInitializer { : c(ClassWithDestructor(v)) {} // no-elide-warning@-1 {{Address of stack memory associated with temporary \ object of type 'ClassWithDestructor' is still referred \ -to by the stack variable 'v' upon returning to the caller}} +to by the caller variable 'v' upon returning to the caller}} }; void testCtorInitializer() { @@ -303,19 +303,19 @@ ClassWithDestructor make1(AddressVector<ClassWithDestructor> &v) { return ClassWithDestructor(v); // no-elide-warning@-1 {{Address of stack memory associated with temporary \ object of type 'ClassWithDestructor' is still referred \ -to by the stack variable 'v' upon returning to the caller}} +to by the caller variable 'v' upon returning to the caller}} } ClassWithDestructor make2(AddressVector<ClassWithDestructor> &v) { return make1(v); // no-elide-warning@-1 {{Address of stack memory associated with temporary \ object of type 'ClassWithDestructor' is still referred \ -to by the stack variable 'v' upon returning to the caller}} +to by the caller variable 'v' upon returning to the caller}} } ClassWithDestructor make3(AddressVector<ClassWithDestructor> &v) { return make2(v); // no-elide-warning@-1 {{Address of stack memory associated with temporary \ object of type 'ClassWithDestructor' is still referred \ -to by the stack variable 'v' upon returning to the caller}} +to by the caller variable 'v' upon returning to the caller}} } void testMultipleReturnsWithDestructors() { @@ -360,7 +360,7 @@ void testMultipleReturnsWithDestructors() { void consume(ClassWithDestructor c) { c.push(); // expected-warning@-1 {{Address of stack memory associated with local \ -variable 'c' is still referred to by the stack variable 'v' upon returning \ +variable 'c' is still referred to by the caller variable 'v' upon returning \ to the caller}} } @@ -407,7 +407,7 @@ struct Foo { Foo make1(Foo **r) { return Foo(r); // no-elide-warning@-1 {{Address of stack memory associated with temporary \ -object of type 'Foo' is still referred to by the stack \ +object of type 'Foo' is still referred to by the caller \ variable 'z' upon returning to the caller}} } diff --git a/clang/test/Analysis/incorrect-checker-names.cpp b/clang/test/Analysis/incorrect-checker-names.cpp index 9854a503fc4f62..8dee0b6f468581 100644 --- a/clang/test/Analysis/incorrect-checker-names.cpp +++ b/clang/test/Analysis/incorrect-checker-names.cpp @@ -16,5 +16,5 @@ char const *p; void f0() { char const str[] = "This will change"; p = str; -} // expected-warning{{Address of stack memory associated with local variable 'str' is still referred to by the global variable 'p' upon returning to the caller. This will be a dangling reference [core.StackAddressEscape]}} -// expected-note@-1{{Address of stack memory associated with local variable 'str' is still referred to by the global variable 'p' upon returning to the caller. This will be a dangling reference}} +} // expected-warning@-1{{Address of stack memory associated with local variable 'str' is still referred to by the global variable 'p' upon returning to the caller. This will be a dangling reference [core.StackAddressEscape]}} +// expected-note@-2{{Address of stack memory associated with local variable 'str' is still referred to by the global variable 'p' upon returning to the caller. This will be a dangling reference}} diff --git a/clang/test/Analysis/loop-block-counts.c b/clang/test/Analysis/loop-block-counts.c index 9af67b1b632f51..ae3705f8bd7ccc 100644 --- a/clang/test/Analysis/loop-block-counts.c +++ b/clang/test/Analysis/loop-block-counts.c @@ -6,7 +6,7 @@ void callee(void **p) { int x; *p = &x; // expected-warning@-1 {{Address of stack memory associated with local \ -variable 'x' is still referred to by the stack variable 'arr' upon \ +variable 'x' is still referred to by the caller variable 'arr' upon \ returning to the caller}} } diff --git a/clang/test/Analysis/stack-addr-ps.c b/clang/test/Analysis/stack-addr-ps.c index 2e14b7820be136..138b8c16b02bde 100644 --- a/clang/test/Analysis/stack-addr-ps.c +++ b/clang/test/Analysis/stack-addr-ps.c @@ -98,13 +98,13 @@ void callTestRegister(void) { void top_level_leaking(int **out) { int local = 42; - *out = &local; // no-warning FIXME + *out = &local; // expected-warning{{Address of stack memory associated with local variable 'local' is still referred to by the caller variable 'out'}} } void callee_leaking_via_param(int **out) { int local = 1; *out = &local; - // expected-warning@-1{{Address of stack memory associated with local variable 'local' is still referred to by the stack variable 'ptr'}} + // expected-warning@-1{{Address of stack memory associated with local variable 'local' is still referred to by the caller variable 'ptr'}} } void caller_for_leaking_callee() { @@ -115,7 +115,7 @@ void caller_for_leaking_callee() { void callee_nested_leaking(int **out) { int local = 1; *out = &local; - // expected-warning@-1{{Address of stack memory associated with local variable 'local' is still referred to by the stack variable 'ptr'}} + // expected-warning@-1{{Address of stack memory associated with local variable 'local' is still referred to by the caller variable 'ptr'}} } void caller_mid_for_nested_leaking(int **mid) { diff --git a/clang/test/Analysis/stack-addr-ps.cpp b/clang/test/Analysis/stack-addr-ps.cpp index b8bd70ff5d98f5..b98fc3beade761 100644 --- a/clang/test/Analysis/stack-addr-ps.cpp +++ b/clang/test/Analysis/stack-addr-ps.cpp @@ -140,7 +140,7 @@ void write_stack_address_to(char **q) { char local; *q = &local; // expected-warning@-1 {{Address of stack memory associated with local \ -variable 'local' is still referred to by the stack variable 'p' upon \ +variable 'local' is still referred to by the caller variable 'p' upon \ returning to the caller}} } @@ -185,14 +185,14 @@ void* global_ptr; void global_direct_pointer() { int local = 42; - global_ptr = &local; -} // expected-warning{{local variable 'local' is still referred to by the global variable 'global_ptr'}} + global_ptr = &local; // expected-warning{{local variable 'local' is still referred to by the global variable 'global_ptr'}} +} void static_direct_pointer_top() { int local = 42; static int* p = &local; - (void)p; -} // expected-warning{{local variable 'local' is still referred to by the static variable 'p'}} + (void)p; // expected-warning{{local variable 'local' is still referred to by the static variable 'p'}} +} void static_direct_pointer_callee() { int local = 42; @@ -216,7 +216,7 @@ void lambda_to_context_direct_pointer() { int *p = nullptr; auto lambda = [&] { int local = 42; - p = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'p'}} + p = &local; // expected-warning{{local variable 'local' is still referred to by the caller variable 'p'}} }; lambda(); (void)p; @@ -242,7 +242,7 @@ void lambda_to_context_direct_pointer_lifetime_extended() { int *p = nullptr; auto lambda = [&] { int&& local = 42; - p = &local; // expected-warning{{'int' lifetime extended by local variable 'local' is still referred to by the stack variable 'p'}} + p = &local; // expected-warning{{'int' lifetime extended by local variable 'local' is still referred to by the caller variable 'p'}} }; lambda(); (void)p; @@ -251,7 +251,7 @@ void lambda_to_context_direct_pointer_lifetime_extended() { template<typename Callback> void lambda_param_capture_direct_pointer_callee(Callback& callee) { int local = 42; - callee(local); // expected-warning{{'local' is still referred to by the stack variable 'p'}} + callee(local); // expected-warning{{'local' is still referred to by the caller variable 'p'}} } void lambda_param_capture_direct_pointer_caller() { @@ -276,19 +276,19 @@ void** global_pp; void global_ptr_local_to_ptr() { int local = 42; int* p = &local; - global_pp = (void**)&p; -} // expected-warning{{local variable 'p' is still referred to by the global variable 'global_pp'}} + global_pp = (void**)&p; // expected-warning{{local variable 'p' is still referred to by the global variable 'global_pp'}} +} void global_ptr_to_ptr() { int local = 42; - *global_pp = &local; // no-warning FIXME + *global_pp = &local; // expected-warning{{local variable 'local' is still referred to by the global variable 'global_pp'}} } void *** global_ppp; void global_ptr_to_ptr_to_ptr() { int local = 42; - **global_ppp = &local; // no-warning FIXME + **global_ppp = &local; // expected-warning{{local variable 'local' is still referred to by the global variable 'global_ppp'}} } void** get_some_pp(); @@ -301,12 +301,12 @@ void static_ptr_to_ptr() { void param_ptr_to_ptr_top(void** pp) { int local = 42; - *pp = &local; // no-warning FIXME + *pp = &local; // expected-warning{{local variable 'local' is still referred to by the caller variable 'pp'}} } void param_ptr_to_ptr_callee(void** pp) { int local = 42; - *pp = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'p'}} + *pp = &local; // expected-warning{{local variable 'local' is still referred to by the caller variable 'p'}} } void param_ptr_to_ptr_caller() { @@ -316,12 +316,12 @@ void param_ptr_to_ptr_caller() { void param_ptr_to_ptr_to_ptr_top(void*** ppp) { int local = 42; - **ppp = &local; // no-warning FIXME + **ppp = &local; // expected-warning {{local variable 'local' is still referred to by the caller variable 'ppp'}} } void param_ptr_to_ptr_to_ptr_callee(void*** ppp) { int local = 42; - **ppp = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'pp'}} + **ppp = &local; // expected-warning{{local variable 'local' is still referred to by the caller variable 'pp'}} } void param_ptr_to_ptr_to_ptr_caller(void** pp) { @@ -331,7 +331,7 @@ void param_ptr_to_ptr_to_ptr_caller(void** pp) { void lambda_to_context_ptr_to_ptr(int **pp) { auto lambda = [&] { int local = 42; - *pp = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'pp'}} + *pp = &local; // expected-warning{{local variable 'local' is still referred to by the caller variable 'pp'}} }; lambda(); (void)*pp; @@ -339,7 +339,7 @@ void lambda_to_context_ptr_to_ptr(int **pp) { void param_ptr_to_ptr_fptr(int **pp) { int local = 42; - *pp = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'p'}} + *pp = &local; // expected-warning{{local variable 'local' is still referred to by the caller variable 'p'}} } void param_ptr_to_ptr_fptr_caller(void (*fptr)(int**)) { @@ -360,7 +360,7 @@ void*& global_rtp = *make_ptr_to_ptr(); void global_ref_to_ptr() { int local = 42; int* p = &local; - global_rtp = p; // no-warning FIXME + global_rtp = p; // expected-warning{{local variable 'local' is still referred to by the global variable 'global_rtp'}} } void static_ref_to_ptr() { @@ -373,13 +373,13 @@ void static_ref_to_ptr() { void param_ref_to_ptr_top(void*& rp) { int local = 42; int* p = &local; - rp = p; // no-warning FIXME + rp = p; // expected-warning{{local variable 'local' is still referred to by the caller variable 'rp'}} } void param_ref_to_ptr_callee(void*& rp) { int local = 42; int* p = &local; - rp = p; // expected-warning{{local variable 'local' is still referred to by the stack variable 'p'}} + rp = p; // expected-warning{{local variable 'local' is still referred to by the caller variable 'p'}} } void param_ref_to_ptr_caller() { @@ -415,26 +415,26 @@ void* global_aop[2]; void global_arr_of_ptr() { int local = 42; int* p = &local; - global_aop[1] = p; -} // expected-warning{{local variable 'local' is still referred to by the global variable 'global_aop'}} + global_aop[1] = p; // expected-warning{{local variable 'local' is still referred to by the global variable 'global_aop'}} +} void static_arr_of_ptr() { int local = 42; static void* arr[2]; arr[1] = &local; - (void)arr[1]; -} // expected-warning{{local variable 'local' is still referred to by the static variable 'arr'}} + (void)arr[1]; // expected-warning{{local variable 'local' is still referred to by the static variable 'arr'}} +} void param_arr_of_ptr_top(void* arr[2]) { int local = 42; int* p = &local; - arr[1] = p; // no-warning FIXME + arr[1] = p; // expected-warning{{local variable 'local' is still referred to by the caller variable 'arr'}} } void param_arr_of_ptr_callee(void* arr[2]) { int local = 42; int* p = &local; - arr[1] = p; // expected-warning{{local variable 'local' is still referred to by the stack variable 'arrStack'}} + arr[1] = p; // expected-warning{{local variable 'local' is still referred to by the caller variable 'arrStack'}} } void param_arr_of_ptr_caller() { @@ -471,26 +471,26 @@ void* global_aop[2]; void global_arr_of_ptr(int idx) { int local = 42; int* p = &local; - global_aop[idx] = p; -} // expected-warning{{local variable 'local' is still referred to by the global variable 'global_aop'}} + global_aop[idx] = p; // expected-warning{{local variable 'local' is still referred to by the global variable 'global_aop'}} +} void static_arr_of_ptr(int idx) { int local = 42; static void* arr[2]; arr[idx] = &local; - (void)arr[idx]; -} // expected-warning{{local variable 'local' is still referred to by the static variable 'arr'}} + (void)arr[idx]; // expected-warning{{local variable 'local' is still referred to by the static variable 'arr'}} +} void param_arr_of_ptr_top(void* arr[2], int idx) { int local = 42; int* p = &local; - arr[idx] = p; // no-warning FIXME + arr[idx] = p; // expected-warning{{local variable 'local' is still referred to by the caller variable 'arr'}} } void param_arr_of_ptr_callee(void* arr[2], int idx) { int local = 42; int* p = &local; - arr[idx] = p; // expected-warning{{local variable 'local' is still referred to by the stack variable 'arrStack'}} + arr[idx] = p; // expected-warning{{local variable 'local' is still referred to by the caller variable 'arrStack'}} } void param_arr_of_ptr_caller(int idx) { @@ -516,7 +516,7 @@ S returned_struct_with_ptr_callee() { int local = 42; S s; s.p = &local; - return s; // expected-warning{{'local' is still referred to by the stack variable 's'}} + return s; // expected-warning{{'local' is still referred to by the caller variable 's'}} } void returned_struct_with_ptr_caller() { @@ -528,15 +528,15 @@ S global_s; void global_struct_with_ptr() { int local = 42; - global_s.p = &local; -} // expected-warning{{'local' is still referred to by the global variable 'global_s'}} + global_s.p = &local; // expected-warning{{'local' is still referred to by the global variable 'global_s'}} +} void static_struct_with_ptr() { int local = 42; static S s; s.p = &local; - (void)s.p; -} // expected-warning{{'local' is still referred to by the static variable 's'}} + (void)s.p; // expected-warning{{'local' is still referred to by the static variable 's'}} +} } // namespace leaking_via_struct_with_ptr namespace leaking_via_ref_to_struct_with_ptr { @@ -548,7 +548,7 @@ S &global_s = *(new S); void global_ref_to_struct_with_ptr() { int local = 42; - global_s.p = &local; // no-warning FIXME + global_s.p = &local; // expected-warning{{'local' is still referred to by the global variable 'global_s'}} } void static_ref_to_struct_with_ptr() { @@ -560,12 +560,12 @@ void static_ref_to_struct_with_ptr() { void param_ref_to_struct_with_ptr_top(S &s) { int local = 42; - s.p = &local; // no-warning FIXME + s.p = &local; // expected-warning{{'local' is still referred to by the caller variable 's'}} } void param_ref_to_struct_with_ptr_callee(S &s) { int local = 42; - s.p = &local; // expected-warning{{'local' is still referred to by the stack variable 'sStack'}} + s.p = &local; // expected-warning{{'local' is still referred to by the caller variable 'sStack'}} } void param_ref_to_struct_with_ptr_caller() { @@ -576,7 +576,7 @@ void param_ref_to_struct_with_ptr_caller() { template<typename Callable> void lambda_param_capture_callee(Callable& callee) { int local = 42; - callee(local); // expected-warning{{'local' is still referred to by the stack variable 'p'}} + callee(local); // expected-warning{{'local' is still referred to by the caller variable 'p'}} } void lambda_param_capture_caller() { @@ -616,7 +616,7 @@ S* global_s; void global_ptr_to_struct_with_ptr() { int local = 42; - global_s->p = &local; // no-warning FIXME + global_s->p = &local; // expected-warning{{'local' is still referred to by the global variable 'global_s'}} } void static_ptr_to_struct_with_ptr_new() { @@ -636,12 +636,12 @@ void static_ptr_to_struct_with_ptr_generated() { void param_ptr_to_struct_with_ptr_top(S* s) { int local = 42; - s->p = &local; // no-warning FIXME + s->p = &local; // expected-warning{{'local' is still referred to by the caller variable 's'}} } void param_ptr_to_struct_with_ptr_callee(S* s) { int local = 42; - s->p = &local; // expected-warning{{'local' is still referred to by the stack variable 's'}} + s->p = &local; // expected-warning{{'local' is still referred to by the caller variable 's'}} } void param_ptr_to_struct_with_ptr_caller() { @@ -679,8 +679,8 @@ S global_s[2]; void global_ptr_to_struct_with_ptr() { int local = 42; - global_s[1].p = &local; -} // expected-warning{{'local' is still referred to by the global variable 'global_s'}} + global_s[1].p = &local; // expected-warning{{'local' is still referred to by the global variable 'global_s'}} +} void static_ptr_to_struct_with_ptr_new() { int local = 42; @@ -699,12 +699,12 @@ void static_ptr_to_struct_with_ptr_generated() { void param_ptr_to_struct_with_ptr_top(S s[2]) { int local = 42; - s[1].p = &local; // no-warning FIXME + s[1].p = &local; // expected-warning{{'local' is still referred to by the caller variable 's'}} } void param_ptr_to_struct_with_ptr_callee(S s[2]) { int local = 42; - s[1].p = &local; // expected-warning{{'local' is still referred to by the stack variable 's'}} + s[1].p = &local; // expected-warning{{'local' is still referred to by the caller variable 's'}} } void param_ptr_to_struct_with_ptr_caller() { @@ -724,17 +724,17 @@ NestedAndTransitive global_nat; void global_nested_and_transitive() { int local = 42; - *global_nat.next[2]->next[1]->p = &local; // no-warning FIXME + *global_nat.next[2]->next[1]->p = &local; // expected-warning{{'local' is still referred to by the global variable 'global_nat'}} } void param_nested_and_transitive_top(NestedAndTransitive* nat) { int local = 42; - *nat->next[2]->next[1]->p = &local; // no-warning FIXME + *nat->next[2]->next[1]->p = &local; // expected-warning{{'local' is still referred to by the caller variable 'nat'}} } void param_nested_and_transitive_callee(NestedAndTransitive* nat) { int local = 42; - *nat->next[2]->next[1]->p = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'natCaller'}} + *nat->next[2]->next[1]->p = &local; // expected-warning{{'local' is still referred to by the caller variable 'natCaller'}} } void param_nested_and_transitive_caller(NestedAndTransitive natCaller) { @@ -757,3 +757,14 @@ class CPtr { } }; } // namespace leaking_as_member + +namespace leaking_via_indirect_global_invalidated { +void** global_pp; +void opaque(); +void global_ptr_to_ptr() { + int local = 42; + *global_pp = &local; + opaque(); + *global_pp = nullptr; +} +} // namespace leaking_via_indirect_global_invalidated diff --git a/clang/test/Analysis/stack-capture-leak-no-arc.mm b/clang/test/Analysis/stack-capture-leak-no-arc.mm index 84bbc5fdc5253a..1831e239a42d25 100644 --- a/clang/test/Analysis/stack-capture-leak-no-arc.mm +++ b/clang/test/Analysis/stack-capture-leak-no-arc.mm @@ -40,7 +40,7 @@ dispatch_block_t test_block_inside_block_async_leak() { // called. void output_block(dispatch_block_t * blk) { int x = 0; - *blk = ^{ f(x); }; // expected-warning {{Address of stack-allocated block declared on line 43 is still referred to by the stack variable 'blk' upon returning to the caller. This will be a dangling reference [core.StackAddressEscape]}} + *blk = ^{ f(x); }; // expected-warning {{Address of stack-allocated block declared on line 43 is still referred to by the caller variable 'blk' upon returning to the caller. This will be a dangling reference [core.StackAddressEscape]}} } // The block literal captures nothing thus is treated as a constant. @@ -54,7 +54,7 @@ void test_block_leak() { __block dispatch_block_t blk; int x = 0; dispatch_block_t p = ^{ - blk = ^{ // expected-warning {{Address of stack-allocated block declared on line 57 is still referred to by the stack variable 'blk' upon returning to the caller. This will be a dangling reference [core.StackAddressEscape]}} + blk = ^{ // expected-warning {{Address of stack-allocated block declared on line 57 is still referred to by the caller variable 'blk' upon returning to the caller. This will be a dangling reference [core.StackAddressEscape]}} f(x); }; }; diff --git a/clang/test/Analysis/stackaddrleak.c b/clang/test/Analysis/stackaddrleak.c index 39c29f2a2635b5..5f508275ba9c8d 100644 --- a/clang/test/Analysis/stackaddrleak.c +++ b/clang/test/Analysis/stackaddrleak.c @@ -7,7 +7,7 @@ char const *p; void f0(void) { char const str[] = "This will change"; p = str; -} // expected-warning{{Address of stack memory associated with local variable 'str' is still referred to by the global variable 'p' upon returning to the caller. This will be a dangling reference}} +} // expected-warning@-1{{Address of stack memory associated with local variable 'str' is still referred to by the global variable 'p' upon returning to the caller. This will be a dangling reference}} void f1(void) { char const str[] = "This will change"; @@ -17,7 +17,7 @@ void f1(void) { void f2(void) { p = (const char *) __builtin_alloca(12); -} // expected-warning{{Address of stack memory allocated by call to alloca() on line 19 is still referred to by the global variable 'p' upon returning to the caller. This will be a dangling reference}} +} // expected-warning@-1{{Address of stack memory allocated by call to alloca() on line 19 is still referred to by the global variable 'p' upon returning to the caller. This will be a dangling reference}} // PR 7383 - previously the stack address checker would crash on this example // because it would attempt to do a direct load from 'pr7383_list'. @@ -33,7 +33,7 @@ void test_multi_return(void) { int x; a = &x; b = &x; -} // expected-warning{{Address of stack memory associated with local variable 'x' is still referred to by the static variable 'a' upon returning}} expected-warning{{Address of stack memory associated with local variable 'x' is still referred to by the static variable 'b' upon returning}} +} // expected-warning@-1{{Address of stack memory associated with local variable 'x' is still referred to by the static variable 'a' upon returning}} expected-warning@-1{{Address of stack memory associated with local variable 'x' is still referred to by the static variable 'b' upon returning}} intptr_t returnAsNonLoc(void) { int x; @@ -49,7 +49,7 @@ void assignAsNonLoc(void) { extern intptr_t ip; int x; ip = (intptr_t)&x; -} // expected-warning{{Address of stack memory associated with local variable 'x' is still referred to by the global variable 'ip' upon returning}} +} // expected-warning@-1{{Address of stack memory associated with local variable 'x' is still referred to by the global variable 'ip' upon returning}} void assignAsBool(void) { extern bool b; _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits