Skip to content

[analyzer] Harden RegionStoreManager::bindArray - #153177

Merged
mantognini merged 7 commits into
llvm:mainfrom
marco-antognini-sonarsource:mb/bindArray
Oct 2, 2025
Merged

[analyzer] Harden RegionStoreManager::bindArray#153177
mantognini merged 7 commits into
llvm:mainfrom
marco-antognini-sonarsource:mb/bindArray

Conversation

@marco-antognini-sonarsource

@marco-antognini-sonarsource marco-antognini-sonarsource commented Aug 12, 2025

Copy link
Copy Markdown
Contributor

Fixes #147686 by handling symbolic values similarly to bindStruct and handling constant values. The latter is actually more of a workaround: bindArray should not have to deal with such constants.

CPP-6688

Fixes llvm#147686 by completing
handling of literals and handling symbolic values similarly to
bindStruct.

CPP-6688
@marco-antognini-sonarsource
marco-antognini-sonarsource marked this pull request as ready for review August 12, 2025 12:33
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Aug 12, 2025
@llvmbot

llvmbot commented Aug 12, 2025

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-clang-static-analyzer-1

@llvm/pr-subscribers-clang

Author: Marco Borgeaud (marco-antognini-sonarsource)

Changes

Fixes #147686 by completing handling of literals and handling symbolic values similarly to bindStruct.

CPP-6688


Full diff: https://github.com/llvm/llvm-project/pull/153177.diff

2 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Core/RegionStore.cpp (+7-2)
  • (modified) clang/test/Analysis/initializer.cpp (+41)
diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
index 02375b0c3469a..ebe1a264e40f8 100644
--- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
+++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
@@ -2654,14 +2654,19 @@ RegionStoreManager::bindArray(LimitedRegionBindingsConstRef B,
     SVal V = getBinding(B.asStore(), *MRV, R->getValueType());
     return bindAggregate(B, R, V);
   }
+  if (auto const *Value = Init.getAsInteger()) {
+    auto SafeValue = StateMgr.getBasicVals().getValue(*Value);
+    return bindAggregate(B, R, nonloc::ConcreteInt(SafeValue));
+  }
 
-  // Handle lazy compound values.
+  // Handle lazy compound values and symbolic values.
   if (std::optional LCV = Init.getAs<nonloc::LazyCompoundVal>()) {
     if (std::optional NewB = tryBindSmallArray(B, R, AT, *LCV))
       return *NewB;
-
     return bindAggregate(B, R, Init);
   }
+  if (isa<nonloc::SymbolVal>(Init))
+    return bindAggregate(B, R, Init);
 
   if (Init.isUnknown())
     return bindAggregate(B, R, UnknownVal());
diff --git a/clang/test/Analysis/initializer.cpp b/clang/test/Analysis/initializer.cpp
index 713e121168571..ee90705ac3d28 100644
--- a/clang/test/Analysis/initializer.cpp
+++ b/clang/test/Analysis/initializer.cpp
@@ -610,3 +610,44 @@ 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}}
+  // FIXME The next eval should result in TRUE.
+  clang_analyzer_eval(*a.m_ptr == 0); // expected-warning{{UNKNOWN}}
+}
+
+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 test4() {
+  A a(0, 314);
+  clang_analyzer_eval(a.m_buf[0] == 0); // expected-warning{{TRUE}}
+  clang_analyzer_eval(*a.m_ptr == 0); // expected-warning{{TRUE}}
+}
+
+} // namespace gh147686

@balazs-benics-sonarsource

Copy link
Copy Markdown
Contributor

I recall that I already reviewed this downstream.
The fix is not perfect, as in the test we shouldn't be in the bindArray handler in Store at all - as we don't elementwise copy any aggregates (aka arrays) in the example.
So this should be thought of a hotfix rather than a proper fix to the underlying issue, which is why are we binding an array here.

I'd recommend to mark the lines in the test with the no-crash where we previously crashed.
And also elaborate in the commit message why this is not a proper fix, but just a hotfix.

All in all, not crashing is strictly better than crashing - in almost all scenario.
This patch does not do anything fundamentally wrong, so I approved downstream, and I'd also approve it here, but there is also a conflict of interest so I'll let the rest of the maintainers decide.

@NagyDonat NagyDonat left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the patch! I have some minor remarks inline but overall I like the patch and I completely agree with the maxim that "not crashing is strictly better than crashing".

Also thanks @steakhal for clarifying that this is a "hotfix" and covers situations that "do not belong to" bindArray. I agree that this should be mentioned in the commit message; in fact I think that we should also mention this in a FIXME source code comment, because it would be really valuable for those who try to understand the purpose of this method (It is called bindArray... but it also handles integer literals... maybe it's misnamed... ?).

Comment thread clang/lib/StaticAnalyzer/Core/RegionStore.cpp Outdated
Comment thread clang/lib/StaticAnalyzer/Core/RegionStore.cpp Outdated
Comment thread clang/lib/StaticAnalyzer/Core/RegionStore.cpp Outdated
Comment thread clang/test/Analysis/initializer.cpp

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NagyDonat I appreciate your patience on this PR. I've managed to find time to address your feedback. Let me know if you have more of course.

Comment thread clang/test/Analysis/initializer.cpp
Comment thread clang/lib/StaticAnalyzer/Core/RegionStore.cpp Outdated
Comment thread clang/lib/StaticAnalyzer/Core/RegionStore.cpp Outdated
Comment thread clang/lib/StaticAnalyzer/Core/RegionStore.cpp Outdated

@NagyDonat NagyDonat left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for the updates!

It's a bit unfortunate that test3Bis doesn't show the ideal behavior, but this commit is a clear step forward and there is no expectation to fix everything with one commit.

@mantognini
mantognini merged commit 8dd2846 into llvm:main Oct 2, 2025
9 checks passed
@marco-antognini-sonarsource
marco-antognini-sonarsource deleted the mb/bindArray branch October 2, 2025 12:14
mahesh-attarde pushed a commit to mahesh-attarde/llvm-project that referenced this pull request Oct 3, 2025
Fixes llvm#147686 by handling
symbolic values similarly to bindStruct and handling constant values.
The latter is actually more of a workaround: bindArray should not have
to deal with such constants.

CPP-6688
steakhal added a commit that referenced this pull request Jul 21, 2026
…r 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>
dyung pushed a commit to llvmbot/llvm-project that referenced this pull request Jul 22, 2026
…r array-to-pointer decay (llvm#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 llvm#178923 for issue llvm#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 llvm#147686 (fixed by llvm#153177, which
its own author noted was "more of a workaround") and llvm#178797 (fixed by
llvm#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 llvm#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 5b1fa37)
midhuncodes7 pushed a commit to midhuncodes7/llvm-project that referenced this pull request Jul 28, 2026
…r array-to-pointer decay (llvm#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 llvm#178923 for issue llvm#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 llvm#147686 (fixed by llvm#153177, which
its own author noted was "more of a workaround") and llvm#178797 (fixed by
llvm#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 llvm#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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:static analyzer clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

clang-tidy 20 crashes in RegionStoreManager

6 participants