When instantiating "callable<T>", the "class CallableType" nested type will only have a declaration in the copy for the instantiation - because it's not refereed to directly by any other code that would need a complete definition. However, in the past, when conservative eval calling member function, we took the static type of the "this" expr, and looked up the CXXRecordDecl it refereed to to see if it has any mutable members (to decide if it needs to refine invalidation or not). Unfortunately, that query needs a definition, and it asserts otherwise, thus we crashed. To fix this, we should consult the dynamic type of the object, because that will have the definition. I anyways added a check for "hasDefinition" just to be on the safe side. Fixes #77378
320 lines
6.8 KiB
C++
320 lines
6.8 KiB
C++
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify -analyzer-config eagerly-assume=false %s
|
|
|
|
void clang_analyzer_eval(bool);
|
|
|
|
struct A {
|
|
int x;
|
|
void foo() const;
|
|
void bar();
|
|
|
|
void testImplicitThisSyntax() {
|
|
x = 3;
|
|
foo();
|
|
clang_analyzer_eval(x == 3); // expected-warning{{TRUE}}
|
|
bar();
|
|
clang_analyzer_eval(x == 3); // expected-warning{{UNKNOWN}}
|
|
}
|
|
};
|
|
|
|
struct B {
|
|
mutable int mut;
|
|
void foo() const;
|
|
};
|
|
|
|
struct C {
|
|
int *p;
|
|
void foo() const;
|
|
};
|
|
|
|
struct MutBase {
|
|
mutable int b_mut;
|
|
};
|
|
|
|
struct MutDerived : MutBase {
|
|
void foo() const;
|
|
};
|
|
|
|
struct PBase {
|
|
int *p;
|
|
};
|
|
|
|
struct PDerived : PBase {
|
|
void foo() const;
|
|
};
|
|
|
|
struct Inner {
|
|
int x;
|
|
int *p;
|
|
void bar() const;
|
|
};
|
|
|
|
struct Outer {
|
|
int x;
|
|
Inner in;
|
|
void foo() const;
|
|
};
|
|
|
|
void checkThatConstMethodWithoutDefinitionDoesNotInvalidateObject() {
|
|
A t;
|
|
t.x = 3;
|
|
t.foo();
|
|
clang_analyzer_eval(t.x == 3); // expected-warning{{TRUE}}
|
|
// Test non-const does invalidate
|
|
t.bar();
|
|
clang_analyzer_eval(t.x); // expected-warning{{UNKNOWN}}
|
|
}
|
|
|
|
void checkThatConstMethodDoesInvalidateMutableFields() {
|
|
B t;
|
|
t.mut = 4;
|
|
t.foo();
|
|
clang_analyzer_eval(t.mut); // expected-warning{{UNKNOWN}}
|
|
}
|
|
|
|
void checkThatConstMethodDoesInvalidatePointedAtMemory() {
|
|
int x = 1;
|
|
C t;
|
|
t.p = &x;
|
|
t.foo();
|
|
clang_analyzer_eval(x); // expected-warning{{UNKNOWN}}
|
|
clang_analyzer_eval(t.p == &x); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
void checkThatConstMethodDoesInvalidateInheritedMutableFields() {
|
|
MutDerived t;
|
|
t.b_mut = 4;
|
|
t.foo();
|
|
clang_analyzer_eval(t.b_mut); // expected-warning{{UNKNOWN}}
|
|
}
|
|
|
|
void checkThatConstMethodDoesInvalidateInheritedPointedAtMemory() {
|
|
int x = 1;
|
|
PDerived t;
|
|
t.p = &x;
|
|
t.foo();
|
|
clang_analyzer_eval(x); // expected-warning{{UNKNOWN}}
|
|
clang_analyzer_eval(t.p == &x); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
void checkThatConstMethodDoesInvalidateContainedPointedAtMemory() {
|
|
int x = 1;
|
|
Outer t;
|
|
t.x = 2;
|
|
t.in.p = &x;
|
|
t.foo();
|
|
clang_analyzer_eval(x); // expected-warning{{UNKNOWN}}
|
|
clang_analyzer_eval(t.x == 2); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(t.in.p == &x); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
void checkThatContainedConstMethodDoesNotInvalidateObjects() {
|
|
Outer t;
|
|
t.x = 1;
|
|
t.in.x = 2;
|
|
t.in.bar();
|
|
clang_analyzer_eval(t.x == 1); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(t.in.x == 2); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
void checkPointerTypedThisExpression(A *a) {
|
|
a->x = 3;
|
|
a->foo();
|
|
clang_analyzer_eval(a->x == 3); // expected-warning{{TRUE}}
|
|
a->bar();
|
|
clang_analyzer_eval(a->x == 3); // expected-warning{{UNKNOWN}}
|
|
}
|
|
|
|
void checkReferenceTypedThisExpression(A &a) {
|
|
a.x = 3;
|
|
a.foo();
|
|
clang_analyzer_eval(a.x == 3); // expected-warning{{TRUE}}
|
|
a.bar();
|
|
clang_analyzer_eval(a.x == 3); // expected-warning{{UNKNOWN}}
|
|
}
|
|
|
|
// --- Versions of the above tests where the const method is inherited --- //
|
|
|
|
struct B1 {
|
|
void foo() const;
|
|
};
|
|
|
|
struct D1 : public B1 {
|
|
int x;
|
|
};
|
|
|
|
struct D2 : public B1 {
|
|
mutable int mut;
|
|
};
|
|
|
|
struct D3 : public B1 {
|
|
int *p;
|
|
};
|
|
|
|
struct DInner : public B1 {
|
|
int x;
|
|
int *p;
|
|
};
|
|
|
|
struct DOuter : public B1 {
|
|
int x;
|
|
DInner in;
|
|
};
|
|
|
|
void checkThatInheritedConstMethodDoesNotInvalidateObject() {
|
|
D1 t;
|
|
t.x = 1;
|
|
t.foo();
|
|
clang_analyzer_eval(t.x == 1); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
void checkThatInheritedConstMethodDoesInvalidateMutableFields() {
|
|
D2 t;
|
|
t.mut = 1;
|
|
t.foo();
|
|
clang_analyzer_eval(t.mut); // expected-warning{{UNKNOWN}}
|
|
}
|
|
|
|
void checkThatInheritedConstMethodDoesInvalidatePointedAtMemory() {
|
|
int x = 1;
|
|
D3 t;
|
|
t.p = &x;
|
|
t.foo();
|
|
clang_analyzer_eval(x); // expected-warning{{UNKNOWN}}
|
|
clang_analyzer_eval(t.p == &x); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
void checkThatInheritedConstMethodDoesInvalidateContainedPointedAtMemory() {
|
|
int x = 1;
|
|
DOuter t;
|
|
t.x = 2;
|
|
t.in.x = 3;
|
|
t.in.p = &x;
|
|
t.foo();
|
|
clang_analyzer_eval(x); // expected-warning{{UNKNOWN}}
|
|
clang_analyzer_eval(t.x == 2); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(t.in.x == 3); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(t.in.p == &x); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
void checkThatInheritedContainedConstMethodDoesNotInvalidateObjects() {
|
|
DOuter t;
|
|
t.x = 1;
|
|
t.in.x = 2;
|
|
t.in.foo();
|
|
clang_analyzer_eval(t.x == 1); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(t.in.x == 2); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
// --- PR21606 --- //
|
|
|
|
struct s1 {
|
|
void g(const int *i) const;
|
|
};
|
|
|
|
struct s2 {
|
|
void f(int *i) {
|
|
m_i = i;
|
|
m_s.g(m_i);
|
|
if (m_i)
|
|
*i = 42; // no-warning
|
|
}
|
|
|
|
int *m_i;
|
|
s1 m_s;
|
|
};
|
|
|
|
void PR21606()
|
|
{
|
|
s2().f(0);
|
|
}
|
|
|
|
// --- PR25392 --- //
|
|
|
|
struct HasConstMemberFunction {
|
|
public:
|
|
void constMemberFunction() const;
|
|
};
|
|
|
|
HasConstMemberFunction hasNoReturn() { } // expected-warning {{non-void function does not return a value}}
|
|
|
|
void testUnknownWithConstMemberFunction() {
|
|
hasNoReturn().constMemberFunction();
|
|
}
|
|
|
|
void testNonRegionLocWithConstMemberFunction() {
|
|
(*((HasConstMemberFunction *)(&&label))).constMemberFunction();
|
|
|
|
label: return;
|
|
}
|
|
|
|
// FIXME
|
|
// When there is a circular reference to an object and a const method is called
|
|
// the object is not invalidated because TK_PreserveContents has already been
|
|
// set.
|
|
struct Outer2;
|
|
|
|
struct InnerWithRef {
|
|
Outer2 *ref;
|
|
};
|
|
|
|
struct Outer2 {
|
|
int x;
|
|
InnerWithRef in;
|
|
void foo() const;
|
|
};
|
|
|
|
void checkThatConstMethodCallDoesInvalidateObjectForCircularReferences() {
|
|
Outer2 t;
|
|
t.x = 1;
|
|
t.in.ref = &t;
|
|
t.foo();
|
|
// FIXME: Should be UNKNOWN.
|
|
clang_analyzer_eval(t.x); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
namespace gh77378 {
|
|
template <typename Signature> class callable;
|
|
|
|
template <typename R> class callable<R()> {
|
|
struct CallableType {
|
|
bool operator()();
|
|
};
|
|
using MethodType = R (CallableType::*)();
|
|
CallableType *object_{nullptr};
|
|
MethodType method_;
|
|
|
|
public:
|
|
callable() = default;
|
|
|
|
template <typename T>
|
|
constexpr callable(const T &obj)
|
|
: object_{reinterpret_cast<CallableType *>(&const_cast<T &>(obj))},
|
|
method_{reinterpret_cast<MethodType>(
|
|
static_cast<bool (T::*)() const>(&T::operator()))} {}
|
|
|
|
constexpr bool const_method() const {
|
|
return (object_->*(method_))();
|
|
}
|
|
|
|
callable call() const & {
|
|
static const auto L = [this]() {
|
|
while (true) {
|
|
// This should not crash when conservative eval calling the member function
|
|
// when it unwinds the call stack due to exhausting the budget or reaching
|
|
// the inlining limit.
|
|
if (this->const_method()) {
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
return L;
|
|
}
|
|
};
|
|
|
|
void entry() {
|
|
callable<bool()>{}.call().const_method();
|
|
// expected-warning@-1 {{Address of stack memory associated with temporary object of type 'callable<bool ()>' is still referred to by the static variable 'L' upon returning to the caller. This will be a dangling reference}}
|
|
}
|
|
} // namespace gh77378
|