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