[clang] [clang][analyzer] Preserve symbolic-offset regions across pointer casts (PR #221213)
Radovan Božić via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 12 09:07:38 PDT 2026
================
@@ -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}}
----------------
bozicrHT wrote:
I think I finally understood this after comparing the exploded-graph transitions.
Before the patch, the cast loses the alias with `m29`, so `l29` points to an unrelated conjured region. Consequently, `memcpy` invalidates that region:
```diff
- SymRegion{conj_$6{...}} None 2 S8b
+ SymRegion{conj_$6{...}} 0 (Default) conj_$11{...}
```
Reads through `l29` therefore see fresh unknown contents, but the binding `m29[j].s3[k] = 1` incorrectly survives. **After** the patch, `l29` correctly aliases `m29[l]`: `&Element{Element{m29, l, struct mm}, 0, struct ll}`. The `memcpy` invalidation now reaches the actual `m29` object. Because `l` is symbolic, `RegionStore` cannot identify one concrete element and conservatively invalidates the enclosing `m29` cluster:
```diff
- m29 None 1 S8b
+ m29 0 (Default) conj_$9{struct mm, ...}
```
The subsequent load no longer has a direct binding. Since its symbolic `j/k` offset cannot be flattened, `RegionStore` returns `UnknownVal`.
Thus, the old `TRUE` came from a **stale binding** caused by lost aliasing. The new `UNKNOWN` is sound but imprecise: the analyzer invalidates the destination but does not model the concrete bytes copied by `memcpy`. **The gain** from the patch is that writes through `l29` can no longer leave stale `m29` bindings behind.
This is the example I used for analysis:
```c
typedef __typeof(sizeof(int)) size_t;
void *memcpy(void *, const void *, size_t);
void clang_analyzer_eval(int);
void clang_analyzer_dump(int);
void clang_analyzer_warnIfReached(void);
struct ll {
char s1[4];
char *s2;
};
struct mm {
char s3[4];
char *s4;
};
void f29(int j, int k, int l, int m) {
struct mm m29[2];
m29[j].s3[k] = 1;
struct ll *l29 = (struct ll *)&m29[l];
l29->s1[m] = 2;
char input[] = {'a', 'b', 'c', 'd'};
memcpy(l29->s1, input, 4);
if (j == l) {
clang_analyzer_warnIfReached();
clang_analyzer_eval(&m29[j] == &m29[l]);
clang_analyzer_dump(m29[j].s3[k]);
clang_analyzer_eval(m29[j].s3[k] == 1);
}
}
```
https://github.com/llvm/llvm-project/pull/221213
More information about the cfe-commits
mailing list