CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
3597 lines
111 KiB
C++
3597 lines
111 KiB
C++
// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify=expected,function %s
|
|
// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety -Wno-dangling -verify=expected,tu %s
|
|
// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -fcxx-exceptions -verify=expected,function %s
|
|
|
|
#include "Inputs/lifetime-analysis.h"
|
|
|
|
struct View;
|
|
|
|
struct [[gsl::Owner]] MyObj {
|
|
int id;
|
|
MyObj();
|
|
MyObj(int);
|
|
MyObj(const MyObj&);
|
|
MyObj(MyObj&&);
|
|
MyObj& operator=(MyObj&&);
|
|
~MyObj() {} // Non-trivial destructor
|
|
MyObj operator+(MyObj);
|
|
|
|
View getView() const [[clang::lifetimebound]];
|
|
const int* getData() const [[clang::lifetimebound]];
|
|
};
|
|
|
|
struct [[gsl::Owner]] MyTrivialObj {
|
|
int id;
|
|
};
|
|
|
|
struct [[gsl::Pointer()]] View {
|
|
View(const MyObj&); // Borrows from MyObj
|
|
View(const MyTrivialObj &); // Borrows from MyTrivialObj
|
|
View();
|
|
void use() const;
|
|
|
|
const MyObj* data() const;
|
|
const MyObj& operator*() const;
|
|
const MyObj* operator->() const;
|
|
};
|
|
|
|
class TriviallyDestructedClass {
|
|
View a, b;
|
|
};
|
|
|
|
MyObj non_trivially_destructed_temporary();
|
|
MyTrivialObj trivially_destructed_temporary();
|
|
View construct_view(const MyObj &obj [[clang::lifetimebound]]) {
|
|
return View(obj);
|
|
}
|
|
void use(View);
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Basic Use-After-Free
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void simple_case() {
|
|
MyObj* p;
|
|
{
|
|
MyObj s;
|
|
p = &s; // expected-warning {{local variable 's' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void simple_case_gsl() {
|
|
View v;
|
|
{
|
|
MyObj s;
|
|
v = s; // expected-warning {{local variable 's' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void no_use_no_error() {
|
|
MyObj* p;
|
|
{
|
|
MyObj s;
|
|
p = &s;
|
|
}
|
|
// 'p' is dangling here, but since it is never used, no warning is issued.
|
|
}
|
|
|
|
void no_use_no_error_gsl() {
|
|
View v;
|
|
{
|
|
MyObj s;
|
|
v = s;
|
|
}
|
|
// 'v' is dangling here, but since it is never used, no warning is issued.
|
|
}
|
|
|
|
void pointer_chain() {
|
|
MyObj* p;
|
|
MyObj* q;
|
|
{
|
|
MyObj s;
|
|
p = &s; // expected-warning {{does not live long enough}}
|
|
q = p;
|
|
} // expected-note {{destroyed here}}
|
|
(void)*q; // expected-note {{later used here}}
|
|
}
|
|
|
|
void propagation_gsl() {
|
|
View v1, v2;
|
|
{
|
|
MyObj s;
|
|
v1 = s; // expected-warning {{local variable 's' does not live long enough}}
|
|
v2 = v1;
|
|
} // expected-note {{destroyed here}}
|
|
v2.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void multiple_uses_one_warning() {
|
|
MyObj* p;
|
|
{
|
|
MyObj s;
|
|
p = &s; // expected-warning {{does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
// No second warning for the same loan.
|
|
p->id = 1;
|
|
MyObj* q = p;
|
|
(void)*q;
|
|
}
|
|
|
|
void multiple_pointers() {
|
|
MyObj *p, *q, *r;
|
|
{
|
|
MyObj s;
|
|
p = &s; // expected-warning {{does not live long enough}}
|
|
q = &s; // expected-warning {{does not live long enough}}
|
|
r = &s; // expected-warning {{does not live long enough}}
|
|
} // expected-note 3 {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
(void)*q; // expected-note {{later used here}}
|
|
(void)*r; // expected-note {{later used here}}
|
|
}
|
|
|
|
void multiple_pointers_chained() {
|
|
MyObj *p;
|
|
{
|
|
MyObj s;
|
|
MyObj* obj1, *obj2;
|
|
p = obj1 = obj2 = &s; // expected-warning {{does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void multiple_pointers_chained_safe() {
|
|
MyObj *p;
|
|
MyObj s;
|
|
{
|
|
MyObj* obj1, *obj2;
|
|
p = obj1 = obj2 = &s;
|
|
}
|
|
(void)*p;
|
|
}
|
|
|
|
void single_pointer_multiple_loans(bool cond) {
|
|
MyObj *p;
|
|
if (cond){
|
|
MyObj s;
|
|
p = &s; // expected-warning {{does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
else {
|
|
MyObj t;
|
|
p = &t; // expected-warning {{does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note 2 {{later used here}}
|
|
}
|
|
|
|
void single_pointer_multiple_loans_gsl(bool cond) {
|
|
View v;
|
|
if (cond){
|
|
MyObj s;
|
|
v = s; // expected-warning {{local variable 's' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
else {
|
|
MyObj t;
|
|
v = t; // expected-warning {{local variable 't' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
v.use(); // expected-note 2 {{later used here}}
|
|
}
|
|
|
|
void if_branch(bool cond) {
|
|
MyObj safe;
|
|
MyObj* p = &safe;
|
|
if (cond) {
|
|
MyObj temp;
|
|
p = &temp; // expected-warning {{local variable 'temp' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void if_branch_potential(bool cond) {
|
|
MyObj safe;
|
|
MyObj* p = &safe;
|
|
if (cond) {
|
|
MyObj temp;
|
|
p = &temp; // expected-warning {{local variable 'temp' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
if (!cond)
|
|
(void)*p; // expected-note {{later used here}}
|
|
else
|
|
p = &safe;
|
|
}
|
|
|
|
void if_branch_gsl(bool cond) {
|
|
MyObj safe;
|
|
View v = safe;
|
|
if (cond) {
|
|
MyObj temp;
|
|
v = temp; // expected-warning {{local variable 'temp' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void potential_together(bool cond) {
|
|
MyObj safe;
|
|
MyObj* p_maybe = &safe;
|
|
MyObj* p_definite = nullptr;
|
|
|
|
{
|
|
MyObj s;
|
|
if (cond)
|
|
p_definite = &s; // expected-warning {{does not live long enough}}
|
|
if (cond)
|
|
p_maybe = &s; // expected-warning {{does not live long enough}}
|
|
} // expected-note 2 {{destroyed here}}
|
|
(void)*p_definite; // expected-note {{later used here}}
|
|
if (!cond)
|
|
(void)*p_maybe; // expected-note {{later used here}}
|
|
}
|
|
|
|
void overrides_potential(bool cond) {
|
|
MyObj safe;
|
|
MyObj* p;
|
|
MyObj* q;
|
|
{
|
|
MyObj s;
|
|
q = &s; // expected-warning {{does not live long enough}}
|
|
p = q;
|
|
} // expected-note {{destroyed here}}
|
|
|
|
if (cond) {
|
|
// 'q' is conditionally "rescued". 'p' is not.
|
|
q = &safe;
|
|
}
|
|
|
|
// The use of 'p' dominates expiry of 's' error because it was never rescued.
|
|
(void)*q;
|
|
(void)*p; // expected-note {{later used here}}
|
|
(void)*q;
|
|
}
|
|
|
|
void due_to_conditional_killing(bool cond) {
|
|
MyObj safe;
|
|
MyObj* q;
|
|
{
|
|
MyObj s;
|
|
q = &s; // expected-warning {{does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
if (cond) {
|
|
// 'q' is conditionally "rescued". 'p' is not.
|
|
q = &safe;
|
|
}
|
|
(void)*q; // expected-note {{later used here}}
|
|
}
|
|
|
|
void for_loop_use_after_loop_body(MyObj safe) {
|
|
MyObj* p = &safe;
|
|
for (int i = 0; i < 1; ++i) {
|
|
MyObj s;
|
|
p = &s; // expected-warning {{does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void safe_for_loop_gsl() {
|
|
MyObj safe;
|
|
View v = safe;
|
|
for (int i = 0; i < 1; ++i) {
|
|
MyObj s;
|
|
v = s;
|
|
v.use();
|
|
}
|
|
}
|
|
|
|
void for_loop_gsl() {
|
|
MyObj safe;
|
|
View v = safe;
|
|
for (int i = 0; i < 1; ++i) {
|
|
MyObj s;
|
|
v = s; // expected-warning {{local variable 's' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void for_loop_use_before_loop_body(MyObj safe) {
|
|
MyObj* p = &safe;
|
|
// Prefer the earlier use for diagnsotics.
|
|
for (int i = 0; i < 1; ++i) {
|
|
(void)*p; // expected-note {{later used here}}
|
|
MyObj s;
|
|
p = &s; // expected-warning {{does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p;
|
|
}
|
|
|
|
void loop_with_break(bool cond) {
|
|
MyObj safe;
|
|
MyObj* p = &safe;
|
|
for (int i = 0; i < 10; ++i) {
|
|
if (cond) {
|
|
MyObj temp;
|
|
p = &temp; // expected-warning {{does not live long enough}}
|
|
break; // expected-note {{destroyed here}}
|
|
}
|
|
}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void loop_with_break_gsl(bool cond) {
|
|
MyObj safe;
|
|
View v = safe;
|
|
for (int i = 0; i < 10; ++i) {
|
|
if (cond) {
|
|
MyObj temp;
|
|
v = temp; // expected-warning {{local variable 'temp' does not live long enough}}
|
|
break; // expected-note {{destroyed here}}
|
|
}
|
|
}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void multiple_expiry_of_same_loan(bool cond) {
|
|
// Choose the last expiry location for the loan (e.g., through scope-ends and break statements).
|
|
MyObj safe;
|
|
MyObj* p = &safe;
|
|
for (int i = 0; i < 10; ++i) {
|
|
MyObj unsafe;
|
|
if (cond) {
|
|
p = &unsafe; // expected-warning {{does not live long enough}}
|
|
break; // expected-note {{destroyed here}}
|
|
}
|
|
}
|
|
(void)*p; // expected-note {{later used here}}
|
|
|
|
p = &safe;
|
|
for (int i = 0; i < 10; ++i) {
|
|
MyObj unsafe;
|
|
if (cond) {
|
|
p = &unsafe; // expected-warning {{does not live long enough}}
|
|
if (cond)
|
|
break; // expected-note {{destroyed here}}
|
|
}
|
|
}
|
|
(void)*p; // expected-note {{later used here}}
|
|
|
|
p = &safe;
|
|
for (int i = 0; i < 10; ++i) {
|
|
if (cond) {
|
|
MyObj unsafe2;
|
|
p = &unsafe2; // expected-warning {{does not live long enough}}
|
|
break; // expected-note {{destroyed here}}
|
|
}
|
|
}
|
|
(void)*p; // expected-note {{later used here}}
|
|
|
|
p = &safe;
|
|
for (int i = 0; i < 10; ++i) {
|
|
MyObj unsafe;
|
|
if (cond)
|
|
p = &unsafe; // expected-warning {{does not live long enough}}
|
|
if (cond)
|
|
break; // expected-note {{destroyed here}}
|
|
}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void switch_potential(int mode) {
|
|
MyObj safe;
|
|
MyObj* p = &safe;
|
|
switch (mode) {
|
|
case 1: {
|
|
MyObj temp;
|
|
p = &temp; // expected-warning {{local variable 'temp' does not live long enough}}
|
|
break; // expected-note {{destroyed here}}
|
|
}
|
|
case 2: {
|
|
p = &safe; // This path is okay.
|
|
break;
|
|
}
|
|
}
|
|
if (mode == 2)
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void switch_uaf(int mode) {
|
|
MyObj safe;
|
|
MyObj* p = &safe;
|
|
// A use domintates all the loan expires --> all definite error.
|
|
switch (mode) {
|
|
case 1: {
|
|
MyObj temp1;
|
|
p = &temp1; // expected-warning {{does not live long enough}}
|
|
break; // expected-note {{destroyed here}}
|
|
}
|
|
case 2: {
|
|
MyObj temp2;
|
|
p = &temp2; // expected-warning {{does not live long enough}}
|
|
break; // expected-note {{destroyed here}}
|
|
}
|
|
default: {
|
|
MyObj temp2;
|
|
p = &temp2; // expected-warning {{does not live long enough}}
|
|
break; // expected-note {{destroyed here}}
|
|
}
|
|
}
|
|
(void)*p; // expected-note 3 {{later used here}}
|
|
}
|
|
|
|
void switch_gsl(int mode) {
|
|
View v;
|
|
switch (mode) {
|
|
case 1: {
|
|
MyObj temp1;
|
|
v = temp1; // expected-warning {{local variable 'temp1' does not live long enough}}
|
|
break; // expected-note {{destroyed here}}
|
|
}
|
|
case 2: {
|
|
MyObj temp2;
|
|
v = temp2; // expected-warning {{local variable 'temp2' does not live long enough}}
|
|
break; // expected-note {{destroyed here}}
|
|
}
|
|
default: {
|
|
MyObj temp3;
|
|
v = temp3; // expected-warning {{local variable 'temp3' does not live long enough}}
|
|
break; // expected-note {{destroyed here}}
|
|
}
|
|
}
|
|
v.use(); // expected-note 3 {{later used here}}
|
|
}
|
|
|
|
void loan_from_previous_iteration(MyObj safe, bool condition) {
|
|
MyObj* p = &safe;
|
|
MyObj* q = &safe;
|
|
|
|
while (condition) {
|
|
MyObj x;
|
|
p = &x; // expected-warning {{does not live long enough}}
|
|
|
|
if (condition)
|
|
q = p;
|
|
(void)*p;
|
|
(void)*q; // expected-note {{later used here}}
|
|
} // expected-note {{destroyed here}}
|
|
}
|
|
|
|
void trivial_int_uaf() {
|
|
int * a;
|
|
{
|
|
int b = 1;
|
|
a = &b; // expected-warning {{local variable 'b' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*a; // expected-note {{later used here}}
|
|
}
|
|
|
|
void trivial_class_uaf() {
|
|
TriviallyDestructedClass* ptr;
|
|
{
|
|
TriviallyDestructedClass s;
|
|
ptr = &s; // expected-warning {{local variable 's' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)ptr; // expected-note {{later used here}}
|
|
}
|
|
|
|
void small_scope_reference_var_no_error() {
|
|
MyObj safe;
|
|
View view;
|
|
{
|
|
const MyObj& ref = safe;
|
|
view = ref;
|
|
}
|
|
view.use();
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Basic Use-After-Return (Return-Stack-Address)
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
MyObj* simple_return_stack_address() {
|
|
MyObj s;
|
|
MyObj* p = &s; // expected-warning {{stack memory associated with local variable 's' is returned}}
|
|
return p; // expected-note {{returned here}}
|
|
}
|
|
|
|
MyObj* direct_return() {
|
|
MyObj s;
|
|
return &s; // expected-warning {{stack memory associated with local variable 's' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
}
|
|
|
|
const MyObj& return_reference_to_param_no_error(const MyObj& in) {
|
|
return in;
|
|
}
|
|
|
|
const MyObj& return_reference_to_param_via_ref_no_error(const MyObj& in) {
|
|
const MyObj& ref = in;
|
|
return ref;
|
|
}
|
|
|
|
const MyObj* getPointer();
|
|
const MyObj& return_reference_to_param_via_pointer_no_error() {
|
|
const MyObj& ref = *getPointer();
|
|
return ref;
|
|
}
|
|
|
|
const MyObj* conditional_assign_unconditional_return(const MyObj& safe, bool c) {
|
|
MyObj s;
|
|
const MyObj* p = &safe;
|
|
if (c) {
|
|
p = &s; // expected-warning {{stack memory associated with local variable 's' is returned}}
|
|
}
|
|
return p; // expected-note {{returned here}}
|
|
}
|
|
|
|
View conditional_assign_both_branches(const MyObj& safe, bool c) {
|
|
MyObj s;
|
|
View p;
|
|
if (c) {
|
|
p = s; // expected-warning {{stack memory associated with local variable 's' is returned}}
|
|
}
|
|
else {
|
|
p = safe;
|
|
}
|
|
return p; // expected-note {{returned here}}
|
|
|
|
}
|
|
|
|
View reassign_safe_to_local(const MyObj& safe) {
|
|
MyObj local;
|
|
View p = safe;
|
|
p = local; // expected-warning {{stack memory associated with local variable 'local' is returned}}
|
|
return p; // expected-note {{returned here}}
|
|
}
|
|
|
|
View pointer_chain_to_local() {
|
|
MyObj local;
|
|
View p1 = local; // expected-warning {{stack memory associated with local variable 'local' is returned}}
|
|
View p2 = p1;
|
|
return p2; // expected-note {{returned here}}
|
|
}
|
|
|
|
View multiple_assign_multiple_return(const MyObj& safe, bool c1, bool c2) {
|
|
MyObj local1;
|
|
MyObj local2;
|
|
View p;
|
|
if (c1) {
|
|
p = local1; // expected-warning {{stack memory associated with local variable 'local1' is returned}}
|
|
return p; // expected-note {{returned here}}
|
|
}
|
|
else if (c2) {
|
|
p = local2; // expected-warning {{stack memory associated with local variable 'local2' is returned}}
|
|
return p; // expected-note {{returned here}}
|
|
}
|
|
p = safe;
|
|
return p;
|
|
}
|
|
|
|
View multiple_assign_single_return(const MyObj& safe, bool c1, bool c2) {
|
|
MyObj local1;
|
|
MyObj local2;
|
|
View p;
|
|
if (c1) {
|
|
p = local1; // expected-warning {{stack memory associated with local variable 'local1' is returned}}
|
|
}
|
|
else if (c2) {
|
|
p = local2; // expected-warning {{stack memory associated with local variable 'local2' is returned}}
|
|
}
|
|
else {
|
|
p = safe;
|
|
}
|
|
return p; // expected-note 2 {{returned here}}
|
|
}
|
|
|
|
View direct_return_of_local() {
|
|
MyObj stack;
|
|
return stack; // expected-warning {{stack memory associated with local variable 'stack' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
}
|
|
|
|
MyObj& reference_return_of_local() {
|
|
MyObj stack;
|
|
return stack; // expected-warning {{stack memory associated with local variable 'stack' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
}
|
|
|
|
int* trivial_int_uar() {
|
|
int *a;
|
|
int b = 1;
|
|
a = &b; // expected-warning {{stack memory associated with local variable 'b' is returned}}
|
|
return a; // expected-note {{returned here}}
|
|
}
|
|
|
|
TriviallyDestructedClass* trivial_class_uar () {
|
|
TriviallyDestructedClass *ptr;
|
|
TriviallyDestructedClass s;
|
|
ptr = &s; // expected-warning {{stack memory associated with local variable 's' is returned}}
|
|
return ptr; // expected-note {{returned here}}
|
|
}
|
|
|
|
const int& return_parameter(int a) {
|
|
return a; // expected-warning {{stack memory associated with parameter 'a' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
}
|
|
|
|
int* return_pointer_to_parameter(int a) {
|
|
return &a; // expected-warning {{stack memory associated with parameter 'a' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
}
|
|
|
|
const int& return_reference_to_parameter(int a) {
|
|
const int &b = a; // expected-warning {{stack memory associated with parameter 'a' is returned}}
|
|
return b; // expected-note {{returned here}}
|
|
}
|
|
int return_reference_to_parameter_no_error(int a) {
|
|
const int &b = a;
|
|
return b;
|
|
}
|
|
|
|
MyObj*& return_ref_to_local_ptr_pointing_to_local() {
|
|
MyObj local;
|
|
MyObj* p = &local; // expected-warning {{stack memory associated with local variable 'local' is returned}}
|
|
return p; // expected-note {{returned here}} \
|
|
// expected-warning {{stack memory associated with local variable 'p' is returned}} \
|
|
// expected-note {{returned here}}
|
|
}
|
|
|
|
const int& reference_via_conditional(int a, int b, bool cond) {
|
|
const int &c = (cond ? ((a)) : (b)); // expected-warning {{stack memory associated with parameter 'b' is returned}} expected-warning {{stack memory associated with parameter 'a' is returned}}
|
|
return c; // expected-note 2 {{returned here}}
|
|
}
|
|
const int* return_pointer_to_parameter_via_reference(int a, int b, bool cond) {
|
|
const int &c = cond ? a : b; // expected-warning {{stack memory associated with parameter 'b' is returned}} expected-warning {{stack memory associated with parameter 'a' is returned}}
|
|
const int* d = &c;
|
|
return d; // expected-note 2 {{returned here}}
|
|
}
|
|
|
|
const int& return_pointer_to_parameter_via_reference_1(int a) {
|
|
const int* d = &a; // expected-warning {{stack memory associated with parameter 'a' is returned}}
|
|
return *d; // expected-note {{returned here}}
|
|
}
|
|
|
|
const int& get_ref_to_local() {
|
|
int a = 42;
|
|
return a; // expected-warning {{stack memory associated with local variable 'a' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
}
|
|
|
|
void test_view_pointer() {
|
|
View* vp;
|
|
{
|
|
View v;
|
|
vp = &v; // expected-warning {{local variable 'v' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
vp->use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void test_view_double_pointer() {
|
|
View** vpp;
|
|
{
|
|
View* vp = nullptr;
|
|
vpp = &vp; // expected-warning {{local variable 'vp' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(**vpp).use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
struct PtrHolder {
|
|
int* ptr;
|
|
int* const& getRef() const [[clang::lifetimebound]] { return ptr; }
|
|
};
|
|
|
|
int* const& test_ref_to_ptr() {
|
|
PtrHolder a;
|
|
int *const &ref = a.getRef(); // expected-warning {{stack memory associated with local variable 'a' is returned}}
|
|
return ref; // expected-note {{returned here}}
|
|
}
|
|
int* const test_ref_to_ptr_no_error() {
|
|
PtrHolder a;
|
|
int *const &ref = a.getRef();
|
|
return ref;
|
|
}
|
|
|
|
int** return_inner_ptr_addr(int*** ppp [[clang::lifetimebound]]);
|
|
void test_lifetimebound_multi_level() {
|
|
int** result;
|
|
{
|
|
int* p = nullptr;
|
|
int** pp = &p;
|
|
int*** ppp = &pp; // expected-warning {{local variable 'pp' does not live long enough}}
|
|
result = return_inner_ptr_addr(ppp);
|
|
} // expected-note {{destroyed here}}
|
|
(void)**result; // expected-note {{used here}}
|
|
}
|
|
|
|
// FIXME: Assignment does not track the dereference of a pointer.
|
|
void test_assign_through_double_ptr() {
|
|
int a = 1, b = 2;
|
|
int* p = &a;
|
|
int** pp = &p;
|
|
{
|
|
int c = 3;
|
|
*pp = &c;
|
|
}
|
|
(void)**pp;
|
|
}
|
|
|
|
int** test_ternary_double_ptr(bool cond) {
|
|
int a = 1, b = 2;
|
|
int* pa = &a; // expected-warning {{stack memory associated with local variable 'a' is returned}}
|
|
int* pb = &b; // expected-warning {{stack memory associated with local variable 'b' is returned}}
|
|
int** result = cond ? &pa : &pb; // expected-warning {{stack memory associated with local variable 'pb' is returned}} expected-warning {{stack memory associated with local variable 'pa' is returned}}
|
|
return result; // expected-note 4 {{returned here}}
|
|
}
|
|
//===----------------------------------------------------------------------===//
|
|
// Use-After-Scope & Use-After-Return (Return-Stack-Address) Combined
|
|
// These are cases where the diagnostic kind is determined by location
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
MyObj* uaf_before_uar() {
|
|
MyObj* p;
|
|
{
|
|
MyObj local_obj;
|
|
p = &local_obj; // expected-warning {{local variable 'local_obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
return p; // expected-note {{later used here}}
|
|
}
|
|
|
|
View uar_before_uaf(const MyObj& safe, bool c) {
|
|
View p;
|
|
{
|
|
MyObj local_obj;
|
|
p = local_obj; // expected-warning {{stack memory associated with local variable 'local_obj' is returned}}
|
|
if (c) {
|
|
return p; // expected-note {{returned here}}
|
|
}
|
|
}
|
|
p.use();
|
|
p = safe;
|
|
return p;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// No-Error Cases
|
|
//===----------------------------------------------------------------------===//
|
|
void no_error_if_dangle_then_rescue() {
|
|
MyObj safe;
|
|
MyObj* p;
|
|
{
|
|
MyObj temp;
|
|
p = &temp; // p is temporarily dangling.
|
|
}
|
|
p = &safe; // p is "rescued" before use.
|
|
(void)*p; // This is safe.
|
|
}
|
|
|
|
void no_error_if_dangle_then_rescue_gsl() {
|
|
MyObj safe;
|
|
View v;
|
|
{
|
|
MyObj temp;
|
|
v = temp; // 'v' is temporarily dangling.
|
|
}
|
|
v = safe; // 'v' is "rescued" before use by reassigning to a valid object.
|
|
v.use(); // This is safe.
|
|
}
|
|
|
|
void no_error_if_dangle_then_rescue_via_ref() {
|
|
MyObj safe;
|
|
MyObj* p;
|
|
MyObj*& ref = p;
|
|
{
|
|
MyObj temp;
|
|
ref = &temp; // p temporarily points to temp via ref.
|
|
}
|
|
ref = &safe; // p is "rescued" via ref before use.
|
|
(void)*ref; // This is safe.
|
|
}
|
|
|
|
void no_error_loan_from_current_iteration(bool cond) {
|
|
// See https://github.com/llvm/llvm-project/issues/156959.
|
|
MyObj b;
|
|
while (cond) {
|
|
MyObj a;
|
|
View p = b;
|
|
if (cond) {
|
|
p = a;
|
|
}
|
|
(void)p;
|
|
}
|
|
}
|
|
|
|
View safe_return(const MyObj& safe) {
|
|
MyObj local;
|
|
View p = local;
|
|
p = safe; // p has been reassigned
|
|
return p; // This is safe
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Lifetimebound Attribute Tests
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
View Identity(View v [[clang::lifetimebound]]);
|
|
const MyObj& IdentityRef(const MyObj& obj [[clang::lifetimebound]]);
|
|
MyObj* Identity(MyObj* v [[clang::lifetimebound]]);
|
|
View Choose(bool cond, View a [[clang::lifetimebound]], View b [[clang::lifetimebound]]);
|
|
MyObj* GetPointer(const MyObj& obj [[clang::lifetimebound]]);
|
|
|
|
void lifetimebound_simple_function() {
|
|
View v;
|
|
{
|
|
MyObj obj;
|
|
v = Identity(obj); // expected-warning {{local variable 'obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void lifetimebound_multiple_args_definite() {
|
|
View v;
|
|
{
|
|
MyObj obj1, obj2;
|
|
v = Choose(true,
|
|
obj1, // expected-warning {{local variable 'obj1' does not live long enough}}
|
|
obj2); // expected-warning {{local variable 'obj2' does not live long enough}}
|
|
} // expected-note 2 {{destroyed here}}
|
|
v.use(); // expected-note 2 {{later used here}}
|
|
}
|
|
|
|
void lifetimebound_multiple_args_potential(bool cond) {
|
|
MyObj safe;
|
|
View v = safe;
|
|
{
|
|
MyObj obj1;
|
|
if (cond) {
|
|
MyObj obj2;
|
|
v = Choose(true,
|
|
obj1, // expected-warning {{local variable 'obj1' does not live long enough}}
|
|
obj2); // expected-warning {{local variable 'obj2' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
} // expected-note {{destroyed here}}
|
|
v.use(); // expected-note 2 {{later used here}}
|
|
}
|
|
|
|
View SelectFirst(View a [[clang::lifetimebound]], View b);
|
|
void lifetimebound_mixed_args() {
|
|
View v;
|
|
{
|
|
MyObj obj1, obj2;
|
|
v = SelectFirst(obj1, // expected-warning {{local variable 'obj1' does not live long enough}}
|
|
obj2);
|
|
} // expected-note {{destroyed here}}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
struct LifetimeBoundMember {
|
|
LifetimeBoundMember();
|
|
View get() const [[clang::lifetimebound]];
|
|
operator View() const [[clang::lifetimebound]];
|
|
};
|
|
|
|
void lifetimebound_member_function() {
|
|
View v;
|
|
{
|
|
MyObj obj;
|
|
v = obj.getView(); // expected-warning {{local variable 'obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
struct LifetimeBoundConversionView {
|
|
LifetimeBoundConversionView();
|
|
~LifetimeBoundConversionView();
|
|
operator View() const [[clang::lifetimebound]];
|
|
};
|
|
|
|
void lifetimebound_conversion_operator() {
|
|
View v;
|
|
{
|
|
LifetimeBoundConversionView obj;
|
|
v = obj; // expected-warning {{local variable 'obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void lifetimebound_chained_calls() {
|
|
View v;
|
|
{
|
|
MyObj obj;
|
|
v = Identity(Identity(Identity(obj))); // expected-warning {{local variable 'obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void lifetimebound_with_pointers() {
|
|
MyObj* ptr;
|
|
{
|
|
MyObj obj;
|
|
ptr = GetPointer(obj); // expected-warning {{local variable 'obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*ptr; // expected-note {{later used here}}
|
|
}
|
|
|
|
void chained_assignment_lifetimebound_call() {
|
|
MyObj *p, *obj;
|
|
{
|
|
MyObj s;
|
|
p = Identity(obj = &s); // expected-warning {{does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void lifetimebound_no_error_safe_usage() {
|
|
MyObj obj;
|
|
View v1 = Identity(obj); // No warning - obj lives long enough
|
|
View v2 = Choose(true, v1, Identity(obj)); // No warning - all args are safe
|
|
v2.use(); // Safe usage
|
|
}
|
|
|
|
void lifetimebound_partial_safety(bool cond) {
|
|
MyObj safe_obj;
|
|
View v = safe_obj;
|
|
|
|
if (cond) {
|
|
MyObj temp_obj;
|
|
v = Choose(true,
|
|
safe_obj,
|
|
temp_obj); // expected-warning {{local variable 'temp_obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
const MyObj& GetObject(View v [[clang::lifetimebound]]);
|
|
void lifetimebound_return_reference() {
|
|
View v;
|
|
const MyObj* ptr;
|
|
{
|
|
MyObj obj;
|
|
View temp_v = obj; // expected-warning {{local variable 'obj' does not live long enough}}
|
|
const MyObj& ref = GetObject(temp_v);
|
|
ptr = &ref;
|
|
} // expected-note {{destroyed here}}
|
|
(void)*ptr; // expected-note {{later used here}}
|
|
}
|
|
|
|
struct LifetimeBoundCtor {
|
|
LifetimeBoundCtor();
|
|
LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]);
|
|
LifetimeBoundCtor(const MyObj& obj, int); // not lifetimebound ctor.
|
|
LifetimeBoundCtor(int* p [[clang::lifetimebound]]);
|
|
LifetimeBoundCtor(std::string_view sv [[clang::lifetimebound]]);
|
|
};
|
|
|
|
void lifetimebound_ctor() {
|
|
LifetimeBoundCtor v;
|
|
{
|
|
MyObj obj;
|
|
v = obj; // expected-warning {{local variable 'obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)v; // expected-note {{later used here}}
|
|
}
|
|
|
|
void lifetimebound_ctor_functional_cast() {
|
|
LifetimeBoundCtor v;
|
|
{
|
|
MyObj obj;
|
|
v = LifetimeBoundCtor(obj); // expected-warning {{local variable 'obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)v; // expected-note {{later used here}}
|
|
}
|
|
|
|
void lifetimebound_ctor_c_style_cast() {
|
|
LifetimeBoundCtor v;
|
|
{
|
|
MyObj obj;
|
|
v = (LifetimeBoundCtor)(obj); // expected-warning {{local variable 'obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)v; // expected-note {{later used here}}
|
|
}
|
|
|
|
void lifetimebound_ctor_static_cast() {
|
|
LifetimeBoundCtor v;
|
|
{
|
|
MyObj obj;
|
|
v = static_cast<LifetimeBoundCtor>(obj); // expected-warning {{local variable 'obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)v; // expected-note {{later used here}}
|
|
}
|
|
|
|
void lifetimebound_make_unique() {
|
|
std::unique_ptr<LifetimeBoundCtor> ptr;
|
|
{
|
|
MyObj obj;
|
|
ptr = std::make_unique<LifetimeBoundCtor>(obj); // tu-warning {{local variable 'obj' does not live long enough}}
|
|
} // tu-note {{destroyed here}}
|
|
(void)ptr; // tu-note {{later used here}}
|
|
}
|
|
|
|
void non_lifetimebound_make_unique() {
|
|
std::unique_ptr<LifetimeBoundCtor> ptr;
|
|
{
|
|
MyObj obj;
|
|
// No error as the ctor is not lifetimebound.
|
|
ptr = std::make_unique<LifetimeBoundCtor>(obj, 0);
|
|
}
|
|
(void)ptr;
|
|
}
|
|
|
|
void lifetimebound_make_unique_temp() {
|
|
std::unique_ptr<LifetimeBoundCtor> ptr = std::make_unique<LifetimeBoundCtor>(MyObj()); // tu-warning {{local temporary object does not live long enough}} \
|
|
// tu-note {{destroyed here}}
|
|
(void)ptr; // tu-note {{later used here}}
|
|
}
|
|
|
|
// FIXME: make_unique annotation cannot be inferred for pointer param in constructor
|
|
void lifetimebound_make_unique_raw_ptr() {
|
|
std::unique_ptr<LifetimeBoundCtor> ptr;
|
|
int x = 0;
|
|
{
|
|
int* p = &x;
|
|
ptr = std::make_unique<LifetimeBoundCtor>(p);
|
|
}
|
|
(void)ptr;
|
|
}
|
|
|
|
// FIXME: make_unique annotation cannot be inferred for view-type param in constructor
|
|
void lifetimebound_make_unique_string_view_local() {
|
|
std::unique_ptr<LifetimeBoundCtor> ptr;
|
|
{
|
|
std::string s;
|
|
std::string_view sv(s);
|
|
ptr = std::make_unique<LifetimeBoundCtor>(sv);
|
|
}
|
|
(void)ptr;
|
|
}
|
|
|
|
struct MultiLifetimeBoundCtor {
|
|
MultiLifetimeBoundCtor(const MyObj& obj1 [[clang::lifetimebound]], const MyObj& obj2);
|
|
MultiLifetimeBoundCtor(const MyObj& obj1, const MyObj& obj2 [[clang::lifetimebound]], int);
|
|
MultiLifetimeBoundCtor(const MyObj& obj1 [[clang::lifetimebound]], const MyObj& obj2 [[clang::lifetimebound]], double);
|
|
};
|
|
|
|
void lifetimebound_make_unique_multi_params() {
|
|
std::unique_ptr<MultiLifetimeBoundCtor> ptr;
|
|
MyObj obj_long;
|
|
{
|
|
MyObj obj_short;
|
|
ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long); // tu-warning {{local variable 'obj_short' does not live long enough}}
|
|
} // tu-note {{destroyed here}}
|
|
(void)ptr; // tu-note {{later used here}}
|
|
}
|
|
|
|
void lifetimebound_make_unique_multi_params2() {
|
|
std::unique_ptr<MultiLifetimeBoundCtor> ptr;
|
|
MyObj obj_long;
|
|
{
|
|
MyObj obj_short;
|
|
ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1); // tu-warning {{local variable 'obj_short' does not live long enough}}
|
|
} // tu-note {{destroyed here}}
|
|
(void)ptr; // tu-note {{later used here}}
|
|
}
|
|
|
|
void lifetimebound_make_unique_multi_params2_no_error_case() {
|
|
std::unique_ptr<MultiLifetimeBoundCtor> ptr;
|
|
MyObj obj_long;
|
|
{
|
|
MyObj obj_short;
|
|
ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long, 1);
|
|
}
|
|
(void)ptr;
|
|
}
|
|
|
|
void lifetimebound_make_unique_multi_params3_1() {
|
|
std::unique_ptr<MultiLifetimeBoundCtor> ptr;
|
|
MyObj obj_long;
|
|
{
|
|
MyObj obj_short;
|
|
ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long, 1.0); // tu-warning {{local variable 'obj_short' does not live long enough}}
|
|
} // tu-note {{destroyed here}}
|
|
(void)ptr; // tu-note {{later used here}}
|
|
}
|
|
|
|
void lifetimebound_make_unique_multi_params3_2() {
|
|
std::unique_ptr<MultiLifetimeBoundCtor> ptr;
|
|
MyObj obj_long;
|
|
{
|
|
MyObj obj_short;
|
|
ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1.0); // tu-warning {{local variable 'obj_short' does not live long enough}}
|
|
} // tu-note {{destroyed here}}
|
|
(void)ptr; // tu-note {{later used here}}
|
|
}
|
|
|
|
View lifetimebound_return_of_local() {
|
|
MyObj stack;
|
|
return Identity(stack); // expected-warning {{stack memory associated with local variable 'stack' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
}
|
|
|
|
const MyObj& lifetimebound_return_ref_to_local() {
|
|
MyObj stack;
|
|
return IdentityRef(stack); // expected-warning {{stack memory associated with local variable 'stack' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
}
|
|
|
|
View lifetimebound_return_by_value_param(MyObj stack_param) {
|
|
return Identity(stack_param); // expected-warning {{stack memory associated with parameter 'stack_param' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
}
|
|
|
|
View lifetimebound_return_by_value_multiple_param(int cond, MyObj a, MyObj b, MyObj c) {
|
|
if (cond == 1)
|
|
return Identity(a); // expected-warning {{stack memory associated with parameter 'a' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
if (cond == 2)
|
|
return Identity(b); // expected-warning {{stack memory associated with parameter 'b' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
return Identity(c); // expected-warning {{stack memory associated with parameter 'c' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
}
|
|
|
|
template<class T>
|
|
View lifetimebound_return_by_value_param_template(T t) {
|
|
return Identity(t); // expected-warning {{stack memory associated with parameter 't' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
}
|
|
void use_lifetimebound_return_by_value_param_template() {
|
|
lifetimebound_return_by_value_param_template(MyObj{}); // function-note {{in instantiation of}}
|
|
}
|
|
|
|
void lambda_uar_param() {
|
|
auto lambda = [](MyObj stack_param) {
|
|
return Identity(stack_param); // expected-warning {{stack memory associated with parameter 'stack_param' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
};
|
|
lambda(MyObj{});
|
|
}
|
|
|
|
// FIXME: This should be detected. We see correct destructors but origin flow breaks somewhere.
|
|
namespace VariadicTemplatedParamsUAR {
|
|
|
|
template<typename... Args>
|
|
View Max(Args... args [[clang::lifetimebound]]);
|
|
|
|
template<typename... Args>
|
|
View lifetimebound_return_of_variadic_param(Args... args) {
|
|
return Max(args...);
|
|
}
|
|
void test_variadic() {
|
|
lifetimebound_return_of_variadic_param(MyObj{1}, MyObj{2}, MyObj{3});
|
|
}
|
|
} // namespace VariadicTemplatedParamsUAR
|
|
|
|
// FIXME: Fails to diagnose UAF when a reference to a by-value param escapes via an out-param.
|
|
void uaf_from_by_value_param_failing(MyObj param, View* out_p) {
|
|
*out_p = Identity(param);
|
|
}
|
|
|
|
// Conditional operator.
|
|
void conditional_operator_one_unsafe_branch(bool cond) {
|
|
MyObj safe;
|
|
MyObj* p = &safe;
|
|
{
|
|
MyObj temp;
|
|
p = cond ? &temp // expected-warning {{local variable 'temp' does not live long enough}}
|
|
: &safe;
|
|
} // expected-note {{destroyed here}}
|
|
|
|
// This is not a use-after-free for any value of `cond` but the analysis
|
|
// cannot reason this and marks the above as a false positive. This
|
|
// ensures safety regardless of cond's value.
|
|
if (cond)
|
|
p = &safe;
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void conditional_operator_two_unsafe_branches(bool cond) {
|
|
MyObj* p;
|
|
{
|
|
MyObj a, b;
|
|
p = cond ? &a // expected-warning {{local variable 'a' does not live long enough}}
|
|
: &b; // expected-warning {{local variable 'b' does not live long enough}}
|
|
} // expected-note 2 {{destroyed here}}
|
|
(void)*p; // expected-note 2 {{later used here}}
|
|
}
|
|
|
|
void conditional_operator_nested(bool cond) {
|
|
MyObj* p;
|
|
{
|
|
MyObj a, b, c, d;
|
|
p = cond ? cond ? &a // expected-warning {{local variable 'a' does not live long enough}}.
|
|
: &b // expected-warning {{local variable 'b' does not live long enough}}.
|
|
: cond ? &c // expected-warning {{local variable 'c' does not live long enough}}.
|
|
: &d; // expected-warning {{local variable 'd' does not live long enough}}.
|
|
} // expected-note 4 {{destroyed here}}
|
|
(void)*p; // expected-note 4 {{later used here}}
|
|
}
|
|
|
|
void conditional_operator_lifetimebound(bool cond) {
|
|
MyObj* p;
|
|
{
|
|
MyObj a, b;
|
|
p = Identity(cond ? &a // expected-warning {{local variable 'a' does not live long enough}}
|
|
: &b); // expected-warning {{local variable 'b' does not live long enough}}
|
|
} // expected-note 2 {{destroyed here}}
|
|
(void)*p; // expected-note 2 {{later used here}}
|
|
}
|
|
|
|
void conditional_operator_lifetimebound_nested(bool cond) {
|
|
MyObj* p;
|
|
{
|
|
MyObj a, b;
|
|
p = Identity(cond ? Identity(&a) // expected-warning {{local variable 'a' does not live long enough}}
|
|
: Identity(&b)); // expected-warning {{local variable 'b' does not live long enough}}
|
|
} // expected-note 2 {{destroyed here}}
|
|
(void)*p; // expected-note 2 {{later used here}}
|
|
}
|
|
|
|
void conditional_operator_lifetimebound_nested_deep(bool cond) {
|
|
MyObj* p;
|
|
{
|
|
MyObj a, b, c, d;
|
|
p = Identity(cond ? Identity(cond ? &a // expected-warning {{local variable 'a' does not live long enough}}
|
|
: &b) // expected-warning {{local variable 'b' does not live long enough}}
|
|
: Identity(cond ? &c // expected-warning {{local variable 'c' does not live long enough}}
|
|
: &d)); // expected-warning {{local variable 'd' does not live long enough}}
|
|
} // expected-note 4 {{destroyed here}}
|
|
(void)*p; // expected-note 4 {{later used here}}
|
|
}
|
|
|
|
void parentheses(bool cond) {
|
|
MyObj* p;
|
|
{
|
|
MyObj a;
|
|
p = &((((a)))); // expected-warning {{local variable 'a' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
|
|
{
|
|
MyObj a;
|
|
p = ((GetPointer((a)))); // expected-warning {{local variable 'a' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
|
|
{
|
|
MyObj a, b, c, d;
|
|
p = &(cond ? (cond ? a // expected-warning {{local variable 'a' does not live long enough}}.
|
|
: b) // expected-warning {{local variable 'b' does not live long enough}}.
|
|
: (cond ? c // expected-warning {{local variable 'c' does not live long enough}}.
|
|
: d)); // expected-warning {{local variable 'd' does not live long enough}}.
|
|
} // expected-note 4 {{destroyed here}}
|
|
(void)*p; // expected-note 4 {{later used here}}
|
|
|
|
{
|
|
MyObj a, b, c, d;
|
|
p = ((cond ? (((cond ? &a : &b))) // expected-warning {{local variable 'b' does not live long enough}} expected-warning {{local variable 'a' does not live long enough}}.
|
|
: &(((cond ? c : d))))); // expected-warning {{local variable 'd' does not live long enough}} expected-warning {{local variable 'c' does not live long enough}}.
|
|
} // expected-note 4 {{destroyed here}}
|
|
(void)*p; // expected-note 4 {{later used here}}
|
|
|
|
}
|
|
|
|
void use_temporary_after_destruction() {
|
|
View a;
|
|
a = non_trivially_destructed_temporary(); // expected-warning {{local temporary object does not live long enough}} \
|
|
expected-note {{destroyed here}}
|
|
use(a); // expected-note {{later used here}}
|
|
}
|
|
|
|
void passing_temporary_to_lifetime_bound_function() {
|
|
View a = construct_view(non_trivially_destructed_temporary()); // expected-warning {{local temporary object does not live long enough}} \
|
|
expected-note {{destroyed here}}
|
|
use(a); // expected-note {{later used here}}
|
|
}
|
|
|
|
void use_trivial_temporary_after_destruction() {
|
|
View a;
|
|
a = trivially_destructed_temporary(); // expected-warning {{local temporary object does not live long enough}} \
|
|
expected-note {{destroyed here}}
|
|
use(a); // expected-note {{later used here}}
|
|
}
|
|
|
|
namespace FullExprCleanupLoc {
|
|
void var_initializer() {
|
|
View v = non_trivially_destructed_temporary() // expected-warning {{local temporary object does not live long enough}}
|
|
.getView(); // expected-note {{destroyed here}}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void expr_statement() {
|
|
View v;
|
|
v = non_trivially_destructed_temporary() // expected-warning {{local temporary object does not live long enough}}
|
|
.getView(); // expected-note {{destroyed here}}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
} // namespace FullExprCleanupLoc
|
|
|
|
namespace GH162834 {
|
|
// https://github.com/llvm/llvm-project/issues/162834
|
|
template <class T>
|
|
struct StatusOr {
|
|
~StatusOr() {}
|
|
const T& value() const& [[clang::lifetimebound]] { return data; }
|
|
|
|
private:
|
|
T data;
|
|
};
|
|
|
|
StatusOr<View> getViewOr();
|
|
StatusOr<MyObj> getStringOr();
|
|
StatusOr<MyObj*> getPointerOr();
|
|
|
|
void foo() {
|
|
View view;
|
|
{
|
|
StatusOr<View> view_or = getViewOr();
|
|
view = view_or.value();
|
|
}
|
|
(void)view;
|
|
}
|
|
|
|
void bar() {
|
|
MyObj* pointer;
|
|
{
|
|
StatusOr<MyObj*> pointer_or = getPointerOr();
|
|
pointer = pointer_or.value();
|
|
}
|
|
(void)*pointer;
|
|
}
|
|
|
|
void foobar() {
|
|
View view;
|
|
{
|
|
StatusOr<MyObj> string_or = getStringOr();
|
|
view = string_or. // expected-warning {{local variable 'string_or' does not live long enough}}
|
|
value();
|
|
} // expected-note {{destroyed here}}
|
|
(void)view; // expected-note {{later used here}}
|
|
}
|
|
} // namespace GH162834
|
|
|
|
namespace RangeBasedForLoop {
|
|
struct MyObjStorage {
|
|
MyObj objs[1];
|
|
MyObjStorage() {}
|
|
~MyObjStorage() {}
|
|
const MyObj *begin() const [[clang::lifetimebound]] { return objs; }
|
|
const MyObj *end() const { return objs + 1; }
|
|
};
|
|
|
|
void range_based_for_use_after_scope() {
|
|
View v;
|
|
{
|
|
MyObjStorage s;
|
|
for (const MyObj &o : s) { // expected-warning {{local variable 's' does not live long enough}}
|
|
v = o;
|
|
}
|
|
} // expected-note {{destroyed here}}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
View range_based_for_use_after_return() {
|
|
MyObjStorage s;
|
|
for (const MyObj &o : s) { // expected-warning {{stack memory associated with local variable 's' is returned}}
|
|
return o; // expected-note {{returned here}}
|
|
}
|
|
return *s.begin(); // expected-warning {{stack memory associated with local variable 's' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
}
|
|
|
|
void range_based_for_not_reference() {
|
|
View v;
|
|
{
|
|
MyObjStorage s;
|
|
for (MyObj o : s) { // expected-note {{destroyed here}}
|
|
v = o; // expected-warning {{local variable 'o' does not live long enough}}
|
|
}
|
|
}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void range_based_for_no_error() {
|
|
View v;
|
|
MyObjStorage s;
|
|
for (const MyObj &o : s) {
|
|
v = o;
|
|
}
|
|
v.use();
|
|
}
|
|
|
|
} // namespace RangeBaseForLoop
|
|
|
|
namespace UserDefinedDereference {
|
|
// Test user-defined dereference operators with lifetimebound
|
|
template<typename T>
|
|
struct SmartPtr {
|
|
T* ptr;
|
|
SmartPtr() {}
|
|
SmartPtr(T* p) : ptr(p) {}
|
|
T& operator*() const [[clang::lifetimebound]] { return *ptr; }
|
|
T* operator->() const [[clang::lifetimebound]] { return ptr; }
|
|
};
|
|
|
|
void test_user_defined_deref_uaf() {
|
|
MyObj* p;
|
|
{
|
|
MyObj obj;
|
|
SmartPtr<MyObj> smart_ptr(&obj);
|
|
p = &(*smart_ptr); // expected-warning {{local variable 'smart_ptr' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
MyObj& test_user_defined_deref_uar() {
|
|
MyObj obj;
|
|
SmartPtr<MyObj> smart_ptr(&obj);
|
|
return *smart_ptr; // expected-warning {{stack memory associated with local variable 'smart_ptr' is returned}}
|
|
// expected-note@-1 {{returned here}}
|
|
}
|
|
|
|
void test_user_defined_deref_with_view() {
|
|
View v;
|
|
{
|
|
MyObj obj;
|
|
SmartPtr<MyObj> smart_ptr(&obj);
|
|
v = *smart_ptr; // expected-warning {{local variable 'smart_ptr' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void test_user_defined_deref_arrow() {
|
|
MyObj* p;
|
|
{
|
|
MyObj obj;
|
|
SmartPtr<MyObj> smart_ptr(&obj);
|
|
p = smart_ptr.operator->(); // expected-warning {{local variable 'smart_ptr' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void test_user_defined_deref_chained() {
|
|
MyObj* p;
|
|
{
|
|
MyObj obj;
|
|
SmartPtr<SmartPtr<MyObj>> double_ptr;
|
|
p = &(**double_ptr); // expected-warning {{local variable 'double_ptr' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
} // namespace UserDefinedDereference
|
|
|
|
namespace structured_binding {
|
|
struct Pair {
|
|
MyObj a;
|
|
MyObj b;
|
|
Pair() {}
|
|
~Pair() {}
|
|
};
|
|
|
|
// FIXME: Detect this.
|
|
void structured_binding_use_after_scope() {
|
|
View v;
|
|
{
|
|
Pair p;
|
|
auto &[a_ref, b_ref] = p;
|
|
v = a_ref;
|
|
}
|
|
v.use();
|
|
}
|
|
}
|
|
|
|
namespace MaxFnLifetimeBound {
|
|
|
|
template<class T>
|
|
T&& MaxT(T&& a [[clang::lifetimebound]], T&& b [[clang::lifetimebound]]);
|
|
|
|
const MyObj& call_max_with_obj() {
|
|
MyObj oa, ob;
|
|
return MaxT(oa, // expected-warning {{stack memory associated with local variable 'oa' is returned}}
|
|
// expected-note@-1 2 {{returned here}}
|
|
ob); // expected-warning {{stack memory associated with local variable 'ob' is returned}}
|
|
|
|
}
|
|
|
|
MyObj* call_max_with_obj_error() {
|
|
MyObj oa, ob;
|
|
return &MaxT(oa, // expected-warning {{stack memory associated with local variable 'oa' is returned}}
|
|
// expected-note@-1 2 {{returned here}}
|
|
ob); // expected-warning {{stack memory associated with local variable 'ob' is returned}}
|
|
}
|
|
|
|
const MyObj* call_max_with_ref_obj_error() {
|
|
MyObj oa, ob;
|
|
const MyObj& refa = oa; // expected-warning {{stack memory associated with local variable 'oa' is returned}}
|
|
const MyObj& refb = ob; // expected-warning {{stack memory associated with local variable 'ob' is returned}}
|
|
return &MaxT(refa, refb); // expected-note 2 {{returned here}}
|
|
}
|
|
const MyObj& call_max_with_ref_obj_return_ref_error() {
|
|
MyObj oa, ob;
|
|
const MyObj& refa = oa; // expected-warning {{stack memory associated with local variable 'oa' is returned}}
|
|
const MyObj& refb = ob; // expected-warning {{stack memory associated with local variable 'ob' is returned}}
|
|
return MaxT(refa, refb); // expected-note 2 {{returned here}}
|
|
}
|
|
|
|
MyObj call_max_with_obj_no_error() {
|
|
MyObj oa, ob;
|
|
return MaxT(oa, ob);
|
|
}
|
|
|
|
const MyObj& call_max_with_ref_obj_no_error(const MyObj& a, const MyObj& b) {
|
|
return MaxT(a, b);
|
|
}
|
|
|
|
const View& call_max_with_view_with_error() {
|
|
View va, vb;
|
|
return MaxT(va, // expected-warning {{stack memory associated with local variable 'va' is returned}}
|
|
// expected-note@-1 2 {{returned here}}
|
|
vb); // expected-warning {{stack memory associated with local variable 'vb' is returned}}
|
|
}
|
|
|
|
struct [[gsl::Pointer]] NonTrivialPointer { ~NonTrivialPointer(); };
|
|
|
|
const NonTrivialPointer& call_max_with_non_trivial_view_with_error() {
|
|
NonTrivialPointer va, vb;
|
|
return MaxT(va, // expected-warning {{stack memory associated with local variable 'va' is returned}}
|
|
// expected-note@-1 2 {{returned here}}
|
|
vb); // expected-warning {{stack memory associated with local variable 'vb' is returned}}
|
|
}
|
|
|
|
namespace MultiPointerTypes {
|
|
int** return_2p() {
|
|
int a = 1;
|
|
int* b = &a; // expected-warning {{stack memory associated with local variable 'a' is returned}}
|
|
int** c = &b; // expected-warning {{stack memory associated with local variable 'b' is returned}}
|
|
return c; // expected-note 2 {{returned here}}
|
|
}
|
|
|
|
int** return_2p_one_is_safe(int& a) {
|
|
int* b = &a;
|
|
int** c = &b; // expected-warning {{stack memory associated with local variable 'b' is returned}}
|
|
return c; // expected-note {{returned here}}
|
|
}
|
|
|
|
int*** return_3p() {
|
|
int a = 1;
|
|
int* b = &a; // expected-warning {{stack memory associated with local variable 'a' is returned}}
|
|
int** c = &b; // expected-warning {{stack memory associated with local variable 'b' is returned}}
|
|
int*** d = &c; // expected-warning {{stack memory associated with local variable 'c' is returned}}
|
|
return d; // expected-note 3 {{returned here}}
|
|
}
|
|
|
|
View** return_view_p() {
|
|
MyObj a;
|
|
View b = a; // expected-warning {{stack memory associated with local variable 'a' is returned}}
|
|
View* c = &b; // expected-warning {{stack memory associated with local variable 'b' is returned}}
|
|
View** d = &c; // expected-warning {{stack memory associated with local variable 'c' is returned}}
|
|
return d; // expected-note 3 {{returned here}}
|
|
}
|
|
|
|
} // namespace MultiPointerTypes
|
|
|
|
View call_max_with_view_without_error() {
|
|
View va, vb;
|
|
return MaxT(va, vb);
|
|
}
|
|
|
|
} // namespace StdMaxStyleLifetimeBound
|
|
|
|
namespace CppCoverage {
|
|
|
|
int getInt();
|
|
|
|
void ReferenceParam(unsigned Value, unsigned &Ref) {
|
|
Value = getInt();
|
|
Ref = getInt();
|
|
}
|
|
|
|
inline void normalize(int &exponent, int &mantissa) {
|
|
const int shift = 1;
|
|
exponent -= shift;
|
|
mantissa <<= shift;
|
|
}
|
|
|
|
void add(int c, MyObj* node) {
|
|
MyObj* arr[10];
|
|
arr[4] = node;
|
|
}
|
|
} // namespace CppCoverage
|
|
|
|
namespace strict_warn_on_move {
|
|
void strict_warn_on_move() {
|
|
MyObj b;
|
|
View v;
|
|
{
|
|
MyObj a;
|
|
v = a; // expected-warning {{local variable 'a' may not live long enough. This could be a false positive as the storage may have been moved later}}
|
|
b = std::move(a); // expected-note {{potentially moved here}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)v; // expected-note {{later used here}}
|
|
}
|
|
|
|
void flow_sensitive(bool c) {
|
|
View v;
|
|
{
|
|
MyObj a;
|
|
if (c) {
|
|
MyObj b = std::move(a);
|
|
return;
|
|
}
|
|
v = a; // expected-warning {{local variable 'a' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)v; // expected-note {{later used here}}
|
|
}
|
|
|
|
void take(MyObj&&);
|
|
void detect_conditional(bool cond) {
|
|
View v;
|
|
{
|
|
MyObj a, b;
|
|
v = cond ? a : b; // expected-warning {{local variable 'a' may not live long enough. This could be a false positive as the storage may have been moved later}} \
|
|
// expected-warning {{local variable 'b' may not live long enough. This could be a false positive as the storage may have been moved later}}
|
|
take(std::move(cond ? a : b)); // expected-note 2 {{potentially moved here}}
|
|
} // expected-note 2 {{destroyed here}}
|
|
(void)v; // expected-note 2 {{later used here}}
|
|
}
|
|
|
|
void wrong_use_of_move_is_permissive() {
|
|
View v;
|
|
{
|
|
MyObj a;
|
|
v = std::move(a); // expected-warning {{local variable 'a' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)v; // expected-note {{later used here}}
|
|
const int* p;
|
|
{
|
|
MyObj a;
|
|
p = std::move(a).getData(); // expected-warning {{local variable 'a' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void take(int*);
|
|
void test_release_no_uaf() {
|
|
int* r;
|
|
// Calling release() marks p as moved from, so its destruction doesn't invalidate r.
|
|
{
|
|
std::unique_ptr<int> p;
|
|
r = p.get(); // expected-warning {{local variable 'p' may not live long enough. This could be a false positive as the storage may have been moved later}}
|
|
take(p.release()); // expected-note {{potentially moved here}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*r; // expected-note {{later used here}}
|
|
}
|
|
} // namespace strict_warn_on_move
|
|
|
|
// Implicit this annotations with redecls.
|
|
namespace GH172013 {
|
|
// https://github.com/llvm/llvm-project/issues/62072
|
|
// https://github.com/llvm/llvm-project/issues/172013
|
|
struct S {
|
|
View x() const [[clang::lifetimebound]];
|
|
MyObj i;
|
|
};
|
|
|
|
View S::x() const { return i; }
|
|
|
|
void bar() {
|
|
View x;
|
|
{
|
|
S s;
|
|
x = s.x(); // expected-warning {{local variable 's' does not live long enough}}
|
|
View y = S().x(); // expected-warning {{local temporary object does not live long enough}} \
|
|
expected-note {{destroyed here}}
|
|
(void)y; // expected-note {{used here}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)x; // expected-note {{used here}}
|
|
}
|
|
}
|
|
|
|
namespace DereferenceViews {
|
|
const MyObj& testDeref(MyObj obj) {
|
|
View v = obj; // expected-warning {{stack memory associated with parameter 'obj' is returned}}
|
|
return *v; // expected-note {{returned here}}
|
|
}
|
|
const MyObj* testDerefAddr(MyObj obj) {
|
|
View v = obj; // expected-warning {{stack memory associated with parameter 'obj' is returned}}
|
|
return &*v; // expected-note {{returned here}}
|
|
}
|
|
const MyObj* testData(MyObj obj) {
|
|
View v = obj; // expected-warning {{stack memory associated with parameter 'obj' is returned}}
|
|
return v.data(); // expected-note {{returned here}}
|
|
}
|
|
const int* testLifetimeboundAccessorOfMyObj(MyObj obj) {
|
|
View v = obj; // expected-warning {{stack memory associated with parameter 'obj' is returned}}
|
|
const MyObj* ptr = v.data();
|
|
return ptr->getData(); // expected-note {{returned here}}
|
|
}
|
|
const int* testLifetimeboundAccessorOfMyObjThroughDeref(MyObj obj) {
|
|
View v = obj; // expected-warning {{stack memory associated with parameter 'obj' is returned}}
|
|
return v->getData(); // expected-note {{returned here}}
|
|
}
|
|
} // namespace DereferenceViews
|
|
|
|
namespace ViewsBeginEndIterators {
|
|
template <typename T>
|
|
struct [[gsl::Pointer]] Iterator {
|
|
Iterator operator++();
|
|
T& operator*() const;
|
|
T* operator->() const;
|
|
bool operator!=(const Iterator& other) const;
|
|
};
|
|
|
|
template <typename T>
|
|
struct [[gsl::Owner]] Container {
|
|
using It = Iterator<T>;
|
|
It begin() const [[clang::lifetimebound]];
|
|
It end() const [[clang::lifetimebound]];
|
|
};
|
|
|
|
MyObj Global;
|
|
|
|
const MyObj& ContainerMyObjReturnRef(Container<MyObj> c) {
|
|
for (const MyObj& x : c) { // expected-warning {{stack memory associated with parameter 'c' is returned}}
|
|
return x; // expected-note {{returned here}}
|
|
}
|
|
return Global;
|
|
}
|
|
|
|
View ContainerMyObjReturnView(Container<MyObj> c) {
|
|
for (const MyObj& x : c) { // expected-warning {{stack memory associated with parameter 'c' is returned}}
|
|
return x; // expected-note {{returned here}}
|
|
}
|
|
for (View x : c) { // expected-warning {{stack memory associated with parameter 'c' is returned}}
|
|
return x; // expected-note {{returned here}}
|
|
}
|
|
return Global;
|
|
}
|
|
|
|
View ContainerViewsOk(Container<View> c) {
|
|
for (View x : c) {
|
|
return x;
|
|
}
|
|
for (const View& x : c) {
|
|
return x;
|
|
}
|
|
return Global;
|
|
}
|
|
} // namespace ViewsBeginEndIterators
|
|
|
|
namespace reference_type_decl_ref_expr {
|
|
struct S {
|
|
S();
|
|
~S();
|
|
const std::string& x() const [[clang::lifetimebound]];
|
|
};
|
|
|
|
const std::string& identity(const std::string& in [[clang::lifetimebound]]);
|
|
const S& identity(const S& in [[clang::lifetimebound]]);
|
|
|
|
void test_temporary() {
|
|
const std::string& x = S().x(); // expected-warning {{local temporary object does not live long enough}} expected-note {{destroyed here}}
|
|
(void)x; // expected-note {{later used here}}
|
|
|
|
const std::string& y = identity(S().x()); // expected-warning {{local temporary object does not live long enough}} expected-note {{destroyed here}}
|
|
(void)y; // expected-note {{later used here}}
|
|
|
|
std::string_view z;
|
|
{
|
|
S s;
|
|
const std::string& zz = s.x(); // expected-warning {{local variable 's' does not live long enough}}
|
|
z = zz;
|
|
} // expected-note {{destroyed here}}
|
|
(void)z; // expected-note {{later used here}}
|
|
}
|
|
|
|
void test_lifetime_extension_ok() {
|
|
const S& x = S();
|
|
(void)x;
|
|
const S& y = identity(S()); // expected-warning {{local temporary object does not live long enough}} expected-note {{destroyed here}}
|
|
(void)y; // expected-note {{later used here}}
|
|
}
|
|
|
|
const std::string& test_return() {
|
|
const std::string& x = S().x(); // expected-warning {{local temporary object does not live long enough}} expected-note {{destroyed here}}
|
|
return x; // expected-note {{later used here}}
|
|
}
|
|
} // namespace reference_type_decl_ref_expr
|
|
|
|
namespace field_access {
|
|
|
|
struct S {
|
|
std::string s;
|
|
std::string_view sv;
|
|
};
|
|
|
|
void uaf() {
|
|
std::string_view view;
|
|
{
|
|
S str;
|
|
S* p = &str; // expected-warning {{local variable 'str' does not live long enough}}
|
|
view = p->s;
|
|
} // expected-note {{destroyed here}}
|
|
(void)view; // expected-note {{later used here}}
|
|
}
|
|
|
|
void not_uaf() {
|
|
std::string_view view;
|
|
{
|
|
S str;
|
|
S* p = &str;
|
|
view = p->sv;
|
|
}
|
|
(void)view;
|
|
}
|
|
|
|
union U {
|
|
std::string s;
|
|
std::string_view sv;
|
|
~U() {}
|
|
};
|
|
|
|
void uaf_union() {
|
|
std::string_view view;
|
|
{
|
|
U u = U{"hello"};
|
|
U* up = &u; // expected-warning {{local variable 'u' does not live long enough}}
|
|
view = up->s;
|
|
} // expected-note {{destroyed here}}
|
|
(void)view; // expected-note {{later used here}}
|
|
}
|
|
|
|
struct AnonymousUnion {
|
|
union {
|
|
int x;
|
|
float y;
|
|
};
|
|
};
|
|
|
|
void uaf_anonymous_union() {
|
|
int* ip;
|
|
{
|
|
AnonymousUnion au;
|
|
AnonymousUnion* up = &au; // expected-warning {{local variable 'au' does not live long enough}}
|
|
ip = &up->x;
|
|
} // expected-note {{destroyed here}}
|
|
(void)ip; // expected-note {{later used here}}
|
|
}
|
|
|
|
struct RefMember {
|
|
std::string& str_ref;
|
|
std::string* str_ptr;
|
|
std::string str;
|
|
std::string_view view;
|
|
std::string_view& view_ref;
|
|
RefMember();
|
|
~RefMember();
|
|
};
|
|
|
|
std::string_view refMemberReturnView1(RefMember a) { return a.str_ref; }
|
|
std::string_view refMemberReturnView2(RefMember a) { return *a.str_ptr; }
|
|
std::string_view refMemberReturnView3(RefMember a) { return a.str; } // expected-warning {{stack memory associated with parameter 'a' is returned}} expected-note {{returned here}}
|
|
std::string& refMemberReturnRef1(RefMember a) { return a.str_ref; }
|
|
std::string& refMemberReturnRef2(RefMember a) { return *a.str_ptr; }
|
|
std::string& refMemberReturnRef3(RefMember a) { return a.str; } // expected-warning {{stack memory associated with parameter 'a' is returned}} expected-note {{returned here}}
|
|
std::string_view refViewMemberReturnView1(RefMember a) { return a.view; }
|
|
std::string_view& refViewMemberReturnView2(RefMember a) { return a.view; } // expected-warning {{stack memory associated with parameter 'a' is returned}} expected-note {{returned here}}
|
|
std::string_view refViewMemberReturnRefView1(RefMember a) { return a.view_ref; }
|
|
std::string_view& refViewMemberReturnRefView2(RefMember a) { return a.view_ref; }
|
|
} // namespace field_access
|
|
|
|
namespace attr_on_template_params {
|
|
struct MyObj {
|
|
~MyObj();
|
|
};
|
|
|
|
template <typename T>
|
|
struct MemberFuncsTpl {
|
|
~MemberFuncsTpl();
|
|
// Template Version A: Attribute on declaration only
|
|
const T* memberA(const T& x [[clang::lifetimebound]]);
|
|
// Template Version B: Attribute on definition only
|
|
const T* memberB(const T& x);
|
|
// Template Version C: Attribute on BOTH declaration and definition
|
|
const T* memberC(const T& x [[clang::lifetimebound]]);
|
|
};
|
|
|
|
template <typename T>
|
|
const T* MemberFuncsTpl<T>::memberA(const T& x) {
|
|
return &x;
|
|
}
|
|
template <typename T>
|
|
const T* MemberFuncsTpl<T>::memberB(const T& x [[clang::lifetimebound]]) {
|
|
return &x;
|
|
}
|
|
template <typename T>
|
|
const T* MemberFuncsTpl<T>::memberC(const T& x [[clang::lifetimebound]]) {
|
|
return &x;
|
|
}
|
|
|
|
void test() {
|
|
MemberFuncsTpl<MyObj> mtf;
|
|
const MyObj* pTMA = mtf.memberA(MyObj()); // expected-warning {{local temporary object does not live long enough}} // expected-note {{destroyed here}}
|
|
const MyObj* pTMB = mtf.memberB(MyObj()); // tu-warning {{local temporary object does not live long enough}} // tu-note {{destroyed here}}
|
|
const MyObj* pTMC = mtf.memberC(MyObj()); // expected-warning {{local temporary object does not live long enough}} // expected-note {{destroyed here}}
|
|
(void)pTMA; // expected-note {{later used here}}
|
|
(void)pTMB; // tu-note {{later used here}}
|
|
(void)pTMC; // expected-note {{later used here}}
|
|
}
|
|
|
|
} // namespace attr_on_template_params
|
|
|
|
namespace non_trivial_views {
|
|
struct [[gsl::Pointer]] View {
|
|
View(const std::string&);
|
|
~View(); // Forces a CXXBindTemporaryExpr.
|
|
};
|
|
|
|
View test1(std::string a) {
|
|
// Make sure we handle CXXBindTemporaryExpr of view types.
|
|
return View(a); // expected-warning {{stack memory associated with parameter 'a' is returned}} expected-note {{returned here}}
|
|
}
|
|
|
|
View test2(std::string a) {
|
|
View b = View(a); // expected-warning {{stack memory associated with parameter 'a' is returned}}
|
|
return b; // expected-note {{returned here}}
|
|
}
|
|
|
|
View test3(std::string a) {
|
|
const View& b = View(a); // expected-warning {{stack memory associated with parameter 'a' is returned}}
|
|
return b; // expected-note {{returned here}}
|
|
}
|
|
} // namespace non_trivial_views
|
|
|
|
namespace OwnerArrowOperator {
|
|
void test_optional_arrow() {
|
|
const char* p;
|
|
{
|
|
std::optional<std::string> opt;
|
|
p = opt->data(); // expected-warning {{local variable 'opt' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void test_optional_arrow_lifetimebound() {
|
|
View v;
|
|
{
|
|
std::optional<MyObj> opt;
|
|
v = opt->getView(); // expected-warning {{local variable 'opt' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
v.use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void test_unique_ptr_arrow() {
|
|
const char* p;
|
|
{
|
|
std::unique_ptr<std::string> up;
|
|
p = up->data(); // expected-warning {{local variable 'up' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void test_optional_view_arrow() {
|
|
const char* p;
|
|
{
|
|
std::optional<std::string_view> opt;
|
|
p = opt->data();
|
|
}
|
|
(void)*p;
|
|
}
|
|
} // namespace OwnerArrowOperator
|
|
|
|
namespace lambda_captures {
|
|
auto return_ref_capture() {
|
|
int local = 1;
|
|
auto lambda = [&local]() { return local; }; // expected-warning {{stack memory associated with local variable 'local' is returned}}
|
|
return lambda; // expected-note {{returned here}}
|
|
}
|
|
|
|
void safe_ref_capture() {
|
|
int local = 1;
|
|
auto lambda = [&local]() { return local; };
|
|
lambda();
|
|
}
|
|
|
|
auto capture_int_by_value() {
|
|
int x = 1;
|
|
auto lambda = [x]() { return x; };
|
|
return lambda;
|
|
}
|
|
|
|
auto capture_view_by_value() {
|
|
MyObj obj;
|
|
View v(obj); // expected-warning {{stack memory associated with local variable 'obj' is returned}}
|
|
auto lambda = [v]() { return v; };
|
|
return lambda; // expected-note {{returned here}}
|
|
}
|
|
|
|
void capture_view_by_value_safe() {
|
|
MyObj obj;
|
|
View v(obj);
|
|
auto lambda = [v]() { return v; };
|
|
lambda();
|
|
}
|
|
|
|
auto capture_pointer_by_ref() {
|
|
MyObj obj;
|
|
MyObj* p = &obj;
|
|
auto lambda = [&p]() { return p; }; // expected-warning {{stack memory associated with local variable 'p' is returned}}
|
|
return lambda; // expected-note {{returned here}}
|
|
}
|
|
|
|
auto capture_multiple() {
|
|
int a, b;
|
|
auto lambda = [
|
|
&a, // expected-warning {{stack memory associated with local variable 'a' is returned}}
|
|
&b // expected-warning {{stack memory associated with local variable 'b' is returned}}
|
|
]() { return a + b; };
|
|
return lambda; // expected-note 2 {{returned here}}
|
|
}
|
|
|
|
auto capture_raw_pointer_by_value() {
|
|
int x;
|
|
int* p = &x; // expected-warning {{stack memory associated with local variable 'x' is returned}}
|
|
auto lambda = [p]() { return p; };
|
|
return lambda; // expected-note {{returned here}}
|
|
}
|
|
|
|
auto capture_raw_pointer_init_capture() {
|
|
int x;
|
|
int* p = &x; // expected-warning {{stack memory associated with local variable 'x' is returned}}
|
|
auto lambda = [q = p]() { return q; };
|
|
return lambda; // expected-note {{returned here}}
|
|
}
|
|
|
|
auto capture_view_init_capture() {
|
|
MyObj obj;
|
|
View v(obj); // expected-warning {{stack memory associated with local variable 'obj' is returned}}
|
|
auto lambda = [w = v]() { return w; };
|
|
return lambda; // expected-note {{returned here}}
|
|
}
|
|
|
|
auto capture_lambda() {
|
|
int x;
|
|
auto inner = [&x]() { return x; }; // expected-warning {{stack memory associated with local variable 'x' is returned}}
|
|
auto outer = [inner]() { return inner(); };
|
|
return outer; // expected-note {{returned here}}
|
|
}
|
|
|
|
auto return_copied_lambda() {
|
|
int local = 1;
|
|
auto lambda = [&local]() { return local; }; // expected-warning {{stack memory associated with local variable 'local' is returned}}
|
|
auto lambda_copy = lambda;
|
|
return lambda_copy; // expected-note {{returned here}}
|
|
}
|
|
|
|
auto implicit_ref_capture() {
|
|
int local = 1;
|
|
auto lambda = [&]() { return local; }; // expected-warning {{stack memory associated with local variable 'local' is returned}}
|
|
return lambda; // expected-note {{returned here}}
|
|
}
|
|
|
|
// TODO: Include the name of the variable in the diagnostic to improve
|
|
// clarity, especially for implicit lambda captures where multiple warnings
|
|
// can point to the same source location.
|
|
auto implicit_ref_capture_multiple() {
|
|
int local = 1, local2 = 2;
|
|
auto lambda = [&]() { return local + local2; }; // expected-warning {{stack memory associated with local variable 'local2' is returned}} expected-warning {{stack memory associated with local variable 'local' is returned}}
|
|
return lambda; // expected-note 2 {{returned here}}
|
|
}
|
|
|
|
auto implicit_value_capture() {
|
|
MyObj obj;
|
|
View v(obj); // expected-warning {{stack memory associated with local variable 'obj' is returned}}
|
|
auto lambda = [=]() { return v; };
|
|
return lambda; // expected-note {{returned here}}
|
|
}
|
|
|
|
auto* pointer_to_lambda_outlives() {
|
|
auto lambda = []() { return 42; };
|
|
return λ // expected-warning {{stack memory associated with local variable 'lambda' is returned}} \
|
|
// expected-note {{returned here}}
|
|
}
|
|
|
|
auto capture_static() {
|
|
static int local = 1;
|
|
// Only automatic storage duration variables may be captured.
|
|
// Variables with static storage duration behave like globals and are directly accessible.
|
|
// The below lambdas should not capture `local`.
|
|
auto lambda = [&]() { return local; };
|
|
auto lambda2 = []() { return local; };
|
|
lambda2();
|
|
return lambda;
|
|
}
|
|
|
|
auto capture_static_address_by_value() {
|
|
static int local = 1;
|
|
int* p = &local;
|
|
auto lambda = [p]() { return p; };
|
|
return lambda;
|
|
}
|
|
|
|
auto capture_static_address_by_ref() {
|
|
static int local = 1;
|
|
int* p = &local;
|
|
auto lambda = [&p]() { return p; }; // expected-warning {{stack memory associated with local variable 'p' is returned}}
|
|
return lambda; // expected-note {{returned here}}
|
|
}
|
|
|
|
auto capture_multilevel_pointer() {
|
|
int x;
|
|
int *p = &x; // expected-warning {{stack memory associated with local variable 'x' is returned}}
|
|
int **q = &p; // expected-warning {{stack memory associated with local variable 'p' is returned}}
|
|
int ***r = &q; // expected-warning {{stack memory associated with local variable 'q' is returned}}
|
|
auto lambda = [=]() { return *p + **q + ***r; };
|
|
return lambda; // expected-note 3 {{returned here}}
|
|
}
|
|
} // namespace lambda_captures
|
|
|
|
namespace LoopLocalPointers {
|
|
|
|
void conditional_assignment_in_loop() {
|
|
for (int i = 0; i < 10; ++i) {
|
|
MyObj obj;
|
|
MyObj* view;
|
|
if (i > 5) {
|
|
view = &obj;
|
|
}
|
|
(void)*view;
|
|
}
|
|
}
|
|
|
|
void unconditional_assignment_in_loop() {
|
|
for (int i = 0; i < 10; ++i) {
|
|
MyObj obj;
|
|
MyObj* view = &obj;
|
|
(void)*view;
|
|
}
|
|
}
|
|
|
|
// FIXME: False positive. Requires modeling flow-sensitive aliased origins
|
|
// to properly expire pp's inner origin when p's lifetime ends.
|
|
void multi_level_pointer_in_loop() {
|
|
for (int i = 0; i < 10; ++i) {
|
|
MyObj obj;
|
|
MyObj* p;
|
|
MyObj** pp;
|
|
if (i > 5) {
|
|
p = &obj; // expected-warning {{local variable 'obj' does not live long enough}}
|
|
pp = &p;
|
|
}
|
|
(void)**pp; // expected-note {{later used here}}
|
|
} // expected-note {{destroyed here}}
|
|
}
|
|
|
|
void outer_pointer_outlives_inner_pointee() {
|
|
MyObj safe;
|
|
MyObj* view = &safe;
|
|
for (int i = 0; i < 10; ++i) {
|
|
MyObj obj;
|
|
view = &obj; // expected-warning {{local variable 'obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*view; // expected-note {{later used here}}
|
|
}
|
|
|
|
} // namespace LoopLocalPointers
|
|
|
|
namespace array {
|
|
|
|
void element_use_after_scope() {
|
|
int* p;
|
|
{
|
|
int a[10]{};
|
|
p = &a[2]; // expected-warning {{local variable 'a' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
int* element_use_after_return() {
|
|
int a[10]{};
|
|
int* p = &a[0]; // expected-warning {{stack memory associated with local variable 'a' is returned}}
|
|
return p; // expected-note {{returned here}}
|
|
}
|
|
|
|
void element_use_same_scope() {
|
|
int a[10]{};
|
|
int* p = &a[0];
|
|
(void)*p;
|
|
}
|
|
|
|
void element_reassigned_safe() {
|
|
int safe[10]{};
|
|
int* p;
|
|
{
|
|
int a[10]{};
|
|
p = &a[0];
|
|
}
|
|
p = &safe[0]; // Rescued.
|
|
(void)*p;
|
|
}
|
|
|
|
void multidimensional_use_after_scope() {
|
|
int* p;
|
|
{
|
|
int a[3][4]{};
|
|
p = &a[1][2]; // expected-warning {{local variable 'a' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void member_array_element_use_after_scope() {
|
|
struct S {
|
|
int arr[10];
|
|
int b;
|
|
};
|
|
int* p;
|
|
{
|
|
S s;
|
|
p = &s.arr[0]; // expected-warning {{local variable 's' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void array_of_pointers_use_after_scope() {
|
|
int** p;
|
|
{
|
|
int* a[10]{};
|
|
p = a; // expected-warning {{local variable 'a' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void reversed_subscript_use_after_scope() {
|
|
int* p;
|
|
{
|
|
int a[10]{};
|
|
p = &(0[a]); // expected-warning {{local variable 'a' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
int* return_decayed_array() {
|
|
int a[10]{};
|
|
int *p = a; // expected-warning {{stack memory associated with local variable 'a' is returned}}
|
|
return p; // expected-note {{returned here}}
|
|
}
|
|
|
|
int* param_array_element(int a[], int n) {
|
|
return &a[n];
|
|
}
|
|
|
|
int* static_array() {
|
|
static int a[10]{};
|
|
return &a[1];
|
|
}
|
|
|
|
void pointer_arithmetic_use_after_scope() {
|
|
int* p;
|
|
int* p2;
|
|
int* p3;
|
|
{
|
|
int a[10]{};
|
|
p = a + 5; // expected-warning {{local variable 'a' does not live long enough}}
|
|
p2 = a - 5; // expected-warning {{local variable 'a' does not live long enough}}
|
|
p3 = 5 + a; // expected-warning {{local variable 'a' does not live long enough}}
|
|
} // expected-note 3 {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
(void)*p2; // expected-note {{later used here}}
|
|
(void)*p3; // expected-note {{later used here}}
|
|
}
|
|
|
|
// FIXME: Copying a pointer value out of an array element is not tracked.
|
|
void copy_pointer_from_array_use_after_scope() {
|
|
int* q;
|
|
{
|
|
int x = 0;
|
|
int* arr[10] = {&x};
|
|
q = arr[0];
|
|
}
|
|
(void)*q; // Should warn.
|
|
}
|
|
|
|
// FIXME: A pointer inside an array becoming dangling is not detected.
|
|
void pointer_in_array_use_after_scope() {
|
|
int* arr[10];
|
|
{
|
|
int x = 0;
|
|
arr[0] = &x;
|
|
}
|
|
(void)*arr[0]; // Should warn.
|
|
}
|
|
|
|
} // namespace array
|
|
|
|
namespace static_call_operator {
|
|
// https://github.com/llvm/llvm-project/issues/187426
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wc++23-extensions"
|
|
|
|
struct S {
|
|
static S operator()(int, int&&);
|
|
static S& operator()(std::string&&,
|
|
const int& a [[clang::lifetimebound]],
|
|
const int& b [[clang::lifetimebound]]);
|
|
};
|
|
|
|
void indexing_with_static_operator() {
|
|
S()(1, 2);
|
|
S& x = S()("1",
|
|
2, // expected-warning {{local temporary object does not live long enough}}
|
|
3); // expected-warning {{local temporary object does not live long enough}} expected-note 2 {{destroyed here}}
|
|
|
|
(void)x; // expected-note 2 {{later used here}}
|
|
|
|
}
|
|
} // namespace static_call_operator
|
|
|
|
namespace track_origins_for_lifetimebound_record_type {
|
|
|
|
template <class T> void use(T);
|
|
|
|
struct S {
|
|
S();
|
|
S(const std::string &s [[clang::lifetimebound]]);
|
|
|
|
S return_self_after_registration() const;
|
|
std::string_view getData() const [[clang::lifetimebound]];
|
|
};
|
|
|
|
S getS(const std::string &s [[clang::lifetimebound]]);
|
|
|
|
void from_free_function() {
|
|
S s = getS(std::string("temp")); // expected-warning {{local temporary object does not live long enough}} \
|
|
// expected-note {{destroyed here}}
|
|
use(s); // expected-note {{later used here}}
|
|
}
|
|
|
|
void from_constructor() {
|
|
S s(std::string("temp")); // expected-warning {{local temporary object does not live long enough}} \
|
|
// expected-note {{destroyed here}}
|
|
use(s); // expected-note {{later used here}}
|
|
}
|
|
|
|
struct Factory {
|
|
S make(const std::string &s [[clang::lifetimebound]]);
|
|
static S create(const std::string &s [[clang::lifetimebound]]);
|
|
S makeThis() const [[clang::lifetimebound]];
|
|
};
|
|
|
|
void from_method() {
|
|
Factory f;
|
|
S s = f.make(std::string("temp")); // expected-warning {{local temporary object does not live long enough}} \
|
|
// expected-note {{destroyed here}}
|
|
use(s); // expected-note {{later used here}}
|
|
}
|
|
|
|
void from_static_method() {
|
|
S s = Factory::create(std::string("temp")); // expected-warning {{local temporary object does not live long enough}} \
|
|
// expected-note {{destroyed here}}
|
|
use(s); // expected-note {{later used here}}
|
|
}
|
|
|
|
void from_lifetimebound_this_method() {
|
|
S value;
|
|
{
|
|
Factory f;
|
|
value = f.makeThis(); // expected-warning {{local variable 'f' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
use(value); // expected-note {{later used here}}
|
|
}
|
|
|
|
void across_scope() {
|
|
S s{};
|
|
{
|
|
std::string str{"abc"};
|
|
s = getS(str); // expected-warning {{local variable 'str' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
use(s); // expected-note {{later used here}}
|
|
}
|
|
|
|
void same_scope() {
|
|
std::string str{"abc"};
|
|
S s = getS(str);
|
|
use(s);
|
|
}
|
|
|
|
S copy_propagation() {
|
|
std::string str{"abc"};
|
|
S a = getS(str); // expected-warning {{stack memory associated with local variable 'str' is returned}}
|
|
S b = a;
|
|
return b; // expected-note {{returned here}}
|
|
}
|
|
|
|
void assignment_propagation() {
|
|
S a, b;
|
|
{
|
|
std::string str{"abc"};
|
|
a = getS(str); // expected-warning {{local variable 'str' does not live long enough}}
|
|
b = a;
|
|
} // expected-note {{destroyed here}}
|
|
use(b); // expected-note {{later used here}}
|
|
}
|
|
|
|
void chained_defaulted_assignment_propagation() {
|
|
S b, c;
|
|
{
|
|
std::string str{"abc"};
|
|
S a = getS(str); // expected-warning {{local variable 'str' does not live long enough}}
|
|
c = b = a;
|
|
} // expected-note {{destroyed here}}
|
|
use(c); // expected-note {{later used here}}
|
|
}
|
|
|
|
S getSNoAnnotation(const std::string &s);
|
|
|
|
void no_annotation() {
|
|
S s = getSNoAnnotation(std::string("temp"));
|
|
use(s);
|
|
}
|
|
|
|
void mix_annotated_and_not() {
|
|
S s1 = getS(std::string("temp")); // expected-warning {{local temporary object does not live long enough}} \
|
|
// expected-note {{destroyed here}}
|
|
S s2 = getSNoAnnotation(std::string("temp"));
|
|
use(s1); // expected-note {{later used here}}
|
|
use(s2);
|
|
}
|
|
|
|
S getS2(const std::string &a [[clang::lifetimebound]], const std::string &b [[clang::lifetimebound]]);
|
|
|
|
S multiple_lifetimebound_params() {
|
|
std::string str{"abc"};
|
|
S s = getS2(str, std::string("temp")); // expected-warning {{stack memory associated with local variable 'str' is returned}} \
|
|
// expected-warning {{local temporary object does not live long enough}} \
|
|
// expected-note {{destroyed here}}
|
|
return s; // expected-note {{returned here}} \
|
|
// expected-note {{later used here}}
|
|
}
|
|
|
|
// TODO: Diagnose [[clang::lifetimebound]] on functions whose return value
|
|
// cannot refer to any object (e.g., returning int or enum).
|
|
int getInt(const std::string &s [[clang::lifetimebound]]);
|
|
|
|
void primitive_return() {
|
|
int i = getInt(std::string("temp"));
|
|
use(i);
|
|
}
|
|
|
|
template <class T>
|
|
T make(const std::string &s [[clang::lifetimebound]]);
|
|
|
|
void from_template_instantiation() {
|
|
S s = make<S>(std::string("temp")); // expected-warning {{local temporary object does not live long enough}} \
|
|
// expected-note {{destroyed here}}
|
|
use(s); // expected-note {{later used here}}
|
|
}
|
|
|
|
struct FieldInitFromLifetimebound {
|
|
S value; // expected-note {{this field dangles}}
|
|
FieldInitFromLifetimebound() : value(getS(std::string("temp"))) {} // expected-warning {{stack memory associated with local temporary object escapes to the field 'value' which will dangle}}
|
|
};
|
|
|
|
S S::return_self_after_registration() const {
|
|
std::string s{"abc"};
|
|
getS(s);
|
|
return *this;
|
|
}
|
|
|
|
struct SWithUserDefinedCopyLikeOps {
|
|
SWithUserDefinedCopyLikeOps();
|
|
SWithUserDefinedCopyLikeOps(const std::string &s [[clang::lifetimebound]]) : owned(s), data(s) {}
|
|
|
|
SWithUserDefinedCopyLikeOps(const SWithUserDefinedCopyLikeOps &other) : owned("copy"), data(owned) {}
|
|
|
|
SWithUserDefinedCopyLikeOps &operator=(const SWithUserDefinedCopyLikeOps &) {
|
|
owned = "copy";
|
|
data = owned;
|
|
return *this;
|
|
}
|
|
|
|
std::string owned;
|
|
std::string_view data;
|
|
};
|
|
|
|
SWithUserDefinedCopyLikeOps getSWithUserDefinedCopyLikeOps(const std::string &s [[clang::lifetimebound]]);
|
|
|
|
SWithUserDefinedCopyLikeOps user_defined_copy_ctor_should_not_assume_origin_propagation() {
|
|
std::string str{"abc"};
|
|
SWithUserDefinedCopyLikeOps s = getSWithUserDefinedCopyLikeOps(str);
|
|
SWithUserDefinedCopyLikeOps copy = s; // Copy is rescued by user-defined copy constructor, so should not warn.
|
|
return copy;
|
|
}
|
|
|
|
void user_defined_assignment_should_not_assume_origin_propagation() {
|
|
SWithUserDefinedCopyLikeOps dst;
|
|
{
|
|
std::string str{"abc"};
|
|
SWithUserDefinedCopyLikeOps src = getSWithUserDefinedCopyLikeOps(str);
|
|
dst = src;
|
|
}
|
|
use(dst);
|
|
}
|
|
|
|
const S &getRef(const std::string &s [[clang::lifetimebound]]);
|
|
|
|
S from_ref() {
|
|
std::string str{"abc"};
|
|
S s = getRef(str);
|
|
return s;
|
|
}
|
|
|
|
using SAlias = S;
|
|
SAlias getSAlias(const std::string &s [[clang::lifetimebound]]);
|
|
|
|
void from_typedef_return() {
|
|
SAlias s = getSAlias(std::string("temp")); // expected-warning {{local temporary object does not live long enough}} \
|
|
// expected-note {{destroyed here}}
|
|
use(s); // expected-note {{later used here}}
|
|
}
|
|
|
|
struct SWithOriginPropagatingCopy {
|
|
SWithOriginPropagatingCopy();
|
|
SWithOriginPropagatingCopy(const std::string &s [[clang::lifetimebound]]) : data(s) {}
|
|
SWithOriginPropagatingCopy(const SWithOriginPropagatingCopy &other) : data(other.data) {}
|
|
std::string_view data;
|
|
};
|
|
|
|
SWithOriginPropagatingCopy getSWithOriginPropagatingCopy(const std::string &s [[clang::lifetimebound]]);
|
|
|
|
// FIXME: False negative. User-defined copy ctor may propagate origins.
|
|
SWithOriginPropagatingCopy user_defined_copy_with_origin_propagation() {
|
|
std::string str{"abc"};
|
|
SWithOriginPropagatingCopy s = getSWithOriginPropagatingCopy(str);
|
|
SWithOriginPropagatingCopy copy = s;
|
|
return copy; // Should warn.
|
|
}
|
|
|
|
struct DefaultedOuter {
|
|
DefaultedOuter();
|
|
DefaultedOuter(const std::string &s [[clang::lifetimebound]]) : inner(s) {}
|
|
SWithUserDefinedCopyLikeOps inner;
|
|
};
|
|
|
|
DefaultedOuter getDefaultedOuter(const std::string &s [[clang::lifetimebound]]);
|
|
|
|
// The defaulted outer copy ctor propagates origins even though the inner
|
|
// user-defined copy ctor may break the borrow. This is intentional: this
|
|
// pattern does not fit the ownership model this analysis supports.
|
|
DefaultedOuter nested_defaulted_outer_with_user_defined_inner() {
|
|
std::string str{"abc"};
|
|
DefaultedOuter o = getDefaultedOuter(str); // expected-warning {{stack memory associated with local variable 'str' is returned}}
|
|
DefaultedOuter copy = o;
|
|
return copy; // expected-note {{returned here}}
|
|
}
|
|
|
|
std::string_view getSV(S s [[clang::lifetimebound]]);
|
|
|
|
// FIXME: False negative. Non-pointer/ref/gsl::Pointer parameter types marked
|
|
// [[clang::lifetimebound]] are not registered for origin tracking.
|
|
void dangling_view_from_non_pointer_param() {
|
|
std::string_view sv;
|
|
{
|
|
S s;
|
|
sv = getSV(s);
|
|
}
|
|
use(sv); // Should warn.
|
|
}
|
|
|
|
MyObj getMyObj(const MyObj &obj [[clang::lifetimebound]]);
|
|
|
|
void gsl_owner_return() {
|
|
MyObj obj;
|
|
View v = obj;
|
|
getMyObj(obj);
|
|
use(v);
|
|
}
|
|
|
|
std::vector<std::string_view> createViews(const std::string &s [[clang::lifetimebound]]);
|
|
|
|
std::span<std::string_view> owner_to_pointer_via_gsl_construction() {
|
|
std::string local;
|
|
auto views = createViews(local);
|
|
return views; // expected-warning {{stack memory associated with local variable 'views' is returned}} \
|
|
// expected-note {{returned here}}
|
|
}
|
|
|
|
std::unique_ptr<S> getUniqueS(const std::string &s [[clang::lifetimebound]]);
|
|
|
|
void owner_return_unique_ptr_s() {
|
|
auto ptr = getUniqueS(std::string("temp")); // expected-warning {{local temporary object does not live long enough}} \
|
|
// expected-note {{destroyed here}}
|
|
(void)ptr; // expected-note {{later used here}}
|
|
}
|
|
|
|
std::string_view return_dangling_view_through_owner() {
|
|
std::string local;
|
|
auto ups = getUniqueS(local);
|
|
S* s = ups.get(); // expected-warning {{stack memory associated with local variable 'ups' is returned}}
|
|
std::string_view sv = s->getData();
|
|
return sv; // expected-note {{returned here}}
|
|
}
|
|
|
|
void owner_outlives_lifetimebound_source() {
|
|
std::unique_ptr<S> ups;
|
|
{
|
|
std::string local;
|
|
ups = getUniqueS(local); // expected-warning {{local variable 'local' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)ups; // expected-note {{later used here}}
|
|
}
|
|
|
|
} // namespace track_origins_for_lifetimebound_record_type
|
|
|
|
namespace gslpointer_construction_from_lifetimebound {
|
|
// https://github.com/llvm/llvm-project/issues/175898
|
|
struct Bar {};
|
|
template <typename T> struct [[gsl::Pointer]] Pointer {
|
|
Pointer();
|
|
Pointer(const T &bar [[clang::lifetimebound]]);
|
|
Pointer(const Pointer<T> &p);
|
|
const T &operator*() const [[clang::lifetimebound]];
|
|
};
|
|
|
|
template <typename T> void use(T);
|
|
|
|
void local_pointer() {
|
|
Pointer<int> p;
|
|
{
|
|
int v;
|
|
p = Pointer(v); // expected-warning {{local variable 'v' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
use(*p); // expected-note {{later used here}}
|
|
}
|
|
|
|
void nested_local_pointer() {
|
|
Pointer<Pointer<Pointer<Bar>>> ppp;
|
|
Pointer<Pointer<Bar>> pp;
|
|
Pointer<Bar> p;
|
|
{
|
|
Bar v;
|
|
p = Pointer(v); // expected-warning {{local variable 'v' does not live long enough}}
|
|
pp = Pointer(p);
|
|
ppp = Pointer(pp);
|
|
} // expected-note {{destroyed here}}
|
|
use(***ppp); // expected-note {{later used here}}
|
|
}
|
|
|
|
struct PFieldFromParam {
|
|
Pointer<Bar> value; // expected-note {{this field dangles}}
|
|
PFieldFromParam(Bar bar) : value(bar) {} // expected-warning {{stack memory associated with parameter 'bar' escapes to the field 'value' which will dangle}}
|
|
};
|
|
|
|
struct PFieldFromTemp {
|
|
Pointer<Bar> value; // expected-note {{this field dangles}}
|
|
PFieldFromTemp() : value(Bar{}) {} // expected-warning {{stack memory associated with local temporary object escapes to the field 'value' which will dangle}}
|
|
};
|
|
|
|
} // namespace gslpointer_construction_from_lifetimebound
|
|
namespace conditional_operator_control_flow {
|
|
// https://github.com/llvm/llvm-project/issues/183895
|
|
|
|
#ifdef __cpp_exceptions
|
|
|
|
void throw_branches(bool cond, int *value) {
|
|
(void)(cond ? throw 1 : value);
|
|
(void)(cond ? throw 1 : throw 2);
|
|
}
|
|
|
|
void nested_throw_branches(bool cond, bool cond2, int *value) {
|
|
(void)(cond ? (cond2 ? throw 1 : value) : throw 2);
|
|
(void)(cond ? throw 1 : (cond2 ? value : throw 2));
|
|
}
|
|
|
|
#endif
|
|
|
|
int *f(int *p [[clang::lifetimebound]]);
|
|
[[noreturn]] int *noret_f(int *p [[clang::lifetimebound]]);
|
|
|
|
|
|
constexpr bool kTrue = true;
|
|
constexpr bool kFalse = false;
|
|
|
|
int *constexpr_dead_false(int *num) {
|
|
int local = 0;
|
|
return kTrue ? num : f(&local);
|
|
}
|
|
|
|
int *constexpr_dead_nested(int *num) {
|
|
int local = 0;
|
|
return kTrue ? (kTrue ? num : f(&local)) : num;
|
|
}
|
|
|
|
int *constexpr_live_false(int *num) {
|
|
int local = 0;
|
|
return kFalse ? num : f(&local); // expected-warning {{stack memory associated with local variable 'local' is returned}} // expected-note {{returned here}}
|
|
}
|
|
|
|
int *constexpr_live_nested(int *num) {
|
|
int local = 0;
|
|
return kTrue ? (kFalse ? num : f(&local)) : num; // expected-warning {{stack memory associated with local variable 'local' is returned}} // expected-note {{returned here}}
|
|
}
|
|
int *noreturn_dead_false(bool cond, int *num) {
|
|
int local = 0;
|
|
return cond ? num : noret_f(&local);
|
|
}
|
|
|
|
int *noreturn_dead_nested(bool cond, bool cond2, int *num) {
|
|
int local = 0;
|
|
return cond ? (cond2 ? num : noret_f(&local)) : num;
|
|
}
|
|
|
|
} // namespace conditional_operator_control_flow
|
|
|
|
namespace new_allocation {
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// new
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void new_view_from_dead_scope() {
|
|
View *p;
|
|
{
|
|
MyObj obj;
|
|
p = new View(obj); // expected-warning {{local variable 'obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
p->use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void new_int_basic() {
|
|
int *p = new int; // expected-warning {{allocated object does not live long enough}}
|
|
delete p; // expected-note {{freed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void new_int_parens() {
|
|
int *p = new int(); // expected-warning {{allocated object does not live long enough}}
|
|
delete p; // expected-note {{freed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void new_int_braces() {
|
|
int *p = new int{}; // expected-warning {{allocated object does not live long enough}}
|
|
delete p; // expected-note {{freed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void conditional_delete(bool cond) {
|
|
int *p1 = new int; // expected-warning {{allocated object does not live long enough}}
|
|
int *p2 = new int; // expected-warning {{allocated object does not live long enough}}
|
|
delete (cond ? p1 : p2); // expected-note 2 {{freed here}}
|
|
(void)*p1; // expected-note {{later used here}}
|
|
(void)*p2; // expected-note {{later used here}}
|
|
}
|
|
|
|
int* foo(int* x [[clang::lifetimebound]], int* y [[clang::lifetimebound]]);
|
|
|
|
void delete_returned_from_call() {
|
|
int* x = new int(1); // expected-warning {{allocated object does not live long enough}}
|
|
int* y = new int(2); // expected-warning {{allocated object does not live long enough}}
|
|
delete foo(x, y); // expected-note 2 {{freed here}}
|
|
(void)x; // expected-note {{later used here}}
|
|
(void)y; // expected-note {{later used here}}
|
|
}
|
|
|
|
void new_pointer_from_pointer() {
|
|
MyObj **p;
|
|
{
|
|
MyObj obj;
|
|
MyObj *q = &obj; // expected-warning {{local variable 'obj' does not live long enough}}
|
|
p = new MyObj *(q);
|
|
} // expected-note {{destroyed here}}
|
|
(void)**p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void new_pointer_from_dead_object() {
|
|
MyObj **p;
|
|
{
|
|
MyObj obj;
|
|
p = new MyObj *(&obj); // expected-warning {{local variable 'obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)**p; // expected-note {{later used here}}
|
|
}
|
|
|
|
struct MultiView {
|
|
MultiView(MyObj& a [[clang::lifetimebound]], MyObj& b [[clang::lifetimebound]]);
|
|
};
|
|
|
|
void new_multiview_from_mixed_scope() {
|
|
MyObj obj1;
|
|
MultiView *p;
|
|
{
|
|
MyObj obj2;
|
|
p = new MultiView(obj1, obj2); // expected-warning {{local variable 'obj2' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void new_array_basic() {
|
|
int *p = new int[2]; // expected-warning {{allocated object does not live long enough}}
|
|
delete[] p; // expected-note {{freed here}}
|
|
(void)p[0]; // expected-note {{later used here}}
|
|
}
|
|
|
|
void new_array_parens() {
|
|
int *p = new int[2](); // expected-warning {{allocated object does not live long enough}}
|
|
delete[] p; // expected-note {{freed here}}
|
|
(void)p[0]; // expected-note {{later used here}}
|
|
}
|
|
|
|
void new_array_braces() {
|
|
int *p = new int[2]{}; // expected-warning {{allocated object does not live long enough}}
|
|
delete[] p; // expected-note {{freed here}}
|
|
(void)p[0]; // expected-note {{later used here}}
|
|
}
|
|
|
|
// FIXME: https://github.com/llvm/llvm-project/issues/187471
|
|
void new_pointer_array_from_dead_objects() {
|
|
MyObj **arr;
|
|
{
|
|
MyObj a, b;
|
|
arr = new MyObj *[2]{&a, &b};
|
|
}
|
|
(void)arr[0]->id;
|
|
(void)arr[1]->id;
|
|
}
|
|
|
|
struct PointerArrayFieldHolder {
|
|
MyObj **Ptrs;
|
|
};
|
|
|
|
// FIXME: https://github.com/llvm/llvm-project/issues/187471
|
|
void pointer_array_field_sensitivity() {
|
|
PointerArrayFieldHolder h;
|
|
{
|
|
MyObj a, b;
|
|
h.Ptrs = new MyObj *[2]{&a, &b};
|
|
}
|
|
(void)h.Ptrs[0]->id;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// delete
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void delete_direct_use_after_free() {
|
|
MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
|
|
delete p; // expected-note {{freed here}}
|
|
(void)p->id; // expected-note {{later used here}}
|
|
}
|
|
|
|
void delete_alias_use_after_free() {
|
|
MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
|
|
MyObj *q = p;
|
|
delete p; // expected-note {{freed here}}
|
|
(void)q->id; // expected-note {{later used here}}
|
|
}
|
|
|
|
void delete_pointer_propagation_use_after_free() {
|
|
MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
|
|
MyObj **pp = &p;
|
|
delete p; // expected-note {{freed here}}
|
|
(void)(*pp)->id; // expected-note {{later used here}}
|
|
}
|
|
|
|
void delete_param_pointer(int* x) { // expected-warning {{parameter does not live long enough}}
|
|
delete x; // expected-note {{freed here}}
|
|
(void)x; // expected-note {{later used here}}
|
|
}
|
|
|
|
// FIXME: false-negative
|
|
struct S {
|
|
int *x;
|
|
void foo() {
|
|
delete x;
|
|
(void)x;
|
|
}
|
|
};
|
|
|
|
void use_inner_origin_after_delete(MyObj* obj) { // expected-warning {{parameter does not live long enough}}
|
|
int* p = &obj->id;
|
|
delete obj; // expected-note {{freed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void delete_nullptr_no_warning() {
|
|
int *p = nullptr;
|
|
delete p;
|
|
}
|
|
|
|
void delete_array_nullptr_no_warning() {
|
|
int *p = nullptr;
|
|
delete[] p;
|
|
}
|
|
|
|
struct ClassSpecificDelete {
|
|
int X;
|
|
static void operator delete(void *);
|
|
};
|
|
|
|
void class_specific_operator_delete_use_after_free() {
|
|
ClassSpecificDelete *p = new ClassSpecificDelete; // expected-warning {{allocated object does not live long enough}}
|
|
delete p; // expected-note {{freed here}}
|
|
(void)p->X; // expected-note {{later used here}}
|
|
}
|
|
|
|
struct PointerFieldHolder {
|
|
MyObj *Ptr;
|
|
};
|
|
|
|
// FIXME: https://github.com/llvm/llvm-project/issues/184344
|
|
void delete_through_pointer_field() {
|
|
PointerFieldHolder h{new MyObj};
|
|
delete h.Ptr;
|
|
(void)h.Ptr->id;
|
|
}
|
|
|
|
void delete_stack_object() {
|
|
MyObj obj;
|
|
MyObj* p = &obj; // expected-warning {{allocated object does not live long enough}}
|
|
delete &obj; // expected-note {{freed here}}
|
|
(void)p->id; // expected-note {{later used here}}
|
|
}
|
|
|
|
void delete_stack_object_int() {
|
|
int obj;
|
|
int* p = &obj; // expected-warning {{allocated object does not live long enough}}
|
|
delete &obj; // expected-note {{freed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void allocate_void_ptr() {
|
|
void** v = new void*;
|
|
delete v;
|
|
}
|
|
|
|
} // namespace new_allocation
|
|
|
|
namespace placement_new {
|
|
|
|
void placement_new_int_basic() {
|
|
int *p;
|
|
{
|
|
int storage;
|
|
p = new (&storage) int; // expected-warning {{local variable 'storage' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void placement_new_view_from_dead_scope() {
|
|
View storage;
|
|
View *p = &storage;
|
|
{
|
|
MyObj obj;
|
|
p = new (&storage) View(obj); // expected-warning {{local variable 'obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
p->use(); // expected-note {{later used here}}
|
|
}
|
|
|
|
void placement_new_pointer_from_dead_object() {
|
|
MyObj *slot = nullptr;
|
|
MyObj **p = &slot;
|
|
{
|
|
MyObj obj;
|
|
p = new (&slot) MyObj *(&obj); // expected-warning {{local variable 'obj' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)**p; // expected-note {{later used here}}
|
|
}
|
|
|
|
void placement_new_array_basic() {
|
|
int *p;
|
|
{
|
|
int storage[2];
|
|
p = new (&storage) int[2]; // expected-warning {{local variable 'storage' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)p[0]; // expected-note {{later used here}}
|
|
}
|
|
|
|
void placement_new_array_braces() {
|
|
int *p;
|
|
{
|
|
int storage[2];
|
|
p = new (&storage) int[2]{}; // expected-warning {{local variable 'storage' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)p[0]; // expected-note {{later used here}}
|
|
}
|
|
|
|
void placement_new_heap_then_delete_use_after_free() {
|
|
int *storage = new int(7); // expected-warning {{allocated object does not live long enough}}
|
|
int *p = new (storage) int(42);
|
|
delete storage; // expected-note {{freed here}}
|
|
(void)*p; // expected-note {{later used here}}
|
|
}
|
|
|
|
struct PlacementArg {};
|
|
|
|
struct VariadicPlacementNew {
|
|
void *operator new(std::size_t, ...);
|
|
};
|
|
|
|
void variadic_placement_new() {
|
|
PlacementArg arg;
|
|
(void)new (arg) VariadicPlacementNew;
|
|
}
|
|
|
|
int* foo(int* x [[clang::lifetimebound]], int* y [[clang::lifetimebound]]);
|
|
|
|
void placement_new_delete_result_of_lifetimebound_call() {
|
|
int *x = new int(1); // expected-warning {{allocated object does not live long enough}}
|
|
int *y = new int(2); // expected-warning {{allocated object does not live long enough}}
|
|
int *slot = nullptr;
|
|
int **p = new (&slot) int *(foo(x, y));
|
|
delete foo(x, y); // expected-note 2 {{freed here}}
|
|
(void)**p; // expected-note 2 {{later used here}}
|
|
}
|
|
|
|
|
|
struct PointerFieldHolder {
|
|
MyObj *Ptr;
|
|
};
|
|
|
|
// FIXME: https://github.com/llvm/llvm-project/issues/184344
|
|
void placement_new_pointer_field_use_after_scope() {
|
|
PointerFieldHolder h;
|
|
PointerFieldHolder *p = &h;
|
|
{
|
|
MyObj obj;
|
|
p = new (&h) PointerFieldHolder{&obj};
|
|
}
|
|
(void)p->Ptr->id;
|
|
}
|
|
} // namespace placement_new
|
|
|
|
// FIXME: Currently this is not diagnosed because placement new does not overwrite the placement argument's origins.
|
|
namespace placement_new_argument {
|
|
struct PlacementNewInMethod {
|
|
View V;
|
|
|
|
void bad_store_after_placement_new() {
|
|
{
|
|
MyObj obj;
|
|
new (&V) View(obj);
|
|
}
|
|
V.use();
|
|
}
|
|
};
|
|
|
|
void placement_new_member_call_from_dead_scope() {
|
|
View *storage = new View;
|
|
{
|
|
MyObj obj;
|
|
new (storage) View(obj);
|
|
}
|
|
storage->use();
|
|
}
|
|
|
|
struct ViewPointerFieldHolder {
|
|
View *Ptr;
|
|
};
|
|
|
|
void placement_new_pointer_field_from_dead_scope() {
|
|
ViewPointerFieldHolder h{new View};
|
|
{
|
|
MyObj obj;
|
|
new (h.Ptr) View(obj);
|
|
}
|
|
h.Ptr->use();
|
|
}
|
|
|
|
void placement_new_array_subscript_from_dead_scope() {
|
|
View *slots[1] = {new View};
|
|
{
|
|
MyObj obj;
|
|
new (slots[0]) View(obj);
|
|
}
|
|
slots[0]->use();
|
|
}
|
|
|
|
void placement_new_pointer_reference_from_dead_scope() {
|
|
View *storage = new View;
|
|
View *&ref = storage;
|
|
{
|
|
MyObj obj;
|
|
new (ref) View(obj);
|
|
}
|
|
storage->use();
|
|
}
|
|
|
|
void placement_new_conditional_pointer_from_dead_scope(bool flag) {
|
|
View *x = new View;
|
|
View *y = new View;
|
|
{
|
|
MyObj obj;
|
|
new (flag ? x : y) View(obj);
|
|
}
|
|
x->use();
|
|
y->use();
|
|
}
|
|
|
|
View *identity(View *p [[clang::lifetimebound]]);
|
|
|
|
void placement_new_function_pointer_from_dead_scope() {
|
|
View *storage = new View;
|
|
{
|
|
MyObj obj;
|
|
new (identity(storage)) View(obj);
|
|
}
|
|
storage->use();
|
|
}
|
|
|
|
struct ViewStorage {
|
|
View Storage;
|
|
View *get() [[clang::lifetimebound]];
|
|
};
|
|
|
|
void placement_new_member_function_pointer_from_dead_scope() {
|
|
ViewStorage storage;
|
|
{
|
|
MyObj obj;
|
|
new (storage.get()) View(obj);
|
|
}
|
|
storage.Storage.use();
|
|
}
|
|
|
|
void placement_new_addressof_from_dead_scope() {
|
|
View storage;
|
|
{
|
|
MyObj obj;
|
|
new (&storage) View(obj);
|
|
}
|
|
storage.use();
|
|
}
|
|
|
|
void placement_new_explicit_void_cast_from_dead_scope() {
|
|
View *storage = new View;
|
|
{
|
|
MyObj obj;
|
|
new ((void *)storage) View(obj);
|
|
}
|
|
storage->use();
|
|
}
|
|
|
|
void placement_new_direct_array_use_after_placement() {
|
|
char storage[sizeof(std::string)];
|
|
std::string* str1 = new (storage) std::string{"Old"};
|
|
auto p1 = str1->c_str();
|
|
new (storage) std::string{"New"};
|
|
(void)*p1;
|
|
}
|
|
|
|
} // namespace placement_new_argument
|
|
|
|
namespace method_call_uses_field_origins {
|
|
int GLOBAL_INT;
|
|
std::string GLOBAL_STRING{"123"};
|
|
|
|
struct S {
|
|
int* p_;
|
|
void bar();
|
|
void foo() {
|
|
{
|
|
int num;
|
|
this->p_ = # // expected-warning {{local variable 'num' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
bar(); // expected-note {{later used here}}
|
|
this->p_ = &GLOBAL_INT;
|
|
}
|
|
void baz() {
|
|
{
|
|
int num;
|
|
this->p_ = #
|
|
}
|
|
this->p_ = &GLOBAL_INT;
|
|
bar();
|
|
}
|
|
};
|
|
|
|
struct T {
|
|
std::string_view v;
|
|
void bar();
|
|
void foo() {
|
|
v = std::string("tmp"); // expected-warning {{local temporary object does not live long enough}} expected-note {{destroyed here}}
|
|
bar(); // expected-note {{later used here}}
|
|
}
|
|
};
|
|
|
|
// FIXME: false-negative
|
|
void foo() {
|
|
S s;
|
|
{
|
|
int num;
|
|
s.p_ = # // does not warn
|
|
}
|
|
s.bar();
|
|
s.p_ = &GLOBAL_INT;
|
|
}
|
|
|
|
struct S2 : S {
|
|
void bar2();
|
|
void foo2() {
|
|
{
|
|
int num;
|
|
this->p_ = # // expected-warning {{local variable 'num' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
bar(); // expected-note {{later used here}}
|
|
this->p_ = &GLOBAL_INT;
|
|
}
|
|
void baz2() {
|
|
{
|
|
int num;
|
|
this->p_ = # // expected-warning {{local variable 'num' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
bar2(); // expected-note {{later used here}}
|
|
this->p_ = nullptr;
|
|
}
|
|
};
|
|
|
|
} // namespace method_call_uses_field_origins
|
|
|
|
namespace CXXDefaultInitExprTests {
|
|
struct Holder {
|
|
std::string_view view = std::string("temporary"); // expected-warning {{stack memory associated with local temporary object escapes to the field 'view' which will dangle}} expected-note {{this field dangles}}
|
|
Holder() {}
|
|
};
|
|
} // namespace CXXDefaultInitExprTests
|
|
|
|
namespace base_class_fields {
|
|
struct X { int* x; }; // expected-note {{this field dangles}}
|
|
struct Y : X {
|
|
int* y;
|
|
void bar() {
|
|
{
|
|
int a;
|
|
x = &a; // expected-warning {{stack memory associated with local variable 'a' escapes to the field 'x' which will dangle}}
|
|
}
|
|
(void)x;
|
|
}
|
|
};
|
|
} // namespace base_class_fields
|
|
|
|
namespace callable_wrappers {
|
|
|
|
std::function<void()> direct_return() {
|
|
int x;
|
|
return [&x]() { (void)x; }; // expected-warning {{stack memory associated with local variable 'x' is returned}} \
|
|
// expected-note {{returned here}}
|
|
}
|
|
|
|
std::function<void()> copy_function() {
|
|
int x;
|
|
std::function<void()> f = [&x]() { (void)x; }; // expected-warning {{stack memory associated with local variable 'x' is returned}}
|
|
std::function<void()> f2 = f;
|
|
return f2; // expected-note {{returned here}}
|
|
}
|
|
|
|
std::function<void()> copy_assign() {
|
|
int x;
|
|
std::function<void()> f = [&x]() { (void)x; }; // expected-warning {{stack memory associated with local variable 'x' is returned}}
|
|
std::function<void()> f2 = []() {};
|
|
f2 = f;
|
|
return f2; // expected-note {{returned here}}
|
|
}
|
|
|
|
std::function<void()> chained_copy_assign() {
|
|
int x;
|
|
std::function<void()> f = [&x]() { (void)x; }; // expected-warning {{stack memory associated with local variable 'x' is returned}}
|
|
std::function<void()> f2 = []() {};
|
|
std::function<void()> f3 = []() {};
|
|
f3 = f2 = f;
|
|
return f3; // expected-note {{returned here}}
|
|
}
|
|
|
|
std::function<void()> move_assign() {
|
|
int x;
|
|
std::function<void()> f = [&x]() { (void)x; }; // expected-warning {{stack memory associated with local variable 'x' is returned}}
|
|
std::function<void()> f2 = []() {};
|
|
f2 = std::move(f);
|
|
return f2; // expected-note {{returned here}}
|
|
}
|
|
|
|
std::function<void()> reassign_safe_then_unsafe() {
|
|
static int safe = 1;
|
|
int local = 2;
|
|
std::function<void()> f = []() { (void)safe; };
|
|
f = [&local]() { (void)local; }; // expected-warning {{stack memory associated with local variable 'local' is returned}}
|
|
return f; // expected-note {{returned here}}
|
|
}
|
|
|
|
std::function<void()> reassign_unsafe_then_safe() {
|
|
static int safe = 1;
|
|
int local = 2;
|
|
std::function<void()> f = [&local]() { (void)local; };
|
|
f = []() { (void)safe; };
|
|
return f;
|
|
}
|
|
|
|
std::function<void()> non_capturing_lambda() {
|
|
return []() {};
|
|
}
|
|
|
|
void free_function();
|
|
|
|
std::function<void()> reassign_lambda_to_function_pointer() {
|
|
int local;
|
|
std::function<void()> f = [&local]() { (void)local; };
|
|
f = &free_function;
|
|
return f;
|
|
}
|
|
|
|
struct Functor { void operator()() const; };
|
|
|
|
std::function<void()> reassign_lambda_to_functor() {
|
|
int local;
|
|
Functor c;
|
|
std::function<void()> f = [&local]() { (void)local; };
|
|
f = c;
|
|
return f;
|
|
}
|
|
|
|
std::function<void()> capture_lifetimebound_param(int &x [[clang::lifetimebound]]) {
|
|
return [&]() { (void)x; };
|
|
}
|
|
|
|
void uaf_via_lifetimebound() {
|
|
std::function<void()> f = []() {};
|
|
{
|
|
int local;
|
|
f = capture_lifetimebound_param(local); // expected-warning {{local variable 'local' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)f; // expected-note {{later used here}}
|
|
}
|
|
|
|
} // namespace callable_wrappers
|
|
|
|
namespace GH126600 {
|
|
struct [[gsl::Pointer]] function_ref {
|
|
template <typename Callable>
|
|
function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) {}
|
|
void (*ref)();
|
|
};
|
|
|
|
// FIXME: The lifetimebound annotation tracks the outer callable object's
|
|
// storage rather than what the callable captures. A mechanism like
|
|
// lifetimebound(2) could enable tracking inner lifetimes, which would
|
|
// avoid this warning for non-capturing lambdas.
|
|
void assign_non_capturing_to_function_ref(function_ref &r) {
|
|
r = []() {}; // expected-warning {{local temporary object does not live long enough}} \
|
|
// expected-note {{destroyed here}}
|
|
(void)r; // expected-note {{later used here}}
|
|
}
|
|
|
|
} // namespace GH126600
|
|
|
|
namespace GH188832 {
|
|
inline namespace __1 {
|
|
template <class T>
|
|
struct __optional_storage_base {
|
|
T& operator*() &;
|
|
T* operator->();
|
|
T& value() &;
|
|
};
|
|
|
|
template <class T>
|
|
struct [[gsl::Owner]] optional : __optional_storage_base<T> {
|
|
~optional();
|
|
};
|
|
} // namespace __1
|
|
|
|
const MyObj& return_optional_deref() {
|
|
optional<MyObj> opt;
|
|
return *opt; // expected-warning {{stack memory associated with local variable 'opt' is returned}} \
|
|
// expected-note {{returned here}}
|
|
}
|
|
|
|
const MyObj& return_optional_value() {
|
|
optional<MyObj> opt;
|
|
return opt.value(); // expected-warning {{stack memory associated with local variable 'opt' is returned}} \
|
|
// expected-note {{returned here}}
|
|
}
|
|
|
|
const int* return_optional_arrow() {
|
|
optional<MyObj> opt;
|
|
return &opt->id; // expected-warning {{stack memory associated with local variable 'opt' is returned}} \
|
|
// expected-note {{returned here}}
|
|
}
|
|
|
|
void deref_use_after_scope() {
|
|
const MyObj* p;
|
|
{
|
|
optional<MyObj> opt;
|
|
p = &*opt; // expected-warning {{local variable 'opt' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)p->id; // expected-note {{later used here}}
|
|
}
|
|
|
|
} // namespace GH188832
|
|
|
|
namespace GH191954 {
|
|
int* return_moved_pointer() {
|
|
int x;
|
|
int* f = &x; // expected-warning {{stack memory associated with local variable 'x' is returned}}
|
|
int* a;
|
|
a = std::move(f);
|
|
return a; // expected-note {{returned here}}
|
|
}
|
|
|
|
int* return_moved_pointer2() {
|
|
int x;
|
|
int* f = &x; // expected-warning {{stack memory associated with local variable 'x' is returned}}
|
|
return std::move(f); // expected-note {{returned here}}
|
|
}
|
|
|
|
View return_moved_view() {
|
|
MyObj o;
|
|
View v(o); // expected-warning {{stack memory associated with local variable 'o' is returned}}
|
|
View v2 = std::move(v);
|
|
return v2; // expected-note {{returned here}}
|
|
}
|
|
|
|
int* return_forwarded_pointer() {
|
|
int x;
|
|
int* f = &x; // expected-warning {{stack memory associated with local variable 'x' is returned}}
|
|
return std::forward<int*>(f); // expected-note {{returned here}}
|
|
}
|
|
|
|
int g;
|
|
int* return_moved_pointer_to_global() {
|
|
int* f = &g;
|
|
return std::move(f);
|
|
}
|
|
} // namespace GH191954
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// lifetime-capture-by
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void setCaptureBy(View& res, View in [[clang::lifetime_capture_by(res)]]);
|
|
|
|
void use_after_free_capture_by() {
|
|
View res;
|
|
{
|
|
MyObj a;
|
|
setCaptureBy(res, a); // expected-warning {{local variable 'a' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)res; // expected-note {{later used here}}
|
|
}
|
|
|
|
View use_after_return_capture_by() {
|
|
MyObj a;
|
|
View res;
|
|
setCaptureBy(res, a); // expected-warning {{stack memory associated with local variable 'a' is returned}}
|
|
return res; // expected-note {{returned here}}
|
|
|
|
}
|
|
|
|
void transitive_capture() {
|
|
View v1, v2;
|
|
{
|
|
MyObj local;
|
|
setCaptureBy(v1, local); // expected-warning {{local variable 'local' does not live long enough}}
|
|
setCaptureBy(v2, v1);
|
|
} // expected-note {{destroyed here}}
|
|
(void)v2; // expected-note {{later used here}}
|
|
}
|
|
|
|
void set1(View& res, const MyObj& in [[clang::lifetime_capture_by(res)]]);
|
|
|
|
void test_reference_to_view() {
|
|
View v;
|
|
{
|
|
MyObj local;
|
|
set1(v, local); // expected-warning {{local variable 'local' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)v; // expected-note {{later used here}}
|
|
}
|
|
|
|
// FIXME: Add special handling for multi-level pointers and lvalue expressions which are not DeclRefExpr.
|
|
void set2(MyObj** res, const MyObj& in [[clang::lifetime_capture_by(res)]]);
|
|
|
|
void test_pointer_to_pointer() {
|
|
MyObj *ptr = nullptr;
|
|
{
|
|
MyObj local;
|
|
set2(&ptr, local);
|
|
}
|
|
(void)ptr;
|
|
}
|
|
|
|
void test_pointer_to_pointer_2(MyObj **ptr) {
|
|
{
|
|
MyObj local;
|
|
set2(ptr, local);
|
|
}
|
|
(void)ptr;
|
|
}
|
|
|
|
void set3(MyObj*& res, const MyObj& in [[clang::lifetime_capture_by(res)]]);
|
|
|
|
void test_reference_to_pointer() {
|
|
MyObj *ptr = nullptr;
|
|
{
|
|
MyObj local;
|
|
set3(ptr, local); // expected-warning {{local variable 'local' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)ptr; // expected-note {{later used here}}
|
|
}
|
|
|
|
struct [[gsl::Pointer]] MyContainer {
|
|
View stored;
|
|
void set(View s [[clang::lifetime_capture_by(this)]]);
|
|
};
|
|
|
|
void member_capture() {
|
|
MyContainer c;
|
|
{
|
|
MyObj local;
|
|
c.set(local); // expected-warning {{local variable 'local' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)c.stored; // expected-note {{later used here}}
|
|
}
|
|
|
|
// FIXME: Add support for simple containers without annotations.
|
|
struct SimpleContainer {
|
|
View stored;
|
|
void set(View s [[clang::lifetime_capture_by(this)]]);
|
|
};
|
|
|
|
void member_capture_simple_container() {
|
|
SimpleContainer c;
|
|
{
|
|
MyObj local;
|
|
c.set(local);
|
|
}
|
|
(void)c.stored;
|
|
}
|
|
|
|
void captureTwo(View& into,
|
|
View a [[clang::lifetime_capture_by(into)]],
|
|
View b [[clang::lifetime_capture_by(into)]]);
|
|
|
|
void multiple_captures() {
|
|
View res;
|
|
MyObj val1;
|
|
{
|
|
MyObj val2;
|
|
captureTwo(res, val1, val2); // expected-warning {{local variable 'val2' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)res; // expected-note {{later used here}}
|
|
}
|
|
|
|
void multiple_local_captures() {
|
|
View res;
|
|
{
|
|
MyObj val1;
|
|
MyObj val2;
|
|
captureTwo(res, val1, val2); // expected-warning {{local variable 'val1' does not live long enough}} // expected-warning {{local variable 'val2' does not live long enough}}
|
|
} // expected-note 2 {{destroyed here}}
|
|
(void)res; // expected-note 2 {{later used here}}
|
|
}
|
|
|
|
void captureIntoTwo(View& v1, View& v2,
|
|
View s [[clang::lifetime_capture_by(v1, v2)]]);
|
|
|
|
void captured_by_multiple_params() {
|
|
View v1, v2;
|
|
{
|
|
MyObj local;
|
|
captureIntoTwo(v1, v2, local); // expected-warning {{local variable 'local' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)v1; // expected-note {{later used here}}
|
|
}
|
|
|
|
void captured_by_multiple_params_2() {
|
|
View v1, v2;
|
|
{
|
|
MyObj local;
|
|
captureIntoTwo(v1, v2, local); // expected-warning {{local variable 'local' does not live long enough}}
|
|
} // expected-note {{destroyed here}}
|
|
(void)v2; // expected-note {{later used here}}
|
|
}
|
|
|
|
void capturing_multiple_locals() {
|
|
View v;
|
|
{
|
|
MyObj local1;
|
|
setCaptureBy(v, local1); // expected-warning{{local variable 'local1' does not live long enough}}
|
|
MyObj local2;
|
|
setCaptureBy(v, local2); // expected-warning{{local variable 'local2' does not live long enough}}
|
|
} // expected-note 2 {{destroyed here}}
|
|
(void)v; // expected-note 2 {{later used here}}
|
|
} |