| // RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core.UseAfterLifetimeEnd,debug.DebugLifetimeModeling \ | |
| // RUN: -analyzer-config cfg-lifetime=true -analyzer-output=text -verify %s | |
| // RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core.UseAfterLifetimeEnd,debug.DebugLifetimeModeling \ | |
| // RUN: -analyzer-output=text %s 2>&1 | FileCheck --strict-whitespace %s | |
| struct A {}; | |
| struct Pair { | |
| int a; | |
| int b; | |
| }; | |
| struct Base { | |
| int x; | |
| }; | |
| struct Derived : Base { | |
| int y; | |
| }; | |
| struct Inner { | |
| int val; | |
| }; | |
| struct Outer { | |
| Inner inner; | |
| }; | |
| struct Buffer { | |
| int arr[4]; | |
| }; | |
| void clang_analyzer_dumpLifetimeOriginsOf(int*); | |
| void clang_analyzer_dumpLifetimeOriginsOf(int&); | |
| void clang_analyzer_dumpLifetimeOriginsOf(A*); | |
| void clang_analyzer_dumpLifetimeOriginsOf(A&); | |
| // These are the cases when the result of function calls are MemRegions. | |
| // Ref type parameter annotated case. | |
| struct X { | |
| int &choose(int &a [[clang::lifetimebound]]) { return a; } | |
| }; | |
| void caller() { | |
| int v = 0; | |
| X obj; | |
| int &r = obj.choose(v); | |
| clang_analyzer_dumpLifetimeOriginsOf(r); | |
| // expected-warning@-1 {{Origin '&v' bound to 'v'}} | |
| // expected-note@-2 {{Origin '&v' bound to 'v'}} | |
| } | |
| // Obj ref type function return annotated case. | |
| struct Y { | |
| A a; | |
| A &getA() [[clang::lifetimebound]] { return a; } | |
| }; | |
| void caller_two() { | |
| // Return statement is annotated case. | |
| Y y; | |
| A &f = y.getA(); | |
| clang_analyzer_dumpLifetimeOriginsOf(f); | |
| // expected-warning@-1 {{Origin '&y.a' bound to 'y'}} | |
| // expected-note@-2 {{Origin '&y.a' bound to 'y'}} | |
| } | |
| // Obj ptr type function return annotated case. | |
| struct Z { | |
| A a; | |
| A *getA() [[clang::lifetimebound]] { return &a; } | |
| }; | |
| void caller_three() { | |
| Z z; | |
| A *func = z.getA(); | |
| clang_analyzer_dumpLifetimeOriginsOf(func); | |
| // expected-warning@-1 {{Origin '&z.a' bound to 'z'}} | |
| // expected-note@-2 {{Origin '&z.a' bound to 'z'}} | |
| } | |
| // Free function with annotated param and ref return. | |
| int &foo(int &num [[clang::lifetimebound]]) { return num; } | |
| void caller_four() { | |
| int num = 5; | |
| int &s = foo(num); | |
| clang_analyzer_dumpLifetimeOriginsOf(s); | |
| // expected-warning@-1 {{Origin '&num' bound to 'num'}} | |
| // expected-note@-2 {{Origin '&num' bound to 'num'}} | |
| } | |
| // Free function with annotated param and ptr return. | |
| int *boo(int *num [[clang::lifetimebound]]) { return num; } | |
| void caller_five() { | |
| int n = 55; | |
| int *n_ptr = &n; | |
| int *s = boo(n_ptr); | |
| clang_analyzer_dumpLifetimeOriginsOf(s); | |
| // expected-warning@-1 {{Origin '&n' bound to 'n'}} | |
| // expected-note@-2 {{Origin '&n' bound to 'n'}} | |
| } | |
| // Free function with both annotated and non-annotated parameters. | |
| int &fn(int &f, int &s [[clang::lifetimebound]]) { return s; } | |
| void caller_six() { | |
| int even = 50; | |
| int odd = 55; | |
| int &s = fn(even, odd); | |
| clang_analyzer_dumpLifetimeOriginsOf(s); | |
| // expected-warning@-1 {{Origin '&odd' bound to 'odd'}} | |
| // expected-note@-2 {{Origin '&odd' bound to 'odd'}} | |
| } | |
| // Test cases for testing when the result of function calls are SymbolRefs. | |
| // Function returns ptr and has an annotated parameter. | |
| int *foo(int *n [[clang::lifetimebound]]); | |
| void caller_seven() { | |
| int y = 15; | |
| int *y_ptr = &y; | |
| auto *bind = foo(y_ptr); | |
| clang_analyzer_dumpLifetimeOriginsOf(bind); | |
| // expected-warning-re@-1 {{Origin '&SymRegion{{.*}}' bound to 'y'}} | |
| // expected-note-re@-2 {{Origin '&SymRegion{{.*}}' bound to 'y'}} | |
| } | |
| // Function returns a reference and has an annotated parameter. | |
| int &func(int &some_number [[clang::lifetimebound]]); | |
| void caller_eight() { | |
| int f = 15; | |
| auto &bind = func(f); | |
| clang_analyzer_dumpLifetimeOriginsOf(bind); | |
| // expected-warning-re@-1 {{Origin '&SymRegion{{.*}}' bound to 'f'}} | |
| // expected-note-re@-2 {{Origin '&SymRegion{{.*}}' bound to 'f'}} | |
| } | |
| // Function returns a reference and has two annotated parameters. | |
| int &f(int &a [[clang::lifetimebound]], int &b [[clang::lifetimebound]]); | |
| void caller_nine() { | |
| int first_num = 1; | |
| int second_num = 2; | |
| int &numbers = f(first_num, second_num); | |
| clang_analyzer_dumpLifetimeOriginsOf(numbers); | |
| // expected-warning-re@-1 {{Origin '&SymRegion{{.*}}' bound to 'first_num', 'second_num'}} | |
| // expected-note-re@-2 {{Origin '&SymRegion{{.*}}' bound to 'first_num', 'second_num'}} | |
| } | |
| struct View { | |
| int *p; | |
| }; | |
| View makeView(int &x [[clang::lifetimebound]]); | |
| void clang_analyzer_dumpLifetimeOriginsOf(View); | |
| void caller_view() { | |
| int v = 42; | |
| View w = makeView(v); | |
| // FIXME: Currently none of the maps cover LazyCompoundVal. | |
| clang_analyzer_dumpLifetimeOriginsOf(w); // no-warning | |
| } | |
| // These are the test cases for testing the correctness of the emitted warning from the UseAfterLifetimeEnd checker. | |
| // Return value bound to annotated param cases. | |
| int *test_func(int *p [[clang::lifetimebound]]); | |
| int *direct_return() { | |
| int i = 5; // expected-note {{'i' initialized here}} | |
| return test_func(&i); | |
| // expected-warning@-1 {{Returning value bound to 'i' that will go out of scope}} | |
| // expected-warning@-2 {{address of stack memory associated with local variable 'i' returned}} | |
| // expected-note@-3 {{Value's lifetime bound to the lifetime of 'i' here}} | |
| // expected-note@-4 {{Lifetime of 'i' ended here}} | |
| } | |
| int *variable_return() { | |
| int y = 5; // expected-note {{'y' initialized here}} | |
| int *p = test_func(&y); // expected-note {{Value's lifetime bound to the lifetime of 'y' here}} | |
| return p; | |
| // expected-warning@-1 {{Returning value bound to 'y' that will go out of scope}} | |
| // expected-note@-2 {{Lifetime of 'y' ended here}} | |
| } | |
| int *borrow_from_caller(int *b [[clang::lifetimebound]]) { | |
| return test_func(b); // no-warning | |
| } | |
| void no_return() { | |
| int i = 5; | |
| int *p = test_func(&i); | |
| (void)p; // no-warning | |
| } | |
| int *g() { | |
| int i = 5; | |
| int *p = test_func(&i); | |
| (void)p; | |
| return nullptr; // no-warning | |
| } | |
| int &multi_param_test_ref(int &a [[clang::lifetimebound]], int &b [[clang::lifetimebound]]); | |
| // Return value bound to annotated parameters (two dangling sources). | |
| int &dangling_sources_ref() { | |
| int x = 1, y = 2; | |
| // expected-note@-1 {{'x' initialized here}} | |
| // expected-note@-2 {{'y' initialized here}} | |
| return multi_param_test_ref(x, y); | |
| // expected-warning@-1 {{Returning value bound to 'x' that will go out of scope}} | |
| // expected-warning@-2 {{Returning value bound to 'y' that will go out of scope}} | |
| // expected-warning@-3 {{reference to stack memory associated with local variable 'x' returned}} | |
| // expected-warning@-4 {{reference to stack memory associated with local variable 'y' returned}} | |
| // expected-note@-5 {{Value's lifetime bound to the lifetime of 'x' here}} | |
| // expected-note@-6 {{Value's lifetime bound to the lifetime of 'y' here}} | |
| // expected-note@-7 {{Lifetime of 'x' ended here}} | |
| // expected-note@-8 {{Lifetime of 'y' ended here}} | |
| } | |
| // Return value bound to annotated parameters (no dangling sources). | |
| int &no_dangling_sources_ref(int &a [[clang::lifetimebound]], int &b [[clang::lifetimebound]]) { | |
| return multi_param_test_ref(a, b); // no-warning | |
| } | |
| // Return value bound to annotated parameters (one dangling source). | |
| int &one_dangling_source_ref(int &a [[clang::lifetimebound]]) { | |
| int x = 1; // expected-note {{'x' initialized here}} | |
| return multi_param_test_ref(a, x); | |
| // expected-warning@-1 {{Returning value bound to 'x' that will go out of scope}} | |
| // expected-warning@-2 {{reference to stack memory associated with local variable 'x' returned}} | |
| // expected-note@-3 {{Value's lifetime bound to the lifetime of 'x' here}} | |
| // expected-note@-4 {{Lifetime of 'x' ended here}} | |
| } | |
| int *multi_param_test_ptr(int *a [[clang::lifetimebound]], int *b [[clang::lifetimebound]]); | |
| // Return value bound to annotated parameters (two dangling sources). | |
| int *dangling_sources_ptr() { | |
| int x = 1, y = 2; | |
| // expected-note@-1 {{'x' initialized here}} | |
| // expected-note@-2 {{'y' initialized here}} | |
| int *x_ptr = &x; | |
| int *y_ptr = &y; | |
| return multi_param_test_ptr(x_ptr, y_ptr); | |
| // expected-warning@-1 {{Returning value bound to 'x' that will go out of scope}} | |
| // expected-note@-2 {{Value's lifetime bound to the lifetime of 'x' here}} | |
| // expected-note@-3 {{Lifetime of 'x' ended here}} | |
| // expected-warning@-4 {{Returning value bound to 'y' that will go out of scope}} | |
| // expected-note@-5 {{Value's lifetime bound to the lifetime of 'y' here}} | |
| // expected-note@-6 {{Lifetime of 'y' ended here}} | |
| } | |
| // Return value bound to annotated parameters (no dangling sources). | |
| int *no_dangling_sources_ptr(int *a [[clang::lifetimebound]], int *b [[clang::lifetimebound]]) { | |
| return multi_param_test_ptr(a, b); // no-warning | |
| } | |
| // Return value bound to annotated parameters (one dangling source). | |
| int *one_dangling_source_ptr(int *a [[clang::lifetimebound]]) { | |
| int x = 1; // expected-note {{'x' initialized here}} | |
| int *x_ptr = &x; | |
| return multi_param_test_ptr(a, x_ptr); | |
| // expected-warning@-1 {{Returning value bound to 'x' that will go out of scope}} | |
| // expected-note@-2 {{Value's lifetime bound to the lifetime of 'x' here}} | |
| // expected-note@-3 {{Lifetime of 'x' ended here}} | |
| } | |
| struct S { | |
| int x; | |
| int* get() [[clang::lifetimebound]] { return &x; } | |
| S() = default; | |
| S(S&& other) { (void)other.get(); } | |
| ~S() { get(); } | |
| }; | |
| S make() { return S(); } | |
| S passThrough(S param) { return param; } | |
| void outer() { | |
| auto f = passThrough(make()); | |
| (void)f; // no-warning | |
| } | |
| int *danglingLocal() { | |
| S s; // expected-note {{'s' initialized here}} | |
| return s.get(); | |
| // expected-warning@-1 {{Returning value bound to 's' that will go out of scope}} | |
| // expected-warning@-2 {{Address of stack memory associated with local variable 's' returned to caller}} | |
| // expected-warning@-3 {{address of stack memory associated with local variable 's' returned}} | |
| // expected-note@-4 {{Address of stack memory associated with local variable 's' returned to caller}} | |
| // expected-note@-5 {{Value's lifetime bound to the lifetime of 's' here}} | |
| // expected-note@-6 {{Lifetime of 's' ended here}} | |
| } | |
| int *danglingParam(S param) { | |
| return param.get(); | |
| // expected-warning@-1 {{Returning value bound to 'param' that will go out of scope}} | |
| // expected-warning@-2 {{Address of stack memory associated with local variable 'param' returned to caller}} | |
| // expected-warning@-3 {{address of stack memory associated with parameter 'param' returned}} | |
| // expected-note@-4 {{Address of stack memory associated with local variable 'param' returned to caller}} | |
| // expected-note@-5 {{Value's lifetime bound to the lifetime of 'param' here}} | |
| // expected-note@-6 {{Lifetime of 'param' ended here}} | |
| } | |
| int *getFieldPtr(Pair &p [[clang::lifetimebound]]) { return &p.a; } | |
| int *field_subobject_dangling() { | |
| Pair pair{3, 5}; // expected-note {{'pair' initialized here}} | |
| return getFieldPtr(pair); | |
| // expected-warning@-1 {{Returning value bound to 'pair' that will go out of scope}} | |
| // expected-warning@-2 {{Address of stack memory associated with local variable 'pair' returned to caller}} | |
| // expected-warning@-3 {{address of stack memory associated with local variable 'pair' returned}} | |
| // expected-note@-4 {{Address of stack memory associated with local variable 'pair' returned to caller}} | |
| // expected-note@-5 {{Value's lifetime bound to the lifetime of 'pair' here}} | |
| // expected-note@-6 {{Lifetime of 'pair' ended here}} | |
| } | |
| int *getBasePtr(Derived &d [[clang::lifetimebound]]) { | |
| return &static_cast<Base &>(d).x; | |
| } | |
| int *base_subobject_dangling() { | |
| Derived derived{}; // expected-note {{'derived' initialized here}} | |
| return getBasePtr(derived); | |
| // expected-warning@-1 {{Returning value bound to 'derived' that will go out of scope}} | |
| // expected-warning@-2 {{Address of stack memory associated with local variable 'derived' returned to caller}} | |
| // expected-warning@-3 {{address of stack memory associated with local variable 'derived' returned}} | |
| // expected-note@-4 {{Address of stack memory associated with local variable 'derived' returned to caller}} | |
| // expected-note@-5 {{Value's lifetime bound to the lifetime of 'derived' here}} | |
| // expected-note@-6 {{Lifetime of 'derived' ended here}} | |
| } | |
| int *getNestedFieldPtr(Outer &o [[clang::lifetimebound]]) { | |
| return &o.inner.val; | |
| } | |
| int *nested_subobject_dangling() { | |
| Outer outer{}; // expected-note {{'outer' initialized here}} | |
| return getNestedFieldPtr(outer); | |
| // expected-warning@-1 {{Returning value bound to 'outer' that will go out of scope}} | |
| // expected-warning@-2 {{Address of stack memory associated with local variable 'outer' returned to caller}} | |
| // expected-warning@-3 {{address of stack memory associated with local variable 'outer' returned}} | |
| // expected-note@-4 {{Address of stack memory associated with local variable 'outer' returned to caller}} | |
| // expected-note@-5 {{Value's lifetime bound to the lifetime of 'outer' here}} | |
| // expected-note@-6 {{Lifetime of 'outer' ended here}} | |
| } | |
| int *getArrayElementPtr(Buffer &b [[clang::lifetimebound]]) { | |
| return &b.arr[0]; | |
| } | |
| int *array_member_subobject_dangling() { | |
| Buffer buf{}; // expected-note {{'buf' initialized here}} | |
| return getArrayElementPtr(buf); | |
| // expected-warning@-1 {{Returning value bound to 'buf' that will go out of scope}} | |
| // expected-warning@-2 {{Address of stack memory associated with local variable 'buf' returned to caller}} | |
| // expected-warning@-3 {{address of stack memory associated with local variable 'buf' returned}} | |
| // expected-note@-4 {{Address of stack memory associated with local variable 'buf' returned to caller}} | |
| // expected-note@-5 {{Value's lifetime bound to the lifetime of 'buf' here}} | |
| // expected-note@-6 {{Lifetime of 'buf' ended here}} | |
| } | |
| // FIXME: Heap allocated memory regions are not yet handled by the lifetime checkers. | |
| int *heap_dangling_source_lifetimebound() { | |
| int *i = new int(5); | |
| int *p = test_func(i); | |
| delete i; | |
| return p; // no-warning | |
| } | |
| struct CustomStringView { | |
| CustomStringView(const char *s [[clang::lifetimebound]]); | |
| }; | |
| // FIXME: The StringView return is a struct returned by value which is represented | |
| // as a LazyCompoundVal that the lifetime checkers do not support as of now. | |
| CustomStringView dangling_sv() { | |
| char s[] = "dangling"; | |
| return CustomStringView(s); // expected-warning {{address of stack memory associated with local variable 's' returned}} | |
| } | |
| // `self()` is annotated [[clang::lifetimebound]], so its return is bound to | |
| // *this. The BoundToSelf instance is built as a by-value argument temporary, | |
| // so its frame is not live on the stack when self() returns. | |
| struct BoundToSelf { | |
| BoundToSelf &self() [[clang::lifetimebound]] { return *this; } // no-warning | |
| BoundToSelf() { | |
| self(); | |
| self(); | |
| } | |
| }; | |
| void takes_by_value(BoundToSelf arg); | |
| void no_dangling_by_value_argument() { | |
| // The BoundToSelf temporary's frame is not live on the stack when `self()` returns. | |
| // The returned reference does not dangle. | |
| takes_by_value(BoundToSelf()); | |
| } | |
| int multi_params_annotated(int *p_one [[clang::lifetimebound]], int *p_two [[clang::lifetimebound]]); | |
| int test_multi_param_highlight() { | |
| int local_one = 1, local_two = 2; | |
| // expected-note@-1 {{'local_one' initialized here}} | |
| // expected-note@-2 {{'local_two' initialized here}} | |
| return multi_params_annotated(&local_one, &local_two); | |
| // expected-warning@-1 {{address of stack memory associated with local variable 'local_one' returned}} | |
| // expected-warning@-2 {{address of stack memory associated with local variable 'local_two' returned}} | |
| // expected-warning@-3 {{Returning value bound to 'local_one' that will go out of scope}} | |
| // expected-note@-4 {{Value's lifetime bound to the lifetime of 'local_one' here}} | |
| // expected-note@-5 {{Lifetime of 'local_one' ended here}} | |
| // expected-warning@-6 {{Returning value bound to 'local_two' that will go out of scope}} | |
| // expected-note@-7 {{Value's lifetime bound to the lifetime of 'local_two' here}} | |
| // expected-note@-8 {{Lifetime of 'local_two' ended here}} | |
| // CHECK: note: Value's lifetime bound to the lifetime of 'local_one' here | |
| // CHECK: return multi_params_annotated(&local_one, &local_two); | |
| // CHECK-NEXT:{{\| \^~~~~~~~~~$}} | |
| // CHECK: note: Value's lifetime bound to the lifetime of 'local_two' here | |
| // CHECK: return multi_params_annotated(&local_one, &local_two); | |
| // CHECK-NEXT:{{\| \^~~~~~~~~~$}} | |
| } | |
| int global_var; | |
| int test_correct_param_highlight() { | |
| int local_n = 5; | |
| // expected-note@-1 {{'local_n' initialized here}} | |
| return multi_params_annotated(&global_var, &local_n); | |
| // expected-warning@-1 {{address of stack memory associated with local variable 'local_n' returned}} | |
| // expected-warning@-2 {{Returning value bound to 'local_n' that will go out of scope}} | |
| // expected-note@-3 {{Value's lifetime bound to the lifetime of 'local_n' here}} | |
| // expected-note@-4 {{Lifetime of 'local_n' ended here}} | |
| // CHECK: note: Value's lifetime bound to the lifetime of 'local_n' here | |
| // CHECK: return multi_params_annotated(&global_var, &local_n); | |
| // CHECK-NEXT:{{\| \^~~~~~~~$}} | |
| } | |
| int test_multi_local_bound_to_param_highlight() { | |
| int j = 4, k = 5; | |
| // expected-note@-1 {{'j' initialized here}} | |
| // expected-note@-2 {{'k' initialized here}} | |
| return multi_params_annotated(&j, &k); | |
| // expected-warning@-1 {{address of stack memory associated with local variable 'j' returned}} | |
| // expected-warning@-2 {{address of stack memory associated with local variable 'k' returned}} | |
| // expected-warning@-3 {{Returning value bound to 'j' that will go out of scope}} | |
| // expected-note@-4 {{Value's lifetime bound to the lifetime of 'j' here}} | |
| // expected-note@-5 {{Lifetime of 'j' ended here}} | |
| // expected-warning@-6 {{Returning value bound to 'k' that will go out of scope}} | |
| // expected-note@-7 {{Value's lifetime bound to the lifetime of 'k' here}} | |
| // expected-note@-8 {{Lifetime of 'k' ended here}} | |
| // CHECK: note: Value's lifetime bound to the lifetime of 'j' here | |
| // CHECK-NEXT: int j = 4, k = 5; | |
| // CHECK-NEXT:{{\| ~$}} | |
| // CHECK: note: Lifetime of 'j' ended here | |
| // CHECK-NEXT: int j = 4, k = 5; | |
| // CHECK-NEXT:{{\| ~$}} | |
| // CHECK: note: Value's lifetime bound to the lifetime of 'k' here | |
| // CHECK-NEXT: int j = 4, k = 5; | |
| // CHECK-NEXT:{{\| ~$}} | |
| // CHECK: note: Lifetime of 'k' ended here | |
| // CHECK-NEXT: int j = 4, k = 5; | |
| // CHECK-NEXT:{{\| ~$}} | |
| } |