[clang] [analyzer] Generalize field initializer resolution in RegionStore (PR #189361)
Endre Fülöp via cfe-commits
cfe-commits at lists.llvm.org
Thu Jun 4 05:26:19 PDT 2026
https://github.com/gamesh411 updated https://github.com/llvm/llvm-project/pull/189361
>From 91f222f8f9b26eaee34416f90033819421a76d94 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 27 Mar 2026 12:18:03 +0100
Subject: [PATCH 01/12] [analyzer] Resolve initializers for fields of struct
array elements in RegionStore
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Extend getBindingForField() to handle the FieldRegion(ElementRegion(VarRegion))
hierarchy when accessing a field of a struct inside an array.
Previously, only FieldRegion(VarRegion) was handled (i.e. fields of a single
struct variable), so the initializer of struct array elements was never
considered.
The same trust conditions apply: const-qualified types are always trusted,
and non-const globals are trusted when analyzing main(). For C++ structs
with in-class (default member) initializers, we conservatively fall through
to the symbolic path to avoid incorrectly returning zero. Actually resolving
default member initializer values is left to a separate change.
This resolves false positives from security.ArrayBound on sentinel-terminated
struct arrays.
Co-authored-by: Donát Nagy <donat.nagy at ericsson.com>
---
clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 40 ++++++++++++++++
...-structs-initializer-not-visible-in-main.c | 47 +++++++++++++++++++
...tructs-initializer-not-visible-in-main.cpp | 36 ++++++++++++++
3 files changed, 123 insertions(+)
create mode 100644 clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.c
create mode 100644 clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.cpp
diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
index 6ec66298e8c45..eac87355f8401 100644
--- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
+++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
@@ -2145,6 +2145,46 @@ SVal RegionStoreManager::getBindingForField(RegionBindingsConstRef B,
}
}
+ // In case of array of structs, the super-region is not directly a VarRegion,
+ // instead there is another layer of ElementRegion in between them, i.e.:
+ // FieldRegion(ElementRegion(VarRegion)).
+ if (const auto *ER = dyn_cast<ElementRegion>(superR)) {
+ if (const auto *VR = dyn_cast<VarRegion>(ER->getSuperRegion())) {
+ const VarDecl *VD = VR->getDecl();
+ QualType ArrayTy = VD->getType();
+ unsigned FieldIdx = FD->getFieldIndex();
+
+ if (ArrayTy.isConstQualified() || Ty.isConstQualified() ||
+ (B.isMainAnalysis() && VD->hasGlobalStorage())) {
+ if (const Expr *Init = VD->getAnyInitializer())
+ if (const auto *InitList = dyn_cast<InitListExpr>(Init))
+ if (const auto CI = ER->getIndex().getAs<nonloc::ConcreteInt>()) {
+ uint64_t ElemIdx = CI->getValue()->getZExtValue();
+
+ const InitListExpr *StructILE = nullptr;
+ if (ElemIdx < InitList->getNumInits()) {
+ StructILE = dyn_cast<InitListExpr>(InitList->getInit(ElemIdx));
+ } else if (!FD->hasInClassInitializer()) {
+ // This is zero initialization, because there is no explicit
+ // initializer for this index and there is no default member
+ // initializer in-class either.
+ return svalBuilder.makeZeroVal(Ty);
+ }
+ if (StructILE) {
+ if (FieldIdx < StructILE->getNumInits()) {
+ if (const Expr *FieldInit = StructILE->getInit(FieldIdx))
+ if (std::optional<SVal> V =
+ svalBuilder.getConstantVal(FieldInit))
+ return *V;
+ } else if (!FD->hasInClassInitializer()) {
+ return svalBuilder.makeZeroVal(Ty);
+ }
+ }
+ }
+ }
+ }
+ }
+
// Handle the case where we are accessing into a larger scalar object.
// For example, this handles:
// struct header {
diff --git a/clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.c b/clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.c
new file mode 100644
index 0000000000000..652a44d2815e9
--- /dev/null
+++ b/clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.c
@@ -0,0 +1,47 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s
+
+// This test verifies that the analyzer can 'see' the initializer of an
+// array of structs, covering const, non-const, and main() vs non-main cases.
+
+void clang_analyzer_warnIfReached(void);
+
+struct S {
+ int a;
+};
+
+// Non-const struct array: initializer should be visible in main().
+struct S struct_array[1] = {
+ {11},
+};
+
+int main(int argc, char **argv) {
+ if (struct_array->a == 11) {
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+ } else {
+ clang_analyzer_warnIfReached(); // unreachable
+ }
+}
+
+// Const struct array: initializer should be visible in any function.
+const struct S struct_array_const[1] = { {44} };
+
+void use_struct_array_const(void) {
+ if (struct_array_const->a == 44) {
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+ } else {
+ clang_analyzer_warnIfReached(); // unreachable
+ }
+}
+
+// Non-const struct array in non-main: initializer must NOT be trusted.
+struct S struct_array_nonconst[1] = { {55} };
+
+void use_struct_array_nonconst(void) {
+ if (struct_array_nonconst->a == 55) {
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+ } else {
+ // This is intentionally reachable, because this is a non-const array which
+ // may have been changed before the call to this function.
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+ }
+}
diff --git a/clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.cpp b/clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.cpp
new file mode 100644
index 0000000000000..6053a24a63f48
--- /dev/null
+++ b/clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.cpp
@@ -0,0 +1,36 @@
+// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core,debug.ExprInspection -verify %s
+
+// This test verifies that the analyzer does not incorrectly assume zero
+// for fields with in-class (default member) initializers when accessing
+// elements of a struct array.
+
+void clang_analyzer_warnIfReached(void);
+
+struct S {
+ int a = 3;
+};
+
+// Non-const array in main: the analyzer must not assume zero for 'a',
+// because it has a default member initializer.
+S sarr_nonconst[2] = {};
+
+int main(int argc, char **argv) {
+ // FIXME: Should recognize that it is 3 (from the default member initializer).
+ if (sarr_nonconst[0].a == 3) {
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+ } else {
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+ }
+}
+
+// Const array in non-main: the analyzer resolves the default member
+// initializer correctly through the lazy binding path.
+const S sarr_const[2] = {};
+
+void use_const(void) {
+ if (sarr_const[0].a == 3) {
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+ } else {
+ clang_analyzer_warnIfReached(); // unreachable
+ }
+}
>From 6e992acf79af456a5c8c1c9b7acb1f8a05b7cadc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Tue, 31 Mar 2026 14:11:14 +0200
Subject: [PATCH 02/12] reorganize test cases
move into one file with 2 runlines to test c and cpp
use clang_analyzer_value instead of warnIfReached
use 2 prefixes
---
...-structs-initializer-not-visible-in-main.c | 47 ------------
...tructs-initializer-not-visible-in-main.cpp | 36 ----------
.../Analysis/array-of-structs-initializer.cpp | 71 +++++++++++++++++++
3 files changed, 71 insertions(+), 83 deletions(-)
delete mode 100644 clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.c
delete mode 100644 clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.cpp
create mode 100644 clang/test/Analysis/array-of-structs-initializer.cpp
diff --git a/clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.c b/clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.c
deleted file mode 100644
index 652a44d2815e9..0000000000000
--- a/clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.c
+++ /dev/null
@@ -1,47 +0,0 @@
-// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s
-
-// This test verifies that the analyzer can 'see' the initializer of an
-// array of structs, covering const, non-const, and main() vs non-main cases.
-
-void clang_analyzer_warnIfReached(void);
-
-struct S {
- int a;
-};
-
-// Non-const struct array: initializer should be visible in main().
-struct S struct_array[1] = {
- {11},
-};
-
-int main(int argc, char **argv) {
- if (struct_array->a == 11) {
- clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
- } else {
- clang_analyzer_warnIfReached(); // unreachable
- }
-}
-
-// Const struct array: initializer should be visible in any function.
-const struct S struct_array_const[1] = { {44} };
-
-void use_struct_array_const(void) {
- if (struct_array_const->a == 44) {
- clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
- } else {
- clang_analyzer_warnIfReached(); // unreachable
- }
-}
-
-// Non-const struct array in non-main: initializer must NOT be trusted.
-struct S struct_array_nonconst[1] = { {55} };
-
-void use_struct_array_nonconst(void) {
- if (struct_array_nonconst->a == 55) {
- clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
- } else {
- // This is intentionally reachable, because this is a non-const array which
- // may have been changed before the call to this function.
- clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
- }
-}
diff --git a/clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.cpp b/clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.cpp
deleted file mode 100644
index 6053a24a63f48..0000000000000
--- a/clang/test/Analysis/array-of-structs-initializer-not-visible-in-main.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core,debug.ExprInspection -verify %s
-
-// This test verifies that the analyzer does not incorrectly assume zero
-// for fields with in-class (default member) initializers when accessing
-// elements of a struct array.
-
-void clang_analyzer_warnIfReached(void);
-
-struct S {
- int a = 3;
-};
-
-// Non-const array in main: the analyzer must not assume zero for 'a',
-// because it has a default member initializer.
-S sarr_nonconst[2] = {};
-
-int main(int argc, char **argv) {
- // FIXME: Should recognize that it is 3 (from the default member initializer).
- if (sarr_nonconst[0].a == 3) {
- clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
- } else {
- clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
- }
-}
-
-// Const array in non-main: the analyzer resolves the default member
-// initializer correctly through the lazy binding path.
-const S sarr_const[2] = {};
-
-void use_const(void) {
- if (sarr_const[0].a == 3) {
- clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
- } else {
- clang_analyzer_warnIfReached(); // unreachable
- }
-}
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..1e048ecb63ee9
--- /dev/null
+++ b/clang/test/Analysis/array-of-structs-initializer.cpp
@@ -0,0 +1,71 @@
+// RUN: %clang_analyze_cc1 -xc -analyzer-checker=core,debug.ExprInspection -verify=expected,c %s
+// RUN: %clang_analyze_cc1 -xc++ -DCPP -std=c++14 -analyzer-checker=core,debug.ExprInspection -verify=expected,cpp %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); // expected-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
+ // FIXME: Once we model default member initialization, this should be 33.
+ clang_analyzer_value(const_cpp_struct_array->a); // cpp-warning {{32s:{ [-2147483648, 2147483647] }}}
+
+ // FIXME: Even if we modeled default member initialization, because of C++
+ // mode, initializers of non-const globals are not considered. If they were,
+ // this should be 33.
+ clang_analyzer_value(nonconst_cpp_struct_array->a); // cpp-warning {{32s:{ [-2147483648, 2147483647] }}}
+
+ // FIXME: Once we model constructors for global arrays, this should be 55.
+ clang_analyzer_value(const_cpp_struct_wctor_array->a); // cpp-warning {{32s:{ [-2147483648, 2147483647] }}}
+
+ // FIXME: Even if we modeled default member initialization, because of C++
+ // mode, non-const globals' initializers are not considered. If they were,
+ // the ctor's initializer list has precedence over the default member
+ // initializer, so the correct value should be 55.
+ clang_analyzer_value(nonconst_cpp_struct_wctor_array->a); // cpp-warning {{32s:{ [-2147483648, 2147483647] }}}
+#endif
+}
+
>From 7f48e4a38c910cf6d30d65ff6e1a765426acd724 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Tue, 31 Mar 2026 15:26:42 +0200
Subject: [PATCH 03/12] rename one partially misleading expectation
---
clang/test/Analysis/array-of-structs-initializer.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/test/Analysis/array-of-structs-initializer.cpp b/clang/test/Analysis/array-of-structs-initializer.cpp
index 1e048ecb63ee9..519f2029800d3 100644
--- a/clang/test/Analysis/array-of-structs-initializer.cpp
+++ b/clang/test/Analysis/array-of-structs-initializer.cpp
@@ -37,7 +37,7 @@ struct CPPStructWithUserCtor {
CPPStructWithUserCtor nonconst_cpp_struct_wctor_array[1] = {};
void use_nonconst_struct_array_cpp(void) {
- clang_analyzer_value(nonconst_cpp_struct_array->a); // expected-warning {{32s:{ [-2147483648, 2147483647] }}}
+ clang_analyzer_value(nonconst_cpp_struct_array->a); // cpp-warning {{32s:{ [-2147483648, 2147483647] }}}
}
const CPPStructWithUserCtor const_cpp_struct_wctor_array[1] = {};
>From 2bb082c994d3fc5e670ae4c3088fd6d985a33015 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Tue, 26 May 2026 19:58:06 +0200
Subject: [PATCH 04/12] Revert original implementation in favor of generalized
approach
Reverts the ad-hoc FieldRegion(ElementRegion(VarRegion)) handling
in getBindingForField, to be replaced by a single helper that
handles arbitrary nesting depth.
---
clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 40 -----------
.../Analysis/array-of-structs-initializer.cpp | 71 -------------------
2 files changed, 111 deletions(-)
delete mode 100644 clang/test/Analysis/array-of-structs-initializer.cpp
diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
index eac87355f8401..6ec66298e8c45 100644
--- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
+++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
@@ -2145,46 +2145,6 @@ SVal RegionStoreManager::getBindingForField(RegionBindingsConstRef B,
}
}
- // In case of array of structs, the super-region is not directly a VarRegion,
- // instead there is another layer of ElementRegion in between them, i.e.:
- // FieldRegion(ElementRegion(VarRegion)).
- if (const auto *ER = dyn_cast<ElementRegion>(superR)) {
- if (const auto *VR = dyn_cast<VarRegion>(ER->getSuperRegion())) {
- const VarDecl *VD = VR->getDecl();
- QualType ArrayTy = VD->getType();
- unsigned FieldIdx = FD->getFieldIndex();
-
- if (ArrayTy.isConstQualified() || Ty.isConstQualified() ||
- (B.isMainAnalysis() && VD->hasGlobalStorage())) {
- if (const Expr *Init = VD->getAnyInitializer())
- if (const auto *InitList = dyn_cast<InitListExpr>(Init))
- if (const auto CI = ER->getIndex().getAs<nonloc::ConcreteInt>()) {
- uint64_t ElemIdx = CI->getValue()->getZExtValue();
-
- const InitListExpr *StructILE = nullptr;
- if (ElemIdx < InitList->getNumInits()) {
- StructILE = dyn_cast<InitListExpr>(InitList->getInit(ElemIdx));
- } else if (!FD->hasInClassInitializer()) {
- // This is zero initialization, because there is no explicit
- // initializer for this index and there is no default member
- // initializer in-class either.
- return svalBuilder.makeZeroVal(Ty);
- }
- if (StructILE) {
- if (FieldIdx < StructILE->getNumInits()) {
- if (const Expr *FieldInit = StructILE->getInit(FieldIdx))
- if (std::optional<SVal> V =
- svalBuilder.getConstantVal(FieldInit))
- return *V;
- } else if (!FD->hasInClassInitializer()) {
- return svalBuilder.makeZeroVal(Ty);
- }
- }
- }
- }
- }
- }
-
// Handle the case where we are accessing into a larger scalar object.
// For example, this handles:
// struct header {
diff --git a/clang/test/Analysis/array-of-structs-initializer.cpp b/clang/test/Analysis/array-of-structs-initializer.cpp
deleted file mode 100644
index 519f2029800d3..0000000000000
--- a/clang/test/Analysis/array-of-structs-initializer.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-// RUN: %clang_analyze_cc1 -xc -analyzer-checker=core,debug.ExprInspection -verify=expected,c %s
-// RUN: %clang_analyze_cc1 -xc++ -DCPP -std=c++14 -analyzer-checker=core,debug.ExprInspection -verify=expected,cpp %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
- // FIXME: Once we model default member initialization, this should be 33.
- clang_analyzer_value(const_cpp_struct_array->a); // cpp-warning {{32s:{ [-2147483648, 2147483647] }}}
-
- // FIXME: Even if we modeled default member initialization, because of C++
- // mode, initializers of non-const globals are not considered. If they were,
- // this should be 33.
- clang_analyzer_value(nonconst_cpp_struct_array->a); // cpp-warning {{32s:{ [-2147483648, 2147483647] }}}
-
- // FIXME: Once we model constructors for global arrays, this should be 55.
- clang_analyzer_value(const_cpp_struct_wctor_array->a); // cpp-warning {{32s:{ [-2147483648, 2147483647] }}}
-
- // FIXME: Even if we modeled default member initialization, because of C++
- // mode, non-const globals' initializers are not considered. If they were,
- // the ctor's initializer list has precedence over the default member
- // initializer, so the correct value should be 55.
- clang_analyzer_value(nonconst_cpp_struct_wctor_array->a); // cpp-warning {{32s:{ [-2147483648, 2147483647] }}}
-#endif
-}
-
>From d852879c12dbf2e2223d0902b438073c9569611b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Tue, 26 May 2026 19:58:23 +0200
Subject: [PATCH 05/12] [analyzer] Generalize field initializer resolution in
RegionStore
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.
Also resolves two FIXMEs where CXXDefaultInitExpr in const arrays
was not recognized.
---
clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 100 ++++++++++++----
.../Analysis/array-of-structs-initializer.cpp | 107 ++++++++++++++++++
clang/test/Analysis/initialization.cpp | 3 +-
3 files changed, 184 insertions(+), 26 deletions(-)
create mode 100644 clang/test/Analysis/array-of-structs-initializer.cpp
diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
index 6ec66298e8c45..3ebce9746f502 100644
--- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
+++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
@@ -585,6 +585,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,
@@ -2113,6 +2115,74 @@ 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_or_null<VarRegion>(Cur);
+ if (!VR)
+ return std::nullopt;
+
+ const VarDecl *VD = VR->getDecl();
+ QualType LeafTy = R->getDecl()->getType();
+
+ // We trust initializers of constants and globals when analyzing main().
+ if (!VD->getType().isConstQualified() && !LeafTy.isConstQualified() &&
+ (!IsMainAnalysis || !VD->hasGlobalStorage()))
+ return std::nullopt;
+
+ const Expr *Init = VD->getAnyInitializer();
+ if (!Init)
+ return std::nullopt;
+
+ const Expr *E = Init;
+ for (const SubRegion *SR : llvm::reverse(Path)) {
+ const auto *ILE = dyn_cast<InitListExpr>(E);
+ if (!ILE)
+ return isa<ImplicitValueInitExpr>(E)
+ ? std::optional<SVal>(svalBuilder.makeZeroVal(LeafTy))
+ : std::nullopt;
+
+ if (const auto *FR = dyn_cast<FieldRegion>(SR)) {
+ // FIXME: For unions, getFieldIndex() does not correspond to the
+ // InitListExpr index. Maybe we should check getInitializedFieldInUnion()
+ // and return nullopt if the accessed field is not the active member.
+ // Currently we may resolve the wrong value (but this is the pre-existing
+ // behavior).
+ unsigned Idx = FR->getDecl()->getFieldIndex();
+ if (Idx < ILE->getNumInits())
+ E = ILE->getInit(Idx);
+ else
+ return std::nullopt;
+ } else 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;
+ } else {
+ return std::nullopt;
+ }
+ }
+
+ return svalBuilder.getConstantVal(E);
+}
+
SVal RegionStoreManager::getBindingForField(RegionBindingsConstRef B,
const FieldRegion* R) {
@@ -2120,30 +2190,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..7de84ea3f9fd9
--- /dev/null
+++ b/clang/test/Analysis/array-of-structs-initializer.cpp
@@ -0,0 +1,107 @@
+// RUN: %clang_analyze_cc1 -xc -analyzer-checker=core,debug.ExprInspection -verify=expected,c %s
+// RUN: %clang_analyze_cc1 -xc++ -DCPP -std=c++14 -analyzer-checker=core,debug.ExprInspection -verify=expected,cpp %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}}
+}
+
+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 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}}
+}
+
diff --git a/clang/test/Analysis/initialization.cpp b/clang/test/Analysis/initialization.cpp
index d064dd6172d63..1cd19768018de 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_eval(sarr[i].a); // expected-warning{{TRUE}}
}
int const glob_arr1[3] = {};
>From 3aa27aada4ab462a65a71138a5f1f8a369385ab7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Wed, 27 May 2026 01:13:33 +0200
Subject: [PATCH 06/12] [analyzer] Handle unions in
getConstantValFromInitializer
A union InitListExpr has one init for one member. Use
getInitializedFieldInUnion() to resolve only the initialized field
and return nullopt for other members.
Handle empty initializers (getNumInits() == 0) by returning zero
for the designated member.
Fixes a pre-existing bug where designated init of a non-first union
member would incorrectly resolve accesses to other members.
---
clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 29 ++++---
.../Analysis/array-of-structs-initializer.cpp | 76 +++++++++++++++++++
2 files changed, 95 insertions(+), 10 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
index 3ebce9746f502..d0fd6b0851d7f 100644
--- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
+++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
@@ -2154,16 +2154,25 @@ RegionStoreManager::getConstantValFromInitializer(const FieldRegion *R,
: std::nullopt;
if (const auto *FR = dyn_cast<FieldRegion>(SR)) {
- // FIXME: For unions, getFieldIndex() does not correspond to the
- // InitListExpr index. Maybe we should check getInitializedFieldInUnion()
- // and return nullopt if the accessed field is not the active member.
- // Currently we may resolve the wrong value (but this is the pre-existing
- // behavior).
- unsigned Idx = FR->getDecl()->getFieldIndex();
- if (Idx < ILE->getNumInits())
- E = ILE->getInit(Idx);
- else
- return std::nullopt;
+ 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 && InitField == FR->getDecl()) {
+ if (ILE->getNumInits() > 0)
+ E = ILE->getInit(0);
+ else
+ return svalBuilder.makeZeroVal(LeafTy);
+ } else {
+ return std::nullopt;
+ }
+ } else {
+ unsigned Idx = FR->getDecl()->getFieldIndex();
+ if (Idx < ILE->getNumInits())
+ E = ILE->getInit(Idx);
+ else
+ return std::nullopt;
+ }
} else if (const auto *ER = dyn_cast<ElementRegion>(SR)) {
auto CI = ER->getIndex().getAs<nonloc::ConcreteInt>();
if (!CI)
diff --git a/clang/test/Analysis/array-of-structs-initializer.cpp b/clang/test/Analysis/array-of-structs-initializer.cpp
index 7de84ea3f9fd9..6f2a011fbac82 100644
--- a/clang/test/Analysis/array-of-structs-initializer.cpp
+++ b/clang/test/Analysis/array-of-structs-initializer.cpp
@@ -105,3 +105,79 @@ void test_array_filler_zero_init(void) {
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] }}}
+}
>From b912a11f2786e544c09cddb07d6afadfa3427d34 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <gamesh411 at gmail.com>
Date: Thu, 28 May 2026 19:17:45 +0200
Subject: [PATCH 07/12] Update clang/lib/StaticAnalyzer/Core/RegionStore.cpp
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Donát Nagy <donat.nagy at ericsson.com>
---
clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
index d0fd6b0851d7f..b09673d9d03ec 100644
--- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
+++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
@@ -2136,9 +2136,10 @@ RegionStoreManager::getConstantValFromInitializer(const FieldRegion *R,
const VarDecl *VD = VR->getDecl();
QualType LeafTy = R->getDecl()->getType();
- // We trust initializers of constants and globals when analyzing main().
- if (!VD->getType().isConstQualified() && !LeafTy.isConstQualified() &&
- (!IsMainAnalysis || !VD->hasGlobalStorage()))
+ bool TrustInit = (VD->getType().isConstQualified() ||
+ LeafTy.isConstQualified() ||
+ (IsMainAnalysis && VD->hasGlobalStorage()));
+ if (!TrustInit)
return std::nullopt;
const Expr *Init = VD->getAnyInitializer();
>From d2fb75c25a6d5d23e834bc104d438865de0b3221 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Thu, 28 May 2026 19:21:46 +0200
Subject: [PATCH 08/12] fix clang-format error
---
clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
index b09673d9d03ec..b5b76c2a9c031 100644
--- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
+++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
@@ -2136,9 +2136,9 @@ RegionStoreManager::getConstantValFromInitializer(const FieldRegion *R,
const VarDecl *VD = VR->getDecl();
QualType LeafTy = R->getDecl()->getType();
- bool TrustInit = (VD->getType().isConstQualified() ||
- LeafTy.isConstQualified() ||
- (IsMainAnalysis && VD->hasGlobalStorage()));
+ bool TrustInit =
+ (VD->getType().isConstQualified() || LeafTy.isConstQualified() ||
+ (IsMainAnalysis && VD->hasGlobalStorage()));
if (!TrustInit)
return std::nullopt;
>From 92d7e8d74df4a32b04473238ec11bd8597031cb8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Tue, 2 Jun 2026 04:31:15 +0200
Subject: [PATCH 09/12] apply review suggestions of @steakhal
---
clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 16 ++++++-----
.../Analysis/array-of-structs-initializer.cpp | 27 +++++++++++++++++--
2 files changed, 34 insertions(+), 9 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
index b5b76c2a9c031..c35fcc4d8a3a4 100644
--- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
+++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
@@ -586,7 +586,7 @@ class RegionStoreManager : public StoreManager {
getConstantValFromConstArrayInitializer(RegionBindingsConstRef B,
const ElementRegion *R);
std::optional<SVal> getConstantValFromInitializer(const FieldRegion *R,
- bool isMainAnalysis);
+ bool IsMainAnalysis);
std::optional<SVal>
getSValFromInitListExpr(const InitListExpr *ILE,
const SmallVector<uint64_t, 2> &ConcreteOffsets,
@@ -2129,7 +2129,7 @@ RegionStoreManager::getConstantValFromInitializer(const FieldRegion *R,
}
}
- const auto *VR = dyn_cast_or_null<VarRegion>(Cur);
+ const auto *VR = dyn_cast<VarRegion>(Cur);
if (!VR)
return std::nullopt;
@@ -2149,21 +2149,23 @@ RegionStoreManager::getConstantValFromInitializer(const FieldRegion *R,
const Expr *E = Init;
for (const SubRegion *SR : llvm::reverse(Path)) {
const auto *ILE = dyn_cast<InitListExpr>(E);
- if (!ILE)
+ // If E is not an InitListExpr, it may be an ImplicitValueInitExpr
+ // representing zero-initialization of this aggregate element.
+ if (!ILE) {
return isa<ImplicitValueInitExpr>(E)
? std::optional<SVal>(svalBuilder.makeZeroVal(LeafTy))
: 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 && InitField == FR->getDecl()) {
- if (ILE->getNumInits() > 0)
- E = ILE->getInit(0);
- else
+ if (InitField == FR->getDecl()) {
+ if (ILE->getNumInits() == 0)
return svalBuilder.makeZeroVal(LeafTy);
+ E = ILE->getInit(0);
} else {
return std::nullopt;
}
diff --git a/clang/test/Analysis/array-of-structs-initializer.cpp b/clang/test/Analysis/array-of-structs-initializer.cpp
index 6f2a011fbac82..fe77623310023 100644
--- a/clang/test/Analysis/array-of-structs-initializer.cpp
+++ b/clang/test/Analysis/array-of-structs-initializer.cpp
@@ -1,5 +1,5 @@
-// RUN: %clang_analyze_cc1 -xc -analyzer-checker=core,debug.ExprInspection -verify=expected,c %s
-// RUN: %clang_analyze_cc1 -xc++ -DCPP -std=c++14 -analyzer-checker=core,debug.ExprInspection -verify=expected,cpp %s
+// 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);
@@ -87,6 +87,19 @@ void test_nested_struct_array_field(void) {
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) {
@@ -96,6 +109,16 @@ void test_2d_array_of_structs(void) {
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) {
>From d05ac08ee003c51529a98e5084275298c4f8e1d7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Tue, 2 Jun 2026 05:11:02 +0200
Subject: [PATCH 10/12] refactor loop body: no fall-through between main cases
---
clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 25 ++++++++++---------
1 file changed, 13 insertions(+), 12 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
index c35fcc4d8a3a4..5ea97679d9d62 100644
--- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
+++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
@@ -2162,21 +2162,21 @@ RegionStoreManager::getConstantValFromInitializer(const FieldRegion *R,
// 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()) {
- if (ILE->getNumInits() == 0)
- return svalBuilder.makeZeroVal(LeafTy);
- E = ILE->getInit(0);
- } else {
+ 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())
- E = ILE->getInit(Idx);
- else
+ if (Idx >= ILE->getNumInits())
return std::nullopt;
+ E = ILE->getInit(Idx);
}
- } else if (const auto *ER = dyn_cast<ElementRegion>(SR)) {
+ continue;
+ }
+
+ if (const auto *ER = dyn_cast<ElementRegion>(SR)) {
auto CI = ER->getIndex().getAs<nonloc::ConcreteInt>();
if (!CI)
return std::nullopt;
@@ -2187,9 +2187,10 @@ RegionStoreManager::getConstantValFromInitializer(const FieldRegion *R,
E = Filler;
else
return std::nullopt;
- } else {
- return std::nullopt;
+ continue;
}
+
+ return std::nullopt;
}
return svalBuilder.getConstantVal(E);
>From 1080616f7c1f03f29a0d825abce445dfebbb03f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Thu, 4 Jun 2026 13:42:50 +0200
Subject: [PATCH 11/12] untangle the 2 InitList sub-cases
---
clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
index 5ea97679d9d62..a8eb5691affdd 100644
--- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
+++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
@@ -2148,14 +2148,14 @@ RegionStoreManager::getConstantValFromInitializer(const FieldRegion *R,
const Expr *E = Init;
for (const SubRegion *SR : llvm::reverse(Path)) {
- const auto *ILE = dyn_cast<InitListExpr>(E);
// If E is not an InitListExpr, it may be an ImplicitValueInitExpr
// representing zero-initialization of this aggregate element.
- if (!ILE) {
- return isa<ImplicitValueInitExpr>(E)
- ? std::optional<SVal>(svalBuilder.makeZeroVal(LeafTy))
- : std::nullopt;
- }
+ 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()) {
>From 82dd4cfede94b721bb3e6c90ececc273b67fbc77 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Thu, 4 Jun 2026 14:25:41 +0200
Subject: [PATCH 12/12] sharpen the test case
---
clang/test/Analysis/initialization.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/test/Analysis/initialization.cpp b/clang/test/Analysis/initialization.cpp
index 1cd19768018de..0fba3a278524c 100644
--- a/clang/test/Analysis/initialization.cpp
+++ b/clang/test/Analysis/initialization.cpp
@@ -10,7 +10,7 @@ struct S {
S const sarr[2] = {};
void definit() {
int i = 1;
- clang_analyzer_eval(sarr[i].a); // expected-warning{{TRUE}}
+ clang_analyzer_dump(sarr[i].a); // expected-warning{{3 S32b}}
}
int const glob_arr1[3] = {};
More information about the cfe-commits
mailing list