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?= <[email protected]> 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 <[email protected]> --- 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?= <[email protected]> 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?= <[email protected]> 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?= <[email protected]> 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?= <[email protected]> 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?= <[email protected]> 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?= <[email protected]> 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 <[email protected]> --- 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?= <[email protected]> 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?= <[email protected]> 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?= <[email protected]> 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?= <[email protected]> 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?= <[email protected]> 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] = {}; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
