mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-28 07:46:07 +00:00

Fixes https://github.com/llvm/llvm-project/issues/123459. Previously, when the StackAddrEscapeChecker checked return values, it did not scan into the structure of the return SVal. Now it does, and we can catch some more false negatives that were already mocked out in the tests in addition to those mentioned in https://github.com/llvm/llvm-project/issues/123459. The warning message at the moment for these newly caught leaks is not great. I think they would be better if they had a better trace of why and how the region leaks. If y'all are happy with these changes, I would try to improve these warnings and work on normalizing this SVal checking on the `checkEndFunction` side of the checker also. Two of the stack address leak test cases now have two warnings, one warning from return expression checking and another from` checkEndFunction` `iterBindings` checking. For these two cases, I prefer the warnings from the return expression checking, but I couldn't figure out a way to drop the `checkEndFunction` without breaking other `checkEndFunction` warnings that we do want. Thoughts here?
1085 lines
32 KiB
C++
1085 lines
32 KiB
C++
// RUN: %clang_analyze_cc1 \
|
|
// RUN: -analyzer-checker=core,debug.ExprInspection \
|
|
// RUN: -verify %s \
|
|
// RUN: -Wno-undefined-bool-conversion
|
|
// RUN: %clang_analyze_cc1 \
|
|
// RUN: -analyzer-checker=core,debug.ExprInspection,unix.Malloc \
|
|
// RUN: -verify %s \
|
|
// RUN: -Wno-undefined-bool-conversion
|
|
// unix.Malloc is necessary to model __builtin_alloca,
|
|
// which could trigger an "unexpected region" bug in StackAddrEscapeChecker.
|
|
|
|
typedef __INTPTR_TYPE__ intptr_t;
|
|
|
|
template <typename T>
|
|
void clang_analyzer_dump(T x);
|
|
|
|
using size_t = decltype(sizeof(int));
|
|
void * malloc(size_t size);
|
|
void free(void*);
|
|
|
|
const int& g() {
|
|
int s;
|
|
return s; // expected-warning{{Address of stack memory associated with local variable 's' returned}} expected-warning{{reference to stack memory associated with local variable 's' returned}}
|
|
}
|
|
|
|
const int& g2() {
|
|
int s1;
|
|
int &s2 = s1; // expected-note {{binding reference variable 's2' here}}
|
|
return s2; // expected-warning{{Address of stack memory associated with local variable 's1' returned}} expected-warning {{reference to stack memory associated with local variable 's1' returned}}
|
|
}
|
|
|
|
const int& g3() {
|
|
int s1;
|
|
int &s2 = s1; // expected-note {{binding reference variable 's2' here}}
|
|
int &s3 = s2; // expected-note {{binding reference variable 's3' here}}
|
|
return s3; // expected-warning{{Address of stack memory associated with local variable 's1' returned}} expected-warning {{reference to stack memory associated with local variable 's1' returned}}
|
|
}
|
|
|
|
void g4() {
|
|
static const int &x = 3; // no warning
|
|
}
|
|
|
|
int get_value();
|
|
|
|
const int &get_reference1() { return get_value(); } // expected-warning{{Address of stack memory associated with temporary object of type 'int' returned}} expected-warning {{returning reference to local temporary}}
|
|
|
|
const int &get_reference2() {
|
|
const int &x = get_value(); // expected-note {{binding reference variable 'x' here}}
|
|
return x; // expected-warning{{Address of stack memory associated with temporary object of type 'int' lifetime extended by local variable 'x' returned to caller}} expected-warning {{returning reference to local temporary}}
|
|
}
|
|
|
|
const int &get_reference3() {
|
|
const int &x1 = get_value(); // expected-note {{binding reference variable 'x1' here}}
|
|
const int &x2 = x1; // expected-note {{binding reference variable 'x2' here}}
|
|
return x2; // expected-warning{{Address of stack memory associated with temporary object of type 'int' lifetime extended by local variable 'x1' returned to caller}} expected-warning {{returning reference to local temporary}}
|
|
}
|
|
|
|
int global_var;
|
|
int *f1() {
|
|
int &y = global_var;
|
|
return &y;
|
|
}
|
|
|
|
int *f2() {
|
|
int x1;
|
|
int &x2 = x1; // expected-note {{binding reference variable 'x2' here}}
|
|
return &x2; // expected-warning{{Address of stack memory associated with local variable 'x1' returned}} expected-warning {{address of stack memory associated with local variable 'x1' returned}}
|
|
}
|
|
|
|
int *f3() {
|
|
int x1;
|
|
int *const &x2 = &x1; // expected-note {{binding reference variable 'x2' here}}
|
|
return x2; // expected-warning {{address of stack memory associated with local variable 'x1' returned}} expected-warning {{Address of stack memory associated with local variable 'x1' returned to caller}}
|
|
}
|
|
|
|
const int *f4() {
|
|
const int &x1 = get_value(); // expected-note {{binding reference variable 'x1' here}}
|
|
const int &x2 = x1; // expected-note {{binding reference variable 'x2' here}}
|
|
return &x2; // expected-warning{{Address of stack memory associated with temporary object of type 'int' lifetime extended by local variable 'x1' returned to caller}} expected-warning {{returning address of local temporary}}
|
|
}
|
|
|
|
struct S {
|
|
int x;
|
|
};
|
|
|
|
int *mf() {
|
|
S s1;
|
|
S &s2 = s1; // expected-note {{binding reference variable 's2' here}}
|
|
int &x = s2.x; // expected-note {{binding reference variable 'x' here}}
|
|
return &x; // expected-warning{{Address of stack memory associated with local variable 's1' returned}} expected-warning {{address of stack memory associated with local variable 's1' returned}}
|
|
}
|
|
|
|
int *return_assign_expr_leak() {
|
|
int x = 1;
|
|
int *y;
|
|
return y = &x; // expected-warning{{Address of stack memory associated with local variable 'x' returned}}
|
|
}
|
|
|
|
// Additional diagnostic from -Wreturn-stack-address in the simple case?
|
|
int *return_comma_separated_expressions_leak() {
|
|
int x = 1;
|
|
return (x=14), &x; // expected-warning{{Address of stack memory associated with local variable 'x' returned to caller}} expected-warning{{address of stack memory associated with local variable 'x' returned}}
|
|
}
|
|
|
|
void *lf() {
|
|
label:
|
|
void *const &x = &&label; // expected-note {{binding reference variable 'x' here}}
|
|
return x; // expected-warning {{returning address of label, which is local}}
|
|
}
|
|
|
|
template <typename T>
|
|
struct TS {
|
|
int *get();
|
|
int *m() {
|
|
int *&x = get();
|
|
return x;
|
|
}
|
|
};
|
|
|
|
int* f5() {
|
|
int& i = i; // expected-warning {{Assigned value is garbage or undefined}} expected-warning{{reference 'i' is not yet bound to a value when used within its own initialization}}
|
|
return &i;
|
|
}
|
|
|
|
void *radar13226577() {
|
|
void *p = &p;
|
|
return p; // expected-warning {{stack memory associated with local variable 'p' returned to caller}}
|
|
}
|
|
|
|
namespace rdar13296133 {
|
|
class ConvertsToBool {
|
|
public:
|
|
operator bool() const { return this; }
|
|
};
|
|
|
|
class ConvertsToIntptr {
|
|
public:
|
|
operator intptr_t() const { return reinterpret_cast<intptr_t>(this); }
|
|
};
|
|
|
|
class ConvertsToPointer {
|
|
public:
|
|
operator const void *() const { return this; }
|
|
};
|
|
|
|
intptr_t returnAsNonLoc() {
|
|
ConvertsToIntptr obj;
|
|
return obj; // expected-warning{{Address of stack memory associated with local variable 'obj' returned to caller}}
|
|
}
|
|
|
|
bool returnAsBool() {
|
|
ConvertsToBool obj;
|
|
return obj; // no-warning
|
|
}
|
|
|
|
intptr_t returnAsNonLocViaPointer() {
|
|
ConvertsToPointer obj;
|
|
return reinterpret_cast<intptr_t>(static_cast<const void *>(obj)); // expected-warning{{Address of stack memory associated with local variable 'obj' returned to caller}}
|
|
}
|
|
|
|
bool returnAsBoolViaPointer() {
|
|
ConvertsToPointer obj;
|
|
return obj; // no-warning
|
|
}
|
|
} // namespace rdar13296133
|
|
|
|
void* ret_cpp_static_cast(short x) {
|
|
return static_cast<void*>(&x); // expected-warning {{Address of stack memory associated with local variable 'x' returned to caller}} expected-warning {{address of stack memory}}
|
|
}
|
|
|
|
int* ret_cpp_reinterpret_cast(double x) {
|
|
return reinterpret_cast<int*>(&x); // expected-warning {{Address of stack memory associated with local variable 'x' returned to caller}} expected-warning {{address of stack me}}
|
|
}
|
|
|
|
int* ret_cpp_const_cast(const int x) {
|
|
return const_cast<int*>(&x); // expected-warning {{Address of stack memory associated with local variable 'x' returned to caller}} expected-warning {{address of stack memory}}
|
|
}
|
|
|
|
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 caller variable 'p' upon \
|
|
returning to the caller}}
|
|
}
|
|
|
|
void test_stack() {
|
|
char *p;
|
|
write_stack_address_to(&p);
|
|
}
|
|
|
|
struct C {
|
|
~C() {} // non-trivial class
|
|
};
|
|
|
|
C make1() {
|
|
C c;
|
|
return c; // no-warning
|
|
}
|
|
|
|
void test_copy_elision() {
|
|
C c1 = make1();
|
|
}
|
|
|
|
C&& return_bind_rvalue_reference_to_temporary() {
|
|
return C(); // expected-warning{{Address of stack memory associated with temporary object of type 'C' returned to caller}}
|
|
// expected-warning@-1{{returning reference to local temporary object}} -Wreturn-stack-address
|
|
}
|
|
|
|
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 caller 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); // expected-warning{{Address of stack memory associated with local variable 'lambda' returned to caller}}
|
|
}
|
|
|
|
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 caller 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 caller 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; // 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; // expected-warning{{local variable 'local' is still referred to by the global variable 'global_ppp'}}
|
|
}
|
|
|
|
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; // 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 caller 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; // 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 caller variable 'pp'}}
|
|
}
|
|
|
|
void ***param_ptr_to_ptr_to_ptr_return(void ***ppp) {
|
|
int local = 0;
|
|
**ppp = &local;
|
|
return ppp;
|
|
// expected-warning@-1 {{Address of stack memory associated with local variable 'local' is still referred to by the caller variable 'ppp' upon returning to the caller. This will be a dangling reference}}
|
|
}
|
|
|
|
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; // expected-warning{{local variable 'local' is still referred to by the caller variable 'pp'}}
|
|
};
|
|
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 caller 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; // expected-warning{{local variable 'local' is still referred to by the global variable 'global_rtp'}}
|
|
}
|
|
|
|
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; // 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 caller 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; // expected-warning{{Address of stack memory associated with local variable 'local' returned to caller}}
|
|
}
|
|
|
|
void** returned_arr_of_ptr_callee() {
|
|
int local = 42;
|
|
int* p = &local;
|
|
void** arr = new void*[2];
|
|
arr[1] = p;
|
|
return arr; // expected-warning{{Address of stack memory associated with local variable 'local' returned to caller}}
|
|
}
|
|
|
|
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; // 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 caller 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; // expected-warning{{Address of stack memory associated with local variable 'local' returned to caller}}
|
|
}
|
|
|
|
void** returned_arr_of_ptr_callee(int idx) {
|
|
int local = 42;
|
|
int* p = &local;
|
|
void** arr = new void*[2];
|
|
arr[idx] = p;
|
|
return arr; // expected-warning{{Address of stack memory associated with local variable 'local' returned to caller}}
|
|
}
|
|
|
|
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; // 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 caller 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; // expected-warning{{Address of stack memory associated with local variable 'local' returned to caller}}
|
|
}
|
|
|
|
S returned_struct_with_ptr_callee() {
|
|
int local = 42;
|
|
S s;
|
|
s.p = &local;
|
|
return s; // expected-warning {{Address of stack memory associated with local variable 'local' returned to caller}} expected-warning{{Address of stack memory associated with local variable 'local' is still referred to by the caller variable 's' upon returning to the caller. This will be a dangling reference}}
|
|
}
|
|
|
|
S leak_through_field_of_returned_object() {
|
|
int local = 14;
|
|
S s{&local};
|
|
return s; // expected-warning {{Address of stack memory associated with local variable 'local' returned to caller}}
|
|
}
|
|
|
|
S leak_through_compound_literal() {
|
|
int local = 0;
|
|
return (S) { &local }; // expected-warning {{Address of stack memory associated with local variable 'local' returned to caller}}
|
|
}
|
|
|
|
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_nested_structs_with_ptr {
|
|
struct Inner {
|
|
int *ptr;
|
|
};
|
|
|
|
struct Outer {
|
|
Inner I;
|
|
};
|
|
|
|
struct Deriving : public Outer {};
|
|
|
|
Outer leaks_through_nested_objects() {
|
|
int local = 0;
|
|
Outer O{&local};
|
|
return O; // expected-warning {{Address of stack memory associated with local variable 'local' returned to caller}}
|
|
}
|
|
|
|
Deriving leaks_through_base_objects() {
|
|
int local = 0;
|
|
Deriving D{&local};
|
|
return D; // expected-warning {{Address of stack memory associated with local variable 'local' returned to caller}}
|
|
}
|
|
} // namespace leaking_via_nested_structs_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; // expected-warning{{'local' is still referred to by the global variable 'global_s'}}
|
|
}
|
|
|
|
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; // 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 caller 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 caller 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; // expected-warning {{Address of stack memory associated with local variable 'local' returned to caller}}
|
|
}
|
|
|
|
S* returned_ptr_to_struct_with_ptr_callee() {
|
|
int local = 42;
|
|
S* s = new S;
|
|
s->p = &local;
|
|
return s; // expected-warning {{Address of stack memory associated with local variable 'local' returned to caller}}
|
|
}
|
|
|
|
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; // 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;
|
|
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; // 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 caller 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; // expected-warning {{Address of stack memory associated with local variable 'local' returned to caller}}
|
|
}
|
|
|
|
S* returned_ptr_to_struct_with_ptr_callee() {
|
|
int local = 42;
|
|
S* s = new S[2];
|
|
s[1].p = &local;
|
|
return s; // expected-warning {{Address of stack memory associated with local variable 'local' returned to caller}}
|
|
}
|
|
|
|
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; // 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 caller 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; // 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; // 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' is still referred to by the caller variable 'natCaller'}}
|
|
}
|
|
|
|
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
|
|
|
|
namespace origin_region_limitation {
|
|
void leaker(int ***leakerArg) {
|
|
int local;
|
|
clang_analyzer_dump(*leakerArg); // expected-warning{{&SymRegion{reg_$0<int ** arg>}}}
|
|
// Incorrect message: 'arg', after it is reinitialized with value returned by 'tweak'
|
|
// is no longer relevant.
|
|
// The message must refer to 'original_arg' instead, but there is no easy way to
|
|
// connect the SymRegion stored in 'original_arg' and 'original_arg' as variable.
|
|
**leakerArg = &local; // expected-warning{{ 'local' is still referred to by the caller variable 'arg'}}
|
|
}
|
|
|
|
int **tweak();
|
|
|
|
void foo(int **arg) {
|
|
int **original_arg = arg;
|
|
arg = tweak();
|
|
leaker(&original_arg);
|
|
}
|
|
} // namespace origin_region_limitation
|
|
|
|
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
|
|
|
|
namespace not_leaking_via_simple_ptr {
|
|
void simple_ptr(const char *p) {
|
|
char tmp;
|
|
p = &tmp; // no-warning
|
|
}
|
|
|
|
void ref_ptr(const char *&p) {
|
|
char tmp;
|
|
p = &tmp; // expected-warning{{variable 'tmp' is still referred to by the caller variable 'p'}}
|
|
}
|
|
|
|
struct S {
|
|
const char *p;
|
|
};
|
|
|
|
void struct_ptr(S s) {
|
|
char tmp;
|
|
s.p = &tmp; // no-warning
|
|
}
|
|
|
|
void array(const char arr[2]) {
|
|
char tmp;
|
|
arr = &tmp; // no-warning
|
|
}
|
|
|
|
extern void copy(char *output, const char *input, unsigned size);
|
|
extern bool foo(const char *input);
|
|
extern void bar(char *output, unsigned count);
|
|
extern bool baz(char *output, const char *input);
|
|
|
|
void repo(const char *input, char *output) {
|
|
char temp[64];
|
|
copy(temp, input, sizeof(temp));
|
|
|
|
char result[64];
|
|
input = temp;
|
|
if (foo(temp)) {
|
|
bar(result, sizeof(result));
|
|
input = result;
|
|
}
|
|
if (!baz(output, input)) {
|
|
copy(output, input, sizeof(result));
|
|
}
|
|
}
|
|
} // namespace not_leaking_via_simple_ptr
|
|
|
|
namespace early_reclaim_dead_limitation {
|
|
void foo();
|
|
void top(char **p) {
|
|
char local;
|
|
*p = &local;
|
|
foo(); // no-warning FIXME: p binding is reclaimed before the function end
|
|
}
|
|
} // namespace early_reclaim_dead_limitation
|
|
|
|
namespace alloca_region_pointer {
|
|
void callee(char **pptr) {
|
|
char local;
|
|
*pptr = &local;
|
|
} // no crash
|
|
|
|
void top_alloca_no_crash_fn() {
|
|
char **pptr = (char**)__builtin_alloca(sizeof(char*));
|
|
callee(pptr);
|
|
}
|
|
|
|
void top_malloc_no_crash_fn() {
|
|
char **pptr = (char**)malloc(sizeof(char*));
|
|
callee(pptr);
|
|
free(pptr);
|
|
}
|
|
} // namespace alloca_region_pointer
|
|
|
|
// These all warn with -Wreturn-stack-address also
|
|
namespace return_address_of_true_positives {
|
|
int* ret_local_addrOf() {
|
|
int x = 1;
|
|
return &*&x; // expected-warning {{Address of stack memory associated with local variable 'x' returned to caller}} expected-warning {{address of stack memory associated with local variable 'x' returned}}
|
|
}
|
|
|
|
int* ret_local_addrOf_paren() {
|
|
int x = 1;
|
|
return (&(*(&x))); // expected-warning {{Address of stack memory associated with local variable 'x' returned to caller}} expected-warning {{address of stack memory associated with local variable 'x' returned}}
|
|
}
|
|
|
|
int* ret_local_addrOf_ptr_arith() {
|
|
int x = 1;
|
|
return &*(&x+1); // expected-warning {{Address of stack memory associated with local variable 'x' returned to caller}} expected-warning {{address of stack memory associated with local variable 'x' returned}}
|
|
}
|
|
|
|
int* ret_local_addrOf_ptr_arith2() {
|
|
int x = 1;
|
|
return &*(&x+1); // expected-warning {{Address of stack memory associated with local variable 'x' returned to caller}} expected-warning {{address of stack memory associated with local variable 'x' returned}}
|
|
}
|
|
|
|
int* ret_local_field() {
|
|
struct { int x; } a;
|
|
return &a.x; // expected-warning {{Address of stack memory associated with local variable 'a' returned to caller}} expected-warning {{address of stack memory associated with local variable 'a' returned}}
|
|
}
|
|
|
|
int& ret_local_field_ref() {
|
|
struct { int x; } a;
|
|
return a.x; // expected-warning {{Address of stack memory associated with local variable 'a' returned to caller}} expected-warning {{reference to stack memory associated with local variable 'a' returned}}
|
|
}
|
|
} //namespace return_address_of_true_positives
|
|
|
|
namespace true_negatives_return_expressions {
|
|
struct Container { int *x; };
|
|
|
|
int test2() {
|
|
int x = 14;
|
|
return x; // no-warning
|
|
}
|
|
|
|
void return_void() {
|
|
return void(); // no-warning
|
|
}
|
|
|
|
int return_assign_expr_safe() {
|
|
int y, x = 14;
|
|
return y = x; // no-warning
|
|
}
|
|
|
|
int return_comma_separated_expressions_safe() {
|
|
int x = 1;
|
|
int *y;
|
|
return (y=&x), (x=15); // no-warning
|
|
}
|
|
|
|
int return_comma_separated_expressions_container_safe() {
|
|
int x = 1;
|
|
Container Other;
|
|
return Other = Container{&x}, x = 14; // no-warning
|
|
}
|
|
|
|
int make_x();
|
|
int return_symbol_safe() {
|
|
int x = make_x();
|
|
clang_analyzer_dump(x); // expected-warning-re {{conj_$2{int, {{.+}}}}}
|
|
return x; // no-warning
|
|
}
|
|
|
|
int *return_symbolic_region_safe(int **ptr) {
|
|
return *ptr; // no-warning
|
|
}
|
|
|
|
int *return_arg_ptr(int *arg) {
|
|
return arg; // no-warning
|
|
}
|
|
|
|
// This has a false positive with -Wreturn-stack-address, but CSA should not
|
|
// warn on this
|
|
int *return_conditional_never_false(int *arg) {
|
|
int x = 14;
|
|
return true ? arg : &x; // expected-warning {{address of stack memory associated with local variable 'x' returned}}
|
|
}
|
|
|
|
// This has a false positive with -Wreturn-stack-address, but CSA should not
|
|
// warn on this
|
|
int *return_conditional_never_true(int *arg) {
|
|
int x = 14;
|
|
return false ? &x : arg; // expected-warning {{address of stack memory associated with local variable 'x' returned}}
|
|
}
|
|
|
|
int *return_conditional_path_split(int *arg, bool b) {
|
|
int x = 14;
|
|
return b ? arg : &x; // expected-warning {{Address of stack memory associated with local variable 'x' returned to caller}} expected-warning {{address of stack memory associated with local variable 'x' returned}} -Wreturn-stack-address
|
|
}
|
|
|
|
// compare of two symbolic regions
|
|
// maybe in some implementation, make_ptr returns interior pointers that are comparable
|
|
int *make_ptr();
|
|
bool return_symbolic_exxpression() {
|
|
int *a = make_ptr();
|
|
int *b = make_ptr();
|
|
return a < b; // no-warning
|
|
}
|
|
|
|
int *return_static() {
|
|
static int x = 0;
|
|
return &x; // no-warning
|
|
}
|
|
|
|
int* return_cpp_reinterpret_cast_no_warning(long x) {
|
|
return reinterpret_cast<int*>(x); // no-warning
|
|
}
|
|
}
|
|
|
|
// TODO: The tail call expression tree must not hold a reference to an arg or stack local:
|
|
// "The lifetimes of all local variables and function parameters end immediately before the
|
|
// [tail] call to the function. This means that it is undefined behaviour to pass a pointer or
|
|
// reference to a local variable to the called function, which is not the case without the
|
|
// attribute."
|
|
// These only warn from -Wreturn-stack-address for now
|
|
namespace with_attr_musttail {
|
|
void TakesIntAndPtr(int, int *);
|
|
void PassAddressOfLocal(int a, int *b) {
|
|
int c;
|
|
[[clang::musttail]] return TakesIntAndPtr(0, &c); // expected-warning {{address of stack memory associated with local variable 'c' pass\
|
|
ed to musttail function}} False-negative on CSA
|
|
}
|
|
void PassAddressOfParam(int a, int *b) {
|
|
[[clang::musttail]] return TakesIntAndPtr(0, &a); // expected-warning {{address of stack memory associated with parameter 'a' passed to\
|
|
musttail function}} False-negative on CSA
|
|
}
|
|
} // namespace with_attr_musttail
|