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

Reply via email to