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