[clang] 5ddae7a - [analyzer] Generalize field initializer resolution in RegionStore (#189361)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Jun 7 09:35:38 PDT 2026
Author: Endre Fülöp
Date: 2026-06-07T18:35:33+02:00
New Revision: 5ddae7ae330fdd9880ad512c986cbfb2faa26e1f
URL: https://github.com/llvm/llvm-project/commit/5ddae7ae330fdd9880ad512c986cbfb2faa26e1f
DIFF: https://github.com/llvm/llvm-project/commit/5ddae7ae330fdd9880ad512c986cbfb2faa26e1f.diff
LOG: [analyzer] Generalize field initializer resolution in RegionStore (#189361)
Replace the ad-hoc blocks in getBindingForField with a single helper
getConstantValFromInitializer that walks up the region chain to a
VarRegion, then walks down the InitListExpr (semantic form) by field
and element indices. Handles arbitrary nesting depth, including
multidimensional arrays of structs and structs containing arrays.
The same trust conditions apply: const-qualified types are always
trusted, and non-const globals are trusted when analyzing main().
For C++ structs with user-defined constructors, we conservatively
fall through to the symbolic path because the constructor body may
establish values that differ from the InitListExpr. Resolving
constructor-initialized values is left to a separate change.
Union initializers are handled by matching the accessed field against
the initialized member, accesses to inactive union members return
unknown.
This resolves false positives from security.ArrayBound on
sentinel-terminated struct arrays and fixes a FIXME where
CXXDefaultInitExpr in const arrays was not recognized.
---------
Co-authored-by: Donát Nagy <donat.nagy at ericsson.com>
Added:
clang/test/Analysis/array-of-structs-initializer.cpp
Modified:
clang/lib/StaticAnalyzer/Core/RegionStore.cpp
clang/test/Analysis/initialization.cpp
Removed:
################################################################################
diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
index a01295bbd2265..0f7e03ce50858 100644
--- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
+++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
@@ -586,6 +586,8 @@ class RegionStoreManager : public StoreManager {
std::optional<SVal>
getConstantValFromConstArrayInitializer(RegionBindingsConstRef B,
const ElementRegion *R);
+ std::optional<SVal> getConstantValFromInitializer(const FieldRegion *R,
+ bool IsMainAnalysis);
std::optional<SVal>
getSValFromInitListExpr(const InitListExpr *ILE,
const SmallVector<uint64_t, 2> &ConcreteOffsets,
@@ -2106,6 +2108,87 @@ SVal RegionStoreManager::getBindingForElement(RegionBindingsConstRef B,
return getBindingForFieldOrElementCommon(B, R, R->getElementType());
}
+std::optional<SVal>
+RegionStoreManager::getConstantValFromInitializer(const FieldRegion *R,
+ bool IsMainAnalysis) {
+ SmallVector<const SubRegion *, 4> Path;
+ const MemRegion *Cur = R;
+ while (Cur && !isa<VarRegion>(Cur)) {
+ if (isa<FieldRegion, ElementRegion>(Cur)) {
+ Path.push_back(cast<SubRegion>(Cur));
+ Cur = cast<SubRegion>(Cur)->getSuperRegion();
+ } else {
+ return std::nullopt;
+ }
+ }
+
+ const auto *VR = dyn_cast<VarRegion>(Cur);
+ if (!VR)
+ return std::nullopt;
+
+ const VarDecl *VD = VR->getDecl();
+ QualType LeafTy = R->getDecl()->getType();
+
+ bool TrustInit =
+ (VD->getType().isConstQualified() || LeafTy.isConstQualified() ||
+ (IsMainAnalysis && VD->hasGlobalStorage()));
+ if (!TrustInit)
+ return std::nullopt;
+
+ const Expr *Init = VD->getAnyInitializer();
+ if (!Init)
+ return std::nullopt;
+
+ const Expr *E = Init;
+ for (const SubRegion *SR : llvm::reverse(Path)) {
+ // If E is not an InitListExpr, it may be an ImplicitValueInitExpr
+ // representing zero-initialization of this aggregate element.
+ if (isa<ImplicitValueInitExpr>(E))
+ return svalBuilder.makeZeroVal(LeafTy);
+
+ const auto *ILE = dyn_cast<InitListExpr>(E);
+ if (!ILE)
+ return std::nullopt;
+
+ if (const auto *FR = dyn_cast<FieldRegion>(SR)) {
+ if (ILE->getType()->isUnionType()) {
+ // A union InitListExpr has one init for one member. We can only
+ // resolve if the accessed field matches the initialized member.
+ const FieldDecl *InitField = ILE->getInitializedFieldInUnion();
+ if (InitField != FR->getDecl())
+ return std::nullopt;
+ if (ILE->getNumInits() == 0)
+ return svalBuilder.makeZeroVal(LeafTy);
+ E = ILE->getInit(0);
+ } else {
+ unsigned Idx = FR->getDecl()->getFieldIndex();
+ if (Idx >= ILE->getNumInits())
+ return std::nullopt;
+ E = ILE->getInit(Idx);
+ }
+ continue;
+ }
+
+ if (const auto *ER = dyn_cast<ElementRegion>(SR)) {
+ auto CI = ER->getIndex().getAs<nonloc::ConcreteInt>();
+ if (!CI)
+ return std::nullopt;
+ uint64_t Idx = CI->getValue()->getZExtValue();
+ if (Idx < ILE->getNumInits())
+ E = ILE->getInit(Idx);
+ else if (const Expr *Filler = ILE->getArrayFiller())
+ E = Filler;
+ else
+ return std::nullopt;
+ continue;
+ }
+
+ return std::nullopt;
+ }
+
+ return svalBuilder.getConstantVal(E);
+}
+
SVal RegionStoreManager::getBindingForField(RegionBindingsConstRef B,
const FieldRegion* R) {
@@ -2113,30 +2196,12 @@ SVal RegionStoreManager::getBindingForField(RegionBindingsConstRef B,
if (const std::optional<SVal> &V = B.getDirectBinding(R))
return *V;
- // If the containing record was initialized, try to get its constant value.
- const FieldDecl *FD = R->getDecl();
- QualType Ty = FD->getType();
- const MemRegion* superR = R->getSuperRegion();
- if (const auto *VR = dyn_cast<VarRegion>(superR)) {
- const VarDecl *VD = VR->getDecl();
- QualType RecordVarTy = VD->getType();
- unsigned Index = FD->getFieldIndex();
- // Either the record variable or the field has an initializer that we can
- // trust. We trust initializers of constants and, additionally, respect
- // initializers of globals when analyzing main().
- if (RecordVarTy.isConstQualified() || Ty.isConstQualified() ||
- (B.isMainAnalysis() && VD->hasGlobalStorage()))
- if (const Expr *Init = VD->getAnyInitializer())
- if (const auto *InitList = dyn_cast<InitListExpr>(Init)) {
- if (Index < InitList->getNumInits()) {
- if (const Expr *FieldInit = InitList->getInit(Index))
- if (std::optional<SVal> V = svalBuilder.getConstantVal(FieldInit))
- return *V;
- } else {
- return svalBuilder.makeZeroVal(Ty);
- }
- }
- }
+ // Try to resolve the field value from the variable's initializer.
+ if (std::optional<SVal> V =
+ getConstantValFromInitializer(R, B.isMainAnalysis()))
+ return *V;
+
+ QualType Ty = R->getDecl()->getType();
// Handle the case where we are accessing into a larger scalar object.
// For example, this handles:
diff --git a/clang/test/Analysis/array-of-structs-initializer.cpp b/clang/test/Analysis/array-of-structs-initializer.cpp
new file mode 100644
index 0000000000000..fe77623310023
--- /dev/null
+++ b/clang/test/Analysis/array-of-structs-initializer.cpp
@@ -0,0 +1,206 @@
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -analyzer-checker=core,debug.ExprInspection -verify=expected,c -xc %s
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu -analyzer-checker=core,debug.ExprInspection -verify=expected,cpp -xc++ -DCPP -std=c++14 %s
+
+void clang_analyzer_value(int);
+
+struct CStruct {
+ int a;
+};
+
+struct CStruct nonconst_c_struct_array[1] = {
+ {11},
+};
+
+void use_nonconst_struct_array_c(void) {
+ clang_analyzer_value(nonconst_c_struct_array->a); // expected-warning {{32s:{ [-2147483648, 2147483647] }}}
+}
+
+const struct CStruct const_c_struct_array[1] = { {22} };
+
+void use_const_struct_array_c(void) {
+ clang_analyzer_value(const_c_struct_array->a); // expected-warning {{22}}
+}
+
+#ifdef CPP
+struct CPPStruct {
+ int a = 33;
+};
+
+CPPStruct nonconst_cpp_struct_array[1] = {};
+const CPPStruct const_cpp_struct_array[1] = {};
+
+struct CPPStructWithUserCtor {
+ int a = 44;
+ CPPStructWithUserCtor(): a(55) {}
+};
+
+CPPStructWithUserCtor nonconst_cpp_struct_wctor_array[1] = {};
+
+void use_nonconst_struct_array_cpp(void) {
+ clang_analyzer_value(nonconst_cpp_struct_array->a); // cpp-warning {{32s:{ [-2147483648, 2147483647] }}}
+}
+
+const CPPStructWithUserCtor const_cpp_struct_wctor_array[1] = {};
+#endif
+
+int main(int argc, char **argv) {
+ // FIXME: In C++ mode, IsMainAnalysis is false because global constructors
+ // may run before main(), so the initializer for non-const globals are not
+ // considered. In C mode this correctly resolves to 11.
+ clang_analyzer_value(nonconst_c_struct_array->a); // c-warning {{11}} cpp-warning {{32s:{ [-2147483648, 2147483647] }}}
+
+#ifdef CPP
+ // Default member initialization is resolved from the initializer.
+ clang_analyzer_value(const_cpp_struct_array->a); // cpp-warning {{33}}
+
+ // FIXME: In C++ mode, non-const globals are not trusted because global
+ // constructors may run before main(). This should be 33.
+ clang_analyzer_value(nonconst_cpp_struct_array->a); // cpp-warning {{32s:{ [-2147483648, 2147483647] }}}
+
+ // FIXME: We do not model constructor calls in initializers. This should
+ // be 55 (from the constructor's initializer list).
+ clang_analyzer_value(const_cpp_struct_wctor_array->a); // cpp-warning {{32s:{ [-2147483648, 2147483647] }}}
+
+ // FIXME: Non-const global in C++ mode, and also requires modeling
+ // constructor calls. This should be 55.
+ clang_analyzer_value(nonconst_cpp_struct_wctor_array->a); // cpp-warning {{32s:{ [-2147483648, 2147483647] }}}
+#endif
+}
+
+struct Inner {
+ int x;
+ int y;
+};
+
+struct Outer {
+ struct Inner arr[2];
+ int z;
+};
+
+const struct Outer nested = {{{10, 20}, {30, 40}}, 50};
+
+void test_nested_struct_array_field(void) {
+ clang_analyzer_value(nested.arr[0].x); // expected-warning {{10}}
+ clang_analyzer_value(nested.arr[0].y); // expected-warning {{20}}
+ clang_analyzer_value(nested.arr[1].x); // expected-warning {{30}}
+ clang_analyzer_value(nested.arr[1].y); // expected-warning {{40}}
+ clang_analyzer_value(nested.z); // expected-warning {{50}}
+}
+
+// Elided braces: see [dcl.init.aggr] p15.
+
+const struct Outer nested_elided = {10, 20, 30, 40, 50};
+
+void test_nested_elided_braces(void) {
+ clang_analyzer_value(nested_elided.arr[0].x); // expected-warning {{10}}
+ clang_analyzer_value(nested_elided.arr[0].y); // expected-warning {{20}}
+ clang_analyzer_value(nested_elided.arr[1].x); // expected-warning {{30}}
+ clang_analyzer_value(nested_elided.arr[1].y); // expected-warning {{40}}
+ clang_analyzer_value(nested_elided.z); // expected-warning {{50}}
+}
+
+
+const struct CStruct matrix[2][2] = {{{1}, {2}}, {{3}, {4}}};
+
+void test_2d_array_of_structs(void) {
+ clang_analyzer_value(matrix[0][0].a); // expected-warning {{1}}
+ clang_analyzer_value(matrix[0][1].a); // expected-warning {{2}}
+ clang_analyzer_value(matrix[1][0].a); // expected-warning {{3}}
+ clang_analyzer_value(matrix[1][1].a); // expected-warning {{4}}
+}
+
+const struct CStruct matrix_elided[2][2] = {1, 2, 3, 4};
+
+void test_matrix_elided_braces(void) {
+ clang_analyzer_value(matrix_elided[0][0].a); // expected-warning {{1}}
+ clang_analyzer_value(matrix_elided[0][1].a); // expected-warning {{2}}
+ clang_analyzer_value(matrix_elided[1][0].a); // expected-warning {{3}}
+ clang_analyzer_value(matrix_elided[1][1].a); // expected-warning {{4}}
+}
+
+
+const struct Inner partial_arr[3] = {{100, 200}};
+
+void test_array_filler_zero_init(void) {
+ clang_analyzer_value(partial_arr[0].x); // expected-warning {{100}}
+ clang_analyzer_value(partial_arr[0].y); // expected-warning {{200}}
+ clang_analyzer_value(partial_arr[1].x); // expected-warning {{0}}
+ clang_analyzer_value(partial_arr[2].y); // expected-warning {{0}}
+}
+
+union IntOrChar {
+ int i;
+ char c;
+};
+
+const union IntOrChar u_int = {42};
+
+void test_union_active_member(void) {
+ clang_analyzer_value(u_int.i); // expected-warning {{42}}
+}
+
+void test_union_inactive_member(void) {
+ clang_analyzer_value(u_int.c); // expected-warning {{8s:{ [-128, 127] }}}
+}
+
+const union IntOrChar u_char = {.c = 'x'};
+
+void test_union_designated_active(void) {
+ clang_analyzer_value(u_char.c); // expected-warning {{120}}
+}
+
+void test_union_designated_inactive(void) {
+ clang_analyzer_value(u_char.i); // expected-warning {{32s:{ [-2147483648, 2147483647] }}}
+}
+
+struct HasUnion {
+ union IntOrChar u;
+ int after;
+};
+
+const struct HasUnion hu = {{99}, 7};
+
+void test_union_in_struct(void) {
+ clang_analyzer_value(hu.u.i); // expected-warning {{99}}
+ clang_analyzer_value(hu.u.c); // expected-warning {{8s:{ [-128, 127] }}}
+ clang_analyzer_value(hu.after); // expected-warning {{7}}
+}
+
+// Empty initializer zero-initializes the first member.
+const union IntOrChar u_empty = {};
+
+void test_union_empty_init(void) {
+ clang_analyzer_value(u_empty.i); // expected-warning {{0}}
+ clang_analyzer_value(u_empty.c); // expected-warning {{8s:{ [-128, 127] }}}
+}
+
+const union IntOrChar u_arr[2] = {{10}, {.c = 'y'}};
+
+void test_union_array(void) {
+ clang_analyzer_value(u_arr[0].i); // expected-warning {{10}}
+ clang_analyzer_value(u_arr[0].c); // expected-warning {{8s:{ [-128, 127] }}}
+ clang_analyzer_value(u_arr[1].c); // expected-warning {{121}}
+ clang_analyzer_value(u_arr[1].i); // expected-warning {{32s:{ [-2147483648, 2147483647] }}}
+}
+
+struct Tagged {
+ int tag;
+ union {
+ int ival;
+ char cval;
+ } payload;
+};
+
+const struct Tagged tagged_arr[2] = {
+ {1, {.ival = 100}},
+ {2, {.cval = 'Z'}},
+};
+
+void test_struct_union_array(void) {
+ clang_analyzer_value(tagged_arr[0].tag); // expected-warning {{1}}
+ clang_analyzer_value(tagged_arr[0].payload.ival); // expected-warning {{100}}
+ clang_analyzer_value(tagged_arr[0].payload.cval); // expected-warning {{8s:{ [-128, 127] }}}
+ clang_analyzer_value(tagged_arr[1].tag); // expected-warning {{2}}
+ clang_analyzer_value(tagged_arr[1].payload.cval); // expected-warning {{90}}
+ clang_analyzer_value(tagged_arr[1].payload.ival); // expected-warning {{32s:{ [-2147483648, 2147483647] }}}
+}
diff --git a/clang/test/Analysis/initialization.cpp b/clang/test/Analysis/initialization.cpp
index 100cd89431e94..2806a085bcde3 100644
--- a/clang/test/Analysis/initialization.cpp
+++ b/clang/test/Analysis/initialization.cpp
@@ -10,8 +10,7 @@ struct S {
S const sarr[2] = {};
void definit() {
int i = 1;
- // FIXME: Should recognize that it is 3.
- clang_analyzer_eval(sarr[i].a); // expected-warning{{UNKNOWN}}
+ clang_analyzer_dump(sarr[i].a); // expected-warning{{3 S32b}}
}
int const glob_arr1[3] = {};
More information about the cfe-commits
mailing list