Files
Andy Ames b82e355d1b [analyzer] Fix crash in RegionStoreManager::bindArray from constructor array-to-pointer decay (#210649)
ProcessInitializer() strips implicit casts from a CXXCtorInitializer's
init expression via IgnoreImplicit(), then decides whether to treat the
initializer as a direct array-to-array member copy by checking
Init->getType()->isArrayType(). For a pointer member initialized via
array-to-pointer decay of a reference-to-array constructor parameter
(e.g. `Foo(T (&arr)[N]) : ptr_(arr) {}`), IgnoreImplicit() strips the
ArrayToPointerDecay cast, exposing the underlying array-typed
expression, so this check misfires even though the field itself is a
pointer, not an array. That branch fetches the raw region address of the
whole array, bypassing the normal decay logic (which produces an
ElementRegion), so the pointer member ends up holding the address of the
whole array typed as the array itself, instead of an ElementRegion at
index 0.

Later, dereferencing and storing through that mistyped pointer routes
into RegionStoreManager::bindArray() (instead of bindScalar()), which
unconditionally casts its Init value to nonloc::CompoundVal, asserting
in a debug build and segfaulting in a release build when Init is
anything else, e.g. a nonloc::LocAsInteger produced by round-tripping a
pointer through an integer type.

Fix the actual bug by checking the field's type instead of the
initializer expression's type. Also generalize bindArray()'s existing
guard (added by #178923 for issue #178797) from an enumeration of
specific SVal kinds to the same exhaustive
`!isa<nonloc::CompoundVal>()` check already used by its siblings
bindStruct() and bindVector(), so it doesn't need to be extended again
every time a new SVal kind reaches this path -- this is what actually
catches our case (nonloc::LocAsInteger), which the prior enumeration
didn't cover.

This is the same underlying bug behind #147686 (fixed by #153177, which
its own author noted was "more of a workaround") and #178797 (fixed by
#178923); both those fixes patched symptoms at bindArray() without
addressing the ProcessInitializer() root cause. Fixing the root cause
also resolves two FIXME-annotated precision gaps in
clang/test/Analysis/initializer.cpp's gh147686 regression test.

Fixes #210183

AI tool use disclosure: Claude Code (Anthropic) assisted in reducing the
original crash to a minimal, dependency-free reproducer (via creduce
plus manual bisection, verifying each reduction step against the actual
crash), which informed root-causing this bug in
RegionStoreManager::bindArray and ExprEngine::ProcessInitializer. The
commits made here were drafted with Claude's assistance and reviewed by
me before being pushed. I've reviewed all AI-assisted contributions here
and take full responsibility for the correctness of this change.

---------

Co-authored-by: Andy Ames <andy.ames@joby.aero>
Co-authored-by: Balázs Benics <benicsbalazs@gmail.com>
(cherry picked from commit 5b1fa37be65be730784a675c40b15d5a702f38ab)
2026-07-22 14:01:06 +00:00

659 lines
16 KiB
C++

// RUN: %clang_analyze_cc1 -w -verify %s\
// RUN: -analyzer-checker=core,unix.Malloc,cplusplus.NewDeleteLeaks\
// RUN: -analyzer-checker=debug.ExprInspection -std=c++11
// RUN: %clang_analyze_cc1 -w -verify %s\
// RUN: -analyzer-checker=core,unix.Malloc,cplusplus.NewDeleteLeaks\
// RUN: -analyzer-checker=debug.ExprInspection -std=c++17
// RUN: %clang_analyze_cc1 -w -verify %s\
// RUN: -analyzer-checker=core,unix.Malloc,cplusplus.NewDeleteLeaks\
// RUN: -analyzer-checker=debug.ExprInspection -std=c++11\
// RUN: -DTEST_INLINABLE_ALLOCATORS
// RUN: %clang_analyze_cc1 -w -verify %s\
// RUN: -analyzer-checker=core,unix.Malloc,cplusplus.NewDeleteLeaks\
// RUN: -analyzer-checker=debug.ExprInspection -std=c++17\
// RUN: -DTEST_INLINABLE_ALLOCATORS
void clang_analyzer_eval(bool);
#include "Inputs/system-header-simulator-cxx.h"
class A {
int x;
public:
A();
};
A::A() : x(0) {
clang_analyzer_eval(x == 0); // expected-warning{{TRUE}}
}
class DirectMember {
int x;
public:
DirectMember(int value) : x(value) {}
int getX() { return x; }
};
void testDirectMember() {
DirectMember obj(3);
clang_analyzer_eval(obj.getX() == 3); // expected-warning{{TRUE}}
}
class IndirectMember {
struct {
int x;
};
public:
IndirectMember(int value) : x(value) {}
int getX() { return x; }
};
void testIndirectMember() {
IndirectMember obj(3);
clang_analyzer_eval(obj.getX() == 3); // expected-warning{{TRUE}}
}
struct DelegatingConstructor {
int x;
DelegatingConstructor(int y) { x = y; }
DelegatingConstructor() : DelegatingConstructor(42) {}
};
void testDelegatingConstructor() {
DelegatingConstructor obj;
clang_analyzer_eval(obj.x == 42); // expected-warning{{TRUE}}
}
struct RefWrapper {
RefWrapper(int *p) : x(*p) {}
RefWrapper(int &r) : x(r) {}
int &x;
};
void testReferenceMember() {
int *p = 0;
RefWrapper X(p); // expected-warning@-7 {{Dereference of null pointer}}
}
void testReferenceMember2() {
int *p = 0;
RefWrapper X(*p); // expected-warning {{Forming reference to null pointer}}
}
extern "C" char *strdup(const char *);
class StringWrapper {
char *str;
public:
StringWrapper(const char *input) : str(strdup(input)) {} // no-warning
};
// PR15070 - Constructing a type containing a non-POD array mistakenly
// tried to perform a bind instead of relying on the CXXConstructExpr,
// which caused a cast<> failure in RegionStore.
namespace DefaultConstructorWithCleanups {
class Element {
public:
int value;
class Helper {
public:
~Helper();
};
Element(Helper h = Helper());
};
class Wrapper {
public:
Element arr[2];
Wrapper();
};
Wrapper::Wrapper() /* initializers synthesized */ {}
int test() {
Wrapper w;
return w.arr[0].value; // no-warning
}
}
namespace DefaultMemberInitializers {
struct Wrapper {
int value = 42;
Wrapper() {}
Wrapper(int x) : value(x) {}
Wrapper(bool) {}
};
void test() {
Wrapper w1;
clang_analyzer_eval(w1.value == 42); // expected-warning{{TRUE}}
Wrapper w2(50);
clang_analyzer_eval(w2.value == 50); // expected-warning{{TRUE}}
Wrapper w3(false);
clang_analyzer_eval(w3.value == 42); // expected-warning{{TRUE}}
}
struct StringWrapper {
const char s[4] = "abc";
const char *p = "xyz";
StringWrapper(bool) {}
};
void testString() {
StringWrapper w(true);
clang_analyzer_eval(w.s[1] == 'b'); // expected-warning{{TRUE}}
clang_analyzer_eval(w.p[1] == 'y'); // expected-warning{{TRUE}}
}
}
namespace ReferenceInitialization {
struct OtherStruct {
OtherStruct(int i);
~OtherStruct();
};
struct MyStruct {
MyStruct(int i);
MyStruct(OtherStruct os);
void method() const;
};
void referenceInitializeLocal() {
const MyStruct &myStruct(5);
myStruct.method(); // no-warning
}
void referenceInitializeMultipleLocals() {
const MyStruct &myStruct1(5), myStruct2(5), &myStruct3(5);
myStruct1.method(); // no-warning
myStruct2.method(); // no-warning
myStruct3.method(); // no-warning
}
void referenceInitializeLocalWithCleanup() {
const MyStruct &myStruct(OtherStruct(5));
myStruct.method(); // no-warning
}
};
namespace PR31592 {
struct C {
C() : f("}") { } // no-crash
const char(&f)[2];
};
}
namespace CXX_initializer_lists {
struct C {
C(std::initializer_list<int *> list);
};
void testPointerEscapeIntoLists() {
C empty{}; // no-crash
// Do not warn that 'x' leaks. It might have been deleted by
// the destructor of 'c'.
int *x = new int;
C c{x}; // no-warning
}
void testPassListsWithExplicitConstructors() {
(void)(std::initializer_list<int>){12}; // no-crash
}
}
namespace CXX17_aggregate_construction {
struct A {
A();
};
struct B: public A {
};
struct C: public B {
};
struct D: public virtual A {
};
// In C++17, classes B and C are aggregates, so they will be constructed
// without actually calling their trivial constructor. Used to crash.
void foo() {
B b = {}; // no-crash
const B &bl = {}; // no-crash
B &&br = {}; // no-crash
C c = {}; // no-crash
const C &cl = {}; // no-crash
C &&cr = {}; // no-crash
D d = {}; // no-crash
#if __cplusplus >= 201703L
C cd = {{}}; // no-crash
const C &cdl = {{}}; // no-crash
C &&cdr = {{}}; // no-crash
const B &bll = {{}}; // no-crash
const B &bcl = C({{}}); // no-crash
B &&bcr = C({{}}); // no-crash
#endif
}
} // namespace CXX17_aggregate_construction
namespace newexpr_init_list_initialization {
template <class FirstT, class... Rest>
void escape(FirstT first, Rest... args);
struct S {
int foo;
int bar;
};
void none_designated() {
S *s = new S{13,1};
clang_analyzer_eval(13 == s->foo); // expected-warning{{TRUE}}
clang_analyzer_eval(1 == s->bar); // expected-warning{{TRUE}}
delete s;
}
void none_designated_swapped() {
S *s = new S{1,13};
clang_analyzer_eval(1 == s->foo); // expected-warning{{TRUE}}
clang_analyzer_eval(13 == s->bar); // expected-warning{{TRUE}}
delete s;
}
void one_designated_one_not() {
S *s = new S{ 1, .bar = 13 };
clang_analyzer_eval(1 == s->foo); // expected-warning{{TRUE}}
clang_analyzer_eval(13 == s->bar); // expected-warning{{TRUE}}
delete s;
}
void all_designated() {
S *s = new S{
.foo = 13,
.bar = 1,
};
clang_analyzer_eval(13 == s->foo); // expected-warning{{TRUE}}
clang_analyzer_eval(1 == s->bar); // expected-warning{{TRUE}}
delete s;
}
void non_designated_array_of_aggr_struct() {
S *s = new S[2] { {1, 2}, {3, 4} };
clang_analyzer_eval(1 == s[0].foo); // expected-warning{{TRUE}}
clang_analyzer_eval(2 == s[0].bar); // expected-warning{{TRUE}}
clang_analyzer_eval(3 == s[1].foo); // expected-warning{{TRUE}}
clang_analyzer_eval(4 == s[1].bar); // expected-warning{{TRUE}}
delete[] s;
}
struct WithGaps {
int foo;
int bar;
int baz;
};
void out_of_order_designated_initializers_with_gaps() {
WithGaps *s = new WithGaps{
.foo = 13,
.baz = 1,
};
clang_analyzer_eval(13 == s->foo); // expected-warning{{TRUE}}
clang_analyzer_eval(0 == s->bar); // expected-warning{{TRUE}}
clang_analyzer_eval(1 == s->baz); // expected-warning{{TRUE}}
delete s;
}
// https://eel.is/c++draft/dcl.init.aggr#note-6:
// Static data members, non-static data members of anonymous
// union members, and unnamed bit-fields are not considered
// elements of the aggregate.
struct NonConsideredFields {
int i;
static int s;
int j;
int :17;
int k;
};
void considered_fields_initd() {
auto S = new NonConsideredFields { 1, 2, 3 };
clang_analyzer_eval(1 == S->i); // expected-warning{{TRUE}}
clang_analyzer_eval(2 == S->j); // expected-warning{{TRUE}}
clang_analyzer_eval(3 == S->k); // expected-warning{{TRUE}}
delete S;
}
#if __cplusplus >= 201703L
enum Enum : int {
};
void list_init_enum() {
Enum *E = new Enum{53};
clang_analyzer_eval(53 == *E); // expected-warning{{TRUE}}
delete E;
}
#endif // __cplusplus >= 201703L
class PubClass {
public:
int foo;
int bar;
};
void public_class_designated_initializers() {
S *s = new S{
.foo = 13,
.bar = 1,
};
clang_analyzer_eval(13 == s->foo); // expected-warning{{TRUE}}
clang_analyzer_eval(1 == s->bar); // expected-warning{{TRUE}}
delete s;
}
union UnionTestTy {
int x;
char y;
};
void new_expr_aggr_init_union_no_designator() {
UnionTestTy *u = new UnionTestTy{};
clang_analyzer_eval(0 == u->x); // expected-warning{{UNKNOWN}} FIXME: should be TRUE
clang_analyzer_eval(u->y); // expected-warning{{UNKNOWN}} FIXME: should be undefined, warning
delete u;
}
void new_expr_aggr_init_union_designated_first_field() {
UnionTestTy *u = new UnionTestTy{ .x = 14 };
clang_analyzer_eval(14 == u->x); // expected-warning{{UNKNOWN}} FIXME: should be TRUE
clang_analyzer_eval(u->y); // expected-warning{{UNKNOWN}} FIXME: should be undefined, warning
delete u;
}
void new_expr_aggr_init_union_designated_non_first_field() {
UnionTestTy *u = new UnionTestTy{ .y = 3 };
clang_analyzer_eval(3 == u->y); // expected-warning{{UNKNOWN}} FIXME: should be TRUE
clang_analyzer_eval(u->x); // expected-warning{{UNKNOWN}} FIXME: should be undefined, warning
delete u;
}
union UnionTestTyWithDefaultMemberInit {
int x;
char y = 14;
};
void union_with_default_member_init_empty_init_list() {
auto U = new UnionTestTyWithDefaultMemberInit{};
// clang_analyzer_eval(14 == U->y); // FIXME: Should be true
clang_analyzer_eval(U->x); // expected-warning{{UNKNOWN}} FIXME: should be undefined, warning
delete U;
}
struct Inner {
int bar;
};
struct Nested {
int foo;
Inner inner;
int baz;
};
void nested_aggregates() {
auto N = new Nested{};
clang_analyzer_eval(0 == N->foo); // expected-warning{{TRUE}}
clang_analyzer_eval(0 == N->inner.bar); // expected-warning{{TRUE}}
clang_analyzer_eval(0 == N->baz); // expected-warning{{TRUE}}
auto N1 = new Nested{1};
clang_analyzer_eval(1 == N1->foo); // expected-warning{{TRUE}}
clang_analyzer_eval(0 == N1->inner.bar); // expected-warning{{TRUE}}
clang_analyzer_eval(0 == N1->baz); // expected-warning{{TRUE}}
auto N2 = new Nested{.baz = 14};
clang_analyzer_eval(0 == N2->foo); // expected-warning{{TRUE}}
clang_analyzer_eval(0 == N2->inner.bar); // expected-warning{{TRUE}}
clang_analyzer_eval(14 == N2->baz); // expected-warning{{TRUE}}
auto N3 = new Nested{1,2,3};
clang_analyzer_eval(1 == N3->foo); // expected-warning{{TRUE}}
clang_analyzer_eval(2 == N3->inner.bar); // expected-warning{{TRUE}}
clang_analyzer_eval(3 == N3->baz); // expected-warning{{TRUE}}
auto N4 = new Nested{1, {}, 3};
clang_analyzer_eval(1 == N4->foo); // expected-warning{{TRUE}}
clang_analyzer_eval(0 == N4->inner.bar); // expected-warning{{TRUE}}
clang_analyzer_eval(3 == N4->baz); // expected-warning{{TRUE}}
auto N5 = new Nested{{},{},{}};
clang_analyzer_eval(0 == N5->foo); // expected-warning{{TRUE}}
clang_analyzer_eval(0 == N5->inner.bar); // expected-warning{{TRUE}}
clang_analyzer_eval(0 == N5->baz); // expected-warning{{TRUE}}
auto N6 = new Nested{1, {.bar = 2}, 3};
clang_analyzer_eval(1 == N6->foo); // expected-warning{{TRUE}}
clang_analyzer_eval(2 == N6->inner.bar); // expected-warning{{TRUE}}
clang_analyzer_eval(3 == N6->baz); // expected-warning{{TRUE}}
auto N7 = new Nested{1, {2}, 3};
clang_analyzer_eval(1 == N7->foo); // expected-warning{{TRUE}}
clang_analyzer_eval(2 == N7->inner.bar); // expected-warning{{TRUE}}
clang_analyzer_eval(3 == N7->baz); // expected-warning{{TRUE}}
escape(N,N1,N2,N3,N4,N5,N6,N7);
}
} // namespace newexpr_init_list_initialization
namespace placement_new_initializer_list_arg {
struct S {
int x;
};
void aggregate_struct() {
S s;
S *s_ptr = new (&s) S{1};
clang_analyzer_eval(1 == s_ptr->x); // expected-warning{{TRUE}}
S vi;
S *vi_ptr = new (&vi) S{};
clang_analyzer_eval(0 == vi_ptr->x); // expected-warning{{TRUE}}
S di;
S *di_ptr = new (&di) S;
int z = di_ptr->x + 1; // expected-warning{{The left operand of '+' is a garbage value}}
}
void initialize_non_zeroth_element(S arr[2]) {
S *s = new (&arr[1]) S{1};
clang_analyzer_eval(1 == s->x); // expected-warning{{TRUE}}
}
void initialize_non_zeroth_argument_pointers(S *arr[2]) {
arr[1] = new (arr[1]) S{1};
clang_analyzer_eval(1 == arr[1]->x); // expected-warning{{TRUE}}
}
} // namespace placement_new_initializer_list_arg
namespace CXX17_transparent_init_list_exprs {
class A {};
class B: private A {};
B boo();
void foo1() {
B b { boo() }; // no-crash
}
class C: virtual public A {};
C coo();
void foo2() {
C c { coo() }; // no-crash
}
B foo_recursive() {
B b { foo_recursive() };
}
} // namespace CXX17_transparent_init_list_exprs
namespace skip_vbase_initializer_side_effects {
int glob;
struct S {
S() { ++glob; }
};
struct A {
A() {}
A(S s) {}
};
struct B : virtual A {
B() : A(S()) {}
};
struct C : B {
C() {}
};
void foo() {
glob = 0;
B b;
clang_analyzer_eval(glob == 1); // expected-warning{{TRUE}}
C c; // no-crash
clang_analyzer_eval(glob == 1); // expected-warning{{TRUE}}
}
} // namespace skip_vbase_initializer_side_effects
namespace dont_skip_vbase_initializers_in_most_derived_class {
struct A {
static int a;
A() { a = 0; }
A(int x) { a = x; }
};
struct B {
static int b;
B() { b = 0; }
B(int y) { b = y; }
};
struct C : virtual A {
C() : A(1) {}
};
struct D : C, virtual B {
D() : B(2) {}
};
void testD() {
D d;
clang_analyzer_eval(A::a == 0); // expected-warning{{TRUE}}
clang_analyzer_eval(B::b == 2); // expected-warning{{TRUE}}
}
struct E : virtual B, C {
E() : B(2) {}
};
void testE() {
E e;
clang_analyzer_eval(A::a == 0); // expected-warning{{TRUE}}
clang_analyzer_eval(B::b == 2); // expected-warning{{TRUE}}
}
struct F : virtual A, virtual B {
F() : A(1) {}
};
struct G : F {
G(): B(2) {}
};
void testG() {
G g;
clang_analyzer_eval(A::a == 0); // expected-warning{{TRUE}}
clang_analyzer_eval(B::b == 2); // expected-warning{{TRUE}}
}
struct H : virtual B, virtual A {
H(): A(1) {}
};
struct I : H {
I(): B(2) {}
};
void testI() {
I i;
clang_analyzer_eval(A::a == 0); // expected-warning{{TRUE}}
clang_analyzer_eval(B::b == 2); // expected-warning{{TRUE}}
}
} // namespace dont_skip_vbase_initializers_in_most_derived_class
namespace elementwise_copy_small_array_from_post_initializer_of_cctor {
struct String {
String(const String &) {}
};
struct MatchComponent {
unsigned numbers[2];
String prerelease;
MatchComponent(MatchComponent const &) = default;
};
MatchComponent get();
void consume(MatchComponent const &);
MatchComponent parseMatchComponent() {
MatchComponent component = get();
component.numbers[0] = 10;
component.numbers[1] = 20;
return component; // We should have no stack addr escape warning here.
}
void top() {
consume(parseMatchComponent());
}
} // namespace elementwise_copy_small_array_from_post_initializer_of_cctor
namespace gh147686 {
// The problem reported in https://github.com/llvm/llvm-project/issues/147686
// is sensitive to the initializer form: using parenthesis to initialize m_ptr
// resulted in crashes when analyzing *m_ptr = '\0'; but using braces is fine.
struct A {
A() : m_ptr(m_buf) { *m_ptr = '\0'; } // no-crash
A(int overload) : m_ptr{m_buf} { *m_ptr = '\0'; }
A(char src) : m_ptr(m_buf) { *m_ptr = src; } // no-crash
A(char src, int overload) : m_ptr{m_buf} { *m_ptr = src; }
char m_buf[64] = {0};
char * m_ptr;
};
void test1() {
A a;
clang_analyzer_eval(a.m_buf[0] == 0); // expected-warning{{TRUE}}
clang_analyzer_eval(*a.m_ptr == 0); // expected-warning{{TRUE}}
}
void test2() {
A a(314);
clang_analyzer_eval(a.m_buf[0] == 0); // expected-warning{{TRUE}}
clang_analyzer_eval(*a.m_ptr == 0); // expected-warning{{TRUE}}
}
void test3() {
A a(0);
clang_analyzer_eval(a.m_buf[0] == 0); // expected-warning{{TRUE}}
clang_analyzer_eval(*a.m_ptr == 0); // expected-warning{{TRUE}}
}
void test3Bis(char arg) {
A a(arg);
clang_analyzer_eval(a.m_buf[0] == arg); // expected-warning{{TRUE}}
clang_analyzer_eval(*a.m_ptr == arg); // expected-warning{{TRUE}}
}
void test4(char arg) {
A a(arg, 314);
clang_analyzer_eval(a.m_buf[0] == arg); // expected-warning{{TRUE}}
clang_analyzer_eval(*a.m_ptr == arg); // expected-warning{{TRUE}}
}
} // namespace gh147686