mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-12-13 19:24:21 +00:00
4ff836a138
In case when the prvalue is returned from the function (kind is one of `SimpleReturnedValueKind`, `CXX17ElidedCopyReturnedValueKind`), then it construction happens in context of the caller. We pass `BldrCtx` explicitly, as `currBldrCtx` will always refer to callee context. In the following example: ``` struct Result {int value; }; Result create() { return Result{10}; } int accessValue(Result r) { return r.value; } void test() { for (int i = 0; i < 2; ++i) accessValue(create()); } ``` In case when the returned object was constructed directly into the argument to a function call `accessValue(create())`, this led to inappropriate value of `blockCount` being used to locate parameter region, and as a consequence resulting object (from `create()`) was constructed into a different region, that was later read by inlined invocation of outer function (`accessValue`). This manifests itself only in case when calling block is visited more than once (loop in above example), as otherwise there is no difference in `blockCount` value between callee and caller context. This happens only in case when copy elision is disabled (before C++17). Reviewed By: NoQ Differential Revision: https://reviews.llvm.org/D132030
450 lines
13 KiB
C++
450 lines
13 KiB
C++
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++11 \
|
|
// RUN: -analyzer-config eagerly-assume=false -verify %s
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 \
|
|
// RUN: -analyzer-config eagerly-assume=false -verify %s
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++11 \
|
|
// RUN: -analyzer-config elide-constructors=false -DNO_ELIDE_FLAG \
|
|
// RUN: -analyzer-config eagerly-assume=false -verify=expected,no-elide %s
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 \
|
|
// RUN: -analyzer-config elide-constructors=false \
|
|
// RUN: -analyzer-config eagerly-assume=false -verify %s
|
|
|
|
// Copy elision always occurs in C++17, otherwise it's under
|
|
// an on-by-default flag.
|
|
#if __cplusplus >= 201703L
|
|
#define ELIDE 1
|
|
#else
|
|
#ifndef NO_ELIDE_FLAG
|
|
#define ELIDE 1
|
|
#endif
|
|
#endif
|
|
|
|
void clang_analyzer_eval(bool);
|
|
void clang_analyzer_dump(int);
|
|
|
|
namespace variable_functional_cast_crash {
|
|
|
|
struct A {
|
|
A(int) {}
|
|
};
|
|
|
|
void foo() {
|
|
A a = A(0);
|
|
}
|
|
|
|
struct B {
|
|
A a;
|
|
B(): a(A(0)) {}
|
|
};
|
|
|
|
} // namespace variable_functional_cast_crash
|
|
|
|
|
|
namespace ctor_initializer {
|
|
|
|
struct S {
|
|
int x, y, z;
|
|
};
|
|
|
|
struct T {
|
|
S s;
|
|
int w;
|
|
T(int w): s(), w(w) {}
|
|
};
|
|
|
|
class C {
|
|
T t;
|
|
public:
|
|
C() : t(T(4)) {
|
|
S s = {1, 2, 3};
|
|
t.s = s;
|
|
// FIXME: Should be TRUE regardless of copy elision.
|
|
clang_analyzer_eval(t.w == 4);
|
|
#ifdef ELIDE
|
|
// expected-warning@-2{{TRUE}}
|
|
#else
|
|
// expected-warning@-4{{UNKNOWN}}
|
|
#endif
|
|
}
|
|
};
|
|
|
|
|
|
struct A {
|
|
int x;
|
|
A(): x(0) {}
|
|
~A() {}
|
|
};
|
|
|
|
struct B {
|
|
A a;
|
|
B() : a(A()) {}
|
|
};
|
|
|
|
void foo() {
|
|
B b;
|
|
clang_analyzer_eval(b.a.x == 0); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
} // namespace ctor_initializer
|
|
|
|
|
|
namespace elision_on_ternary_op_branches {
|
|
class C1 {
|
|
int x;
|
|
public:
|
|
C1(int x): x(x) {}
|
|
int getX() const { return x; }
|
|
~C1();
|
|
};
|
|
|
|
class C2 {
|
|
int x;
|
|
int y;
|
|
public:
|
|
C2(int x, int y): x(x), y(y) {}
|
|
int getX() const { return x; }
|
|
int getY() const { return y; }
|
|
~C2();
|
|
};
|
|
|
|
void foo(int coin) {
|
|
C1 c1 = coin ? C1(1) : C1(2);
|
|
if (coin) {
|
|
clang_analyzer_eval(c1.getX() == 1); // expected-warning{{TRUE}}
|
|
} else {
|
|
clang_analyzer_eval(c1.getX() == 2); // expected-warning{{TRUE}}
|
|
}
|
|
C2 c2 = coin ? C2(3, 4) : C2(5, 6);
|
|
if (coin) {
|
|
clang_analyzer_eval(c2.getX() == 3); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(c2.getY() == 4); // expected-warning{{TRUE}}
|
|
} else {
|
|
clang_analyzer_eval(c2.getX() == 5); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(c2.getY() == 6); // expected-warning{{TRUE}}
|
|
}
|
|
}
|
|
} // namespace elision_on_ternary_op_branches
|
|
|
|
|
|
namespace address_vector_tests {
|
|
|
|
template <typename T> struct AddressVector {
|
|
T *buf[20];
|
|
int len;
|
|
|
|
AddressVector() : len(0) {}
|
|
|
|
void push(T *t) {
|
|
buf[len] = t;
|
|
++len;
|
|
}
|
|
};
|
|
|
|
class ClassWithoutDestructor {
|
|
AddressVector<ClassWithoutDestructor> &v;
|
|
|
|
public:
|
|
ClassWithoutDestructor(AddressVector<ClassWithoutDestructor> &v) : v(v) {
|
|
push();
|
|
}
|
|
|
|
ClassWithoutDestructor(ClassWithoutDestructor &&c) : v(c.v) { push(); }
|
|
ClassWithoutDestructor(const ClassWithoutDestructor &c) : v(c.v) { push(); }
|
|
|
|
void push() { v.push(this); }
|
|
};
|
|
|
|
ClassWithoutDestructor make1(AddressVector<ClassWithoutDestructor> &v) {
|
|
return ClassWithoutDestructor(v);
|
|
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
|
|
object of type 'ClassWithoutDestructor' is still \
|
|
referred to by the stack variable 'v' upon returning to the caller}}
|
|
}
|
|
ClassWithoutDestructor make2(AddressVector<ClassWithoutDestructor> &v) {
|
|
return make1(v);
|
|
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
|
|
object of type 'ClassWithoutDestructor' is still \
|
|
referred to by the stack variable 'v' upon returning to the caller}}
|
|
}
|
|
ClassWithoutDestructor make3(AddressVector<ClassWithoutDestructor> &v) {
|
|
return make2(v);
|
|
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
|
|
object of type 'ClassWithoutDestructor' is still \
|
|
referred to by the stack variable 'v' upon returning to the caller}}
|
|
}
|
|
|
|
void testMultipleReturns() {
|
|
AddressVector<ClassWithoutDestructor> v;
|
|
ClassWithoutDestructor c = make3(v);
|
|
|
|
#if ELIDE
|
|
clang_analyzer_eval(v.len == 1); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[0] == &c); // expected-warning{{TRUE}}
|
|
#else
|
|
clang_analyzer_eval(v.len == 5); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[0] != v.buf[1]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[1] != v.buf[2]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[2] != v.buf[3]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[3] != v.buf[4]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[4] == &c); // expected-warning{{TRUE}}
|
|
#endif
|
|
}
|
|
|
|
void consume(ClassWithoutDestructor c) {
|
|
c.push();
|
|
// expected-warning@-1 {{Address of stack memory associated with local \
|
|
variable 'c' is still referred to by the stack variable 'v' upon returning \
|
|
to the caller}}
|
|
}
|
|
|
|
void testArgumentConstructorWithoutDestructor() {
|
|
AddressVector<ClassWithoutDestructor> v;
|
|
|
|
consume(make3(v));
|
|
|
|
#if ELIDE
|
|
clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}}
|
|
#else
|
|
clang_analyzer_eval(v.len == 6); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[0] != v.buf[1]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[1] != v.buf[2]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[2] != v.buf[3]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[3] != v.buf[4]); // expected-warning{{TRUE}}
|
|
// We forced a push() in consume(), let's see if the address here matches
|
|
// the address during construction.
|
|
clang_analyzer_eval(v.buf[4] == v.buf[5]); // expected-warning{{TRUE}}
|
|
#endif
|
|
}
|
|
|
|
class ClassWithDestructor {
|
|
AddressVector<ClassWithDestructor> &v;
|
|
|
|
public:
|
|
ClassWithDestructor(AddressVector<ClassWithDestructor> &v) : v(v) {
|
|
push();
|
|
}
|
|
|
|
ClassWithDestructor(ClassWithDestructor &&c) : v(c.v) { push(); }
|
|
ClassWithDestructor(const ClassWithDestructor &c) : v(c.v) { push(); }
|
|
|
|
~ClassWithDestructor() { push(); }
|
|
|
|
void push() { v.push(this); }
|
|
};
|
|
|
|
void testVariable() {
|
|
AddressVector<ClassWithDestructor> v;
|
|
{
|
|
ClassWithDestructor c = ClassWithDestructor(v);
|
|
// Check if the last destructor is an automatic destructor.
|
|
// A temporary destructor would have fired by now.
|
|
#if ELIDE
|
|
clang_analyzer_eval(v.len == 1); // expected-warning{{TRUE}}
|
|
#else
|
|
clang_analyzer_eval(v.len == 3); // expected-warning{{TRUE}}
|
|
#endif
|
|
}
|
|
#if ELIDE
|
|
// 0. Construct the variable.
|
|
// 1. Destroy the variable.
|
|
clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}}
|
|
#else
|
|
// 0. Construct the temporary.
|
|
// 1. Construct the variable.
|
|
// 2. Destroy the temporary.
|
|
// 3. Destroy the variable.
|
|
clang_analyzer_eval(v.len == 4); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[0] == v.buf[2]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[1] == v.buf[3]); // expected-warning{{TRUE}}
|
|
#endif
|
|
}
|
|
|
|
struct TestCtorInitializer {
|
|
ClassWithDestructor c;
|
|
TestCtorInitializer(AddressVector<ClassWithDestructor> &v)
|
|
: c(ClassWithDestructor(v)) {}
|
|
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
|
|
object of type 'ClassWithDestructor' is still referred \
|
|
to by the stack variable 'v' upon returning to the caller}}
|
|
};
|
|
|
|
void testCtorInitializer() {
|
|
AddressVector<ClassWithDestructor> v;
|
|
{
|
|
TestCtorInitializer t(v);
|
|
// Check if the last destructor is an automatic destructor.
|
|
// A temporary destructor would have fired by now.
|
|
#if ELIDE
|
|
clang_analyzer_eval(v.len == 1); // expected-warning{{TRUE}}
|
|
#else
|
|
clang_analyzer_eval(v.len == 3); // expected-warning{{TRUE}}
|
|
#endif
|
|
}
|
|
#if ELIDE
|
|
// 0. Construct the member variable.
|
|
// 1. Destroy the member variable.
|
|
clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}}
|
|
#else
|
|
// 0. Construct the temporary.
|
|
// 1. Construct the member variable.
|
|
// 2. Destroy the temporary.
|
|
// 3. Destroy the member variable.
|
|
clang_analyzer_eval(v.len == 4); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[0] == v.buf[2]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[1] == v.buf[3]); // expected-warning{{TRUE}}
|
|
#endif
|
|
}
|
|
|
|
|
|
ClassWithDestructor make1(AddressVector<ClassWithDestructor> &v) {
|
|
return ClassWithDestructor(v);
|
|
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
|
|
object of type 'ClassWithDestructor' is still referred \
|
|
to by the stack variable 'v' upon returning to the caller}}
|
|
}
|
|
ClassWithDestructor make2(AddressVector<ClassWithDestructor> &v) {
|
|
return make1(v);
|
|
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
|
|
object of type 'ClassWithDestructor' is still referred \
|
|
to by the stack variable 'v' upon returning to the caller}}
|
|
}
|
|
ClassWithDestructor make3(AddressVector<ClassWithDestructor> &v) {
|
|
return make2(v);
|
|
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
|
|
object of type 'ClassWithDestructor' is still referred \
|
|
to by the stack variable 'v' upon returning to the caller}}
|
|
}
|
|
|
|
void testMultipleReturnsWithDestructors() {
|
|
AddressVector<ClassWithDestructor> v;
|
|
{
|
|
ClassWithDestructor c = make3(v);
|
|
// Check if the last destructor is an automatic destructor.
|
|
// A temporary destructor would have fired by now.
|
|
#if ELIDE
|
|
clang_analyzer_eval(v.len == 1); // expected-warning{{TRUE}}
|
|
#else
|
|
clang_analyzer_eval(v.len == 9); // expected-warning{{TRUE}}
|
|
#endif
|
|
}
|
|
|
|
#if ELIDE
|
|
// 0. Construct the variable. Yes, constructor in make1() constructs
|
|
// the variable 'c'.
|
|
// 1. Destroy the variable.
|
|
clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}}
|
|
#else
|
|
// 0. Construct the temporary in make1().
|
|
// 1. Construct the temporary in make2().
|
|
// 2. Destroy the temporary in make1().
|
|
// 3. Construct the temporary in make3().
|
|
// 4. Destroy the temporary in make2().
|
|
// 5. Construct the temporary here.
|
|
// 6. Destroy the temporary in make3().
|
|
// 7. Construct the variable.
|
|
// 8. Destroy the temporary here.
|
|
// 9. Destroy the variable.
|
|
clang_analyzer_eval(v.len == 10); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[0] == v.buf[2]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[1] == v.buf[4]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[3] == v.buf[6]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[5] == v.buf[8]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[7] == v.buf[9]); // expected-warning{{TRUE}}
|
|
#endif
|
|
}
|
|
|
|
void consume(ClassWithDestructor c) {
|
|
c.push();
|
|
// expected-warning@-1 {{Address of stack memory associated with local \
|
|
variable 'c' is still referred to by the stack variable 'v' upon returning \
|
|
to the caller}}
|
|
}
|
|
|
|
void testArgumentConstructorWithDestructor() {
|
|
AddressVector<ClassWithDestructor> v;
|
|
|
|
consume(make3(v));
|
|
|
|
#if ELIDE
|
|
// 0. Construct the argument.
|
|
// 1. Forced push() in consume().
|
|
// 2. Destroy the argument.
|
|
clang_analyzer_eval(v.len == 3); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[1] == v.buf[2]); // expected-warning{{TRUE}}
|
|
#else
|
|
// 0. Construct the temporary in make1().
|
|
// 1. Construct the temporary in make2().
|
|
// 2. Destroy the temporary in make1().
|
|
// 3. Construct the temporary in make3().
|
|
// 4. Destroy the temporary in make2().
|
|
// 5. Construct the temporary here.
|
|
// 6. Destroy the temporary in make3().
|
|
// 7. Construct the argument.
|
|
// 8. Forced push() in consume().
|
|
// 9. Destroy the argument. Notice the reverse order!
|
|
// 10. Destroy the temporary here.
|
|
clang_analyzer_eval(v.len == 11); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[0] == v.buf[2]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[1] == v.buf[4]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[3] == v.buf[6]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[5] == v.buf[10]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[7] == v.buf[8]); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(v.buf[8] == v.buf[9]); // expected-warning{{TRUE}}
|
|
#endif
|
|
}
|
|
|
|
struct Foo {
|
|
Foo(Foo **q) {
|
|
*q = this;
|
|
}
|
|
};
|
|
|
|
Foo make1(Foo **r) {
|
|
return Foo(r);
|
|
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
|
|
object of type 'Foo' is still referred to by the stack \
|
|
variable 'z' upon returning to the caller}}
|
|
}
|
|
|
|
void test_copy_elision() {
|
|
Foo *z;
|
|
// If the copy elided, 'z' points to 'tmp', otherwise it's a dangling pointer.
|
|
Foo tmp = make1(&z);
|
|
(void)tmp;
|
|
}
|
|
|
|
} // namespace address_vector_tests
|
|
|
|
namespace arg_directly_from_return_in_loop {
|
|
|
|
struct Result {
|
|
int value;
|
|
};
|
|
|
|
Result create() {
|
|
return Result{10};
|
|
}
|
|
|
|
int accessValue(Result r) {
|
|
return r.value;
|
|
}
|
|
|
|
void test() {
|
|
for (int i = 0; i < 3; ++i) {
|
|
int v = accessValue(create());
|
|
if (i == 0) {
|
|
clang_analyzer_dump(v); // expected-warning {{10 S32b}}
|
|
} else {
|
|
clang_analyzer_dump(v); // expected-warning {{10 S32b}}
|
|
// was {{reg_${{[0-9]+}}<int r.value> }} for C++11
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace arg_directly_from_return_in_loop
|