[clang] [clang][analyzer] Preserve symbolic-offset regions across pointer casts (PR #221213)

Radovan Božić via cfe-commits cfe-commits at lists.llvm.org
Fri Sep 4 05:28:12 PDT 2026


https://github.com/bozicrHT created https://github.com/llvm/llvm-project/pull/221213

`castRegion()` returns `nullopt` for `ElementRegion`s with symbolic indices, causing pointer casts to become `UnknownVal` and lose nullness information. Preserve the original region as a zero-offset view of the destination type, while stripping redundant cast views.

Update tests for symbolic pointer casts.


Fixes #220281 

>From 9238bc2b15c782fc511a7a292572cafa63b16ca0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Radovan=20Bo=C5=BEi=C4=87?= <radovan.bozic at htecgroup.com>
Date: Fri, 4 Sep 2026 14:19:17 +0200
Subject: [PATCH] [clang][analyzer] Preserve symbolic-offset regions across
 pointer casts

When an ElementRegion has a symbolic index, `castRegion()` cannot
compute a concrete byte offset and returns `nullopt`. Preserve the
original region as a zero-offset view of the destination type and strip
redundant cast views.
---
 clang/lib/StaticAnalyzer/Core/Store.cpp     | 11 ++++--
 clang/test/Analysis/pr22954.c               |  5 ++-
 clang/test/Analysis/symbolic-pointer-cast.c | 42 +++++++++++++++++++++
 3 files changed, 52 insertions(+), 6 deletions(-)
 create mode 100644 clang/test/Analysis/symbolic-pointer-cast.c

diff --git a/clang/lib/StaticAnalyzer/Core/Store.cpp b/clang/lib/StaticAnalyzer/Core/Store.cpp
index 3b56dde1b56d6..0a91b452ee11d 100644
--- a/clang/lib/StaticAnalyzer/Core/Store.cpp
+++ b/clang/lib/StaticAnalyzer/Core/Store.cpp
@@ -175,10 +175,13 @@ std::optional<const MemRegion *> StoreManager::castRegion(const MemRegion *R,
       const RegionRawOffset &rawOff = elementR->getAsArrayOffset();
       const MemRegion *baseR = rawOff.getRegion();
 
-      // If we cannot compute a raw offset, throw up our hands and return
-      // a NULL MemRegion*.
-      if (!baseR)
-        return std::nullopt;
+      if (!baseR) {
+        const MemRegion *uncastedR = elementR->StripCasts(false);
+        if (IsSameRegionType(uncastedR, CanonPointeeTy))
+          return uncastedR;
+
+        return MakeElementRegion(cast<SubRegion>(uncastedR), PointeeTy);
+      }
 
       CharUnits off = rawOff.getOffset();
 
diff --git a/clang/test/Analysis/pr22954.c b/clang/test/Analysis/pr22954.c
index b3910da6c70ab..1a14d83ca2ae9 100644
--- a/clang/test/Analysis/pr22954.c
+++ b/clang/test/Analysis/pr22954.c
@@ -615,7 +615,8 @@ int f29(int i, int j, int k, int l, int m) {
   l29->s1[m] = 2;
   char input[] = {'a', 'b', 'c', 'd'};
   memcpy(l29->s1, input, 4);
-  clang_analyzer_eval(m29[0].s3[0] == 1); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(m29[0].s3[0] == 1); // expected-warning{{UNKNOWN}}\
+  expected-warning{{Potential leak of memory pointed to by field 's4'}}
   clang_analyzer_eval(m29[0].s3[1] == 1); // expected-warning{{UNKNOWN}}
   clang_analyzer_eval(m29[0].s3[2] == 1); // expected-warning{{UNKNOWN}}
   clang_analyzer_eval(m29[0].s3[3] == 1); // expected-warning{{UNKNOWN}}
@@ -627,7 +628,7 @@ int f29(int i, int j, int k, int l, int m) {
   clang_analyzer_eval(m29[i].s3[1] == 1); // expected-warning{{UNKNOWN}}
   clang_analyzer_eval(m29[i].s3[2] == 1); // expected-warning{{UNKNOWN}}
   clang_analyzer_eval(m29[i].s3[3] == 1); // expected-warning{{UNKNOWN}}
-  clang_analyzer_eval(m29[j].s3[k] == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(m29[j].s3[k] == 1); // expected-warning{{UNKNOWN}}
   clang_analyzer_eval(l29->s1[m] == 2); // expected-warning{{UNKNOWN}}
   // FIXME: Should warn that m29[i].s4 leaks. But not on the previous line,
   // because l29 and m29 alias.
diff --git a/clang/test/Analysis/symbolic-pointer-cast.c b/clang/test/Analysis/symbolic-pointer-cast.c
new file mode 100644
index 0000000000000..9804254808343
--- /dev/null
+++ b/clang/test/Analysis/symbolic-pointer-cast.c
@@ -0,0 +1,42 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s
+
+void clang_analyzer_eval(int);
+
+void symbolic_offset_pointer_cast_preserves_nonnull(_Bool i) {
+  unsigned char a[2];
+  unsigned char *q = a + i;
+  char *r = (char *)q;
+
+  clang_analyzer_eval(q != 0); // expected-warning{{TRUE}}
+  clang_analyzer_eval(r != 0); // expected-warning{{TRUE}}
+
+  if (!r)
+    *r = 0; // no-warning
+}
+
+void differently_sized_symbolic_offset_pointer_cast_preserves_nonnull(_Bool i) {
+  int a[2];
+  int *q = a + i;
+  char *r = (char *)q;
+  int *s = (int *)r;
+
+  clang_analyzer_eval(r != 0); // expected-warning{{TRUE}}
+  clang_analyzer_eval(s == q); // expected-warning{{TRUE}}
+
+  if (!r)
+    *r = 0; // no-warning
+}
+
+void symbolic_base_pointer_cast_preserves_nonnull(int *p, _Bool i) {
+  if (!p)
+    return;
+
+  int *q = p + i;
+  char *r = (char *)q;
+
+  clang_analyzer_eval(q != 0); // expected-warning{{TRUE}}
+  clang_analyzer_eval(r != 0); // expected-warning{{TRUE}}
+
+  if (!r)
+    *r = 0; // no-warning
+}



More information about the cfe-commits mailing list