[analyzer] Harden RegionStoreManager::bindArray - #153177
Conversation
Fixes llvm#147686 by completing handling of literals and handling symbolic values similarly to bindStruct. CPP-6688
|
@llvm/pr-subscribers-clang-static-analyzer-1 @llvm/pr-subscribers-clang Author: Marco Borgeaud (marco-antognini-sonarsource) ChangesFixes #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:
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
|
|
I recall that I already reviewed this downstream. I'd recommend to mark the lines in the test with the All in all, not crashing is strictly better than crashing - in almost all scenario. |
NagyDonat
left a comment
There was a problem hiding this comment.
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... ?).
No need to unwrap constants. This is a workaround to not crash -- not a proper fix.
marco-antognini-sonarsource
left a comment
There was a problem hiding this comment.
@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.
NagyDonat
left a comment
There was a problem hiding this comment.
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.
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
…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>
…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)
…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>
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