https://github.com/llvmbot created https://github.com/llvm/llvm-project/pull/211069
Backport 5b1fa37be65be730784a675c40b15d5a702f38ab Requested by: @steakhal >From 224b2a86f45bbb4564f266b8b33a75b99bd93ac9 Mon Sep 17 00:00:00 2001 From: Andy Ames <[email protected]> Date: Tue, 21 Jul 2026 09:17:12 -0700 Subject: [PATCH] [analyzer] Fix crash in RegionStoreManager::bindArray from constructor array-to-pointer decay (#210649) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 <[email protected]> Co-authored-by: Balázs Benics <[email protected]> (cherry picked from commit 5b1fa37be65be730784a675c40b15d5a702f38ab) --- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 2 +- clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 10 +++++- clang/test/Analysis/initializer.cpp | 8 ++--- clang/test/Analysis/issue-210183.cpp | 34 +++++++++++++++++++ 4 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 clang/test/Analysis/issue-210183.cpp diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 2d86c779ba850..678b07ebba1b0 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1196,7 +1196,7 @@ void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit, } SVal InitVal; - if (Init->getType()->isArrayType()) { + if (Field->getType()->isArrayType()) { // Handle arrays of trivial type. We can represent this with a // primitive load/copy from the base array region. const ArraySubscriptExpr *ASE; diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index 0f7e03ce50858..01c792a9011f9 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -2726,8 +2726,16 @@ RegionStoreManager::bindArray(LimitedRegionBindingsConstRef B, return bindAggregate(B, R, Init); } - if (isa<nonloc::SymbolVal, UnknownVal, UndefinedVal>(Init)) + // We may get non-CompoundVal accidentally due to imprecise cast logic or + // that we are binding a genuinely symbolic/unknown/undefined array value. + // Preserve Init as a default binding rather than lossily converting to + // UnknownVal(), and handle every non-CompoundVal case exhaustively (like + // bindStruct()/bindVector() do) rather than enumerating specific SVal + // kinds one at a time. + if (!isa<nonloc::CompoundVal>(Init)) { + assert((isa<nonloc::SymbolVal, UnknownVal, UndefinedVal>(Init))); return bindAggregate(B, R, Init); + } // Remaining case: explicit compound values. const nonloc::CompoundVal& CV = Init.castAs<nonloc::CompoundVal>(); diff --git a/clang/test/Analysis/initializer.cpp b/clang/test/Analysis/initializer.cpp index 88758f7c3ac1d..dd0c91b7ead61 100644 --- a/clang/test/Analysis/initializer.cpp +++ b/clang/test/Analysis/initializer.cpp @@ -628,8 +628,7 @@ struct A { void test1() { A a; clang_analyzer_eval(a.m_buf[0] == 0); // expected-warning{{TRUE}} - // FIXME The next eval should result in TRUE. - clang_analyzer_eval(*a.m_ptr == 0); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(*a.m_ptr == 0); // expected-warning{{TRUE}} } void test2() { @@ -646,9 +645,8 @@ void test3() { void test3Bis(char arg) { A a(arg); - // FIXME This test should behave like test3. - clang_analyzer_eval(a.m_buf[0] == arg); // expected-warning{{FALSE}} // expected-warning{{TRUE}} - clang_analyzer_eval(*a.m_ptr == arg); // expected-warning{{UNKNOWN}} + 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) { diff --git a/clang/test/Analysis/issue-210183.cpp b/clang/test/Analysis/issue-210183.cpp new file mode 100644 index 0000000000000..a9c2ff24bccb8 --- /dev/null +++ b/clang/test/Analysis/issue-210183.cpp @@ -0,0 +1,34 @@ +// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -analyzer-checker=core,debug.ExprInspection -std=c++17 -verify %s + +// https://github.com/llvm/llvm-project/issues/210183 +// +// A pointer member initialized via array-to-pointer decay of a +// reference-to-array constructor parameter used to be modeled as the +// address of the whole array (instead of its first element). Dereferencing +// and storing through that mistyped pointer then reached +// RegionStoreManager::bindArray() with a scalar Init value, crashing on an +// unchecked castAs<nonloc::CompoundVal>(). + +template <class T> void clang_analyzer_dump(T); + +template <class T> struct Span { + template <int N> + Span(T (&arr)[N]) : ptr_(arr) {} + T *data() { return ptr_; } + T *ptr_; +}; + +char *ptr(); +char buffer[10]; + +void test() { + char *p = Span<char>(buffer).data(); + + // p and buffer must resolve to the same address: the first element of + // buffer, not the whole array. + clang_analyzer_dump(buffer); // expected-warning{{&Element{buffer,0 S64b,char}}} + clang_analyzer_dump(p); // expected-warning{{&Element{buffer,0 S64b,char}}} + + int v = (int)(long)ptr(); + *p = v; // no-crash +} _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
