Repository navigation
fix(stack-buffer): check accesses through a local pointer to an array element - #265
Merged
Merged
Conversation
… element Since a store of an element's address stopped counting as a write, `char* p = &buf[10]; *p = 42;` got no report at all. The access through `p` is now checked where it happens, at `*p = 42`, with `p` in the alias path. Only addresses computed from the array itself are followed (`&buf[i]`, `buf + i`, an array field of a struct): their index counts from the array's start. A load of a variable stands for such an address when its MemorySSA clobber is the store of the address to that variable, the contract tryGetConstFromValue already relies on. That store dominates the load, so the index still holds the value it had when the address was formed, and the pilot and the heuristics judge it at the access, under the conditions the access runs on. A load that may read another store, such as one in the next iteration of a loop or after a branch that assigns the variable, is not followed. Copies from one variable to another are followed the same way. access-through-pointer-variable.c covers a write, a read through a copy, `buf + 10`, a reassignment each way, an access on every iteration, a guarded one, one through the address the previous iteration formed, and one after the loop. A unit test checks that a step back from a pointer variable is not taken for an index of the array.
This was referenced Nov 7, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
#264 stopped counting a store of an element's address (
p = &buf[10]) as a write to that element. That report was misplaced, but it was the only one for a real overflow throughp. This PR checks the access through the pointer variable, where it happens:The fix lives in how
StackBufferAnalysiscollects the loads and stores that use an address. The index-bounds pilot is unchanged.&buf[i],buf + i, an array field of a struct). Their index counts from the array's start. A step from a pointer variable (--end) is not followed.tryGetConstFromValueand the SMT encoding of loads already rely on this contract (fix(analysis): read local constants from their MemorySSA clobber #262).buf[i].q = p) are followed the same way.ptr = buf; ptr[i].Diagnostic changes
The analyzer's full output was compared on every fixture, in the default and smt-z3 passes. Diagnostic blocks were compared regardless of their order.
Tests
test/bound-storage/access-through-pointer-variable.c, with strict details, 7 reports:p, a read through a copyq = p, andbuf + 10;&buf[10]then&buf[9]gives no report;&buf[0]then&buf[10]is reported;i > 4may reach 10;do/whileloop reaches 10 on every run.&buf[10]without dereferencing it, and a conditional access through a pointer that is safe (element-address-is-not-a-write.c).--endfrom&buf[10]readsbuf[9]and is not reported (testIndexBoundsPerAccess).Verification
python3 run_test.py --jobs=4 --no-cachepasses 2653/2653. It covers both passes and the unit tests, and ctest passes 2/2. Environment: Linux x86_64, LLVM 20.1.8, Z3, the CI's CMake options.-Wunused-resultin the unit tests. clang-format is clean.ci.ymlonly runs for pull requests intomain, so the test jobs run here once the stack is retargeted.Cost
Measured per process on the 936 fixture runs, both passes:
MemorySSA is built only in functions that store an address computed from an array and then load the variable.
Limits
p = &buf[10]; p[0] = 1;gets no stack-buffer report.p[i]resolves tobufbut countsifrom the array's start, as before this PR. The offsetof/container_of check reports it as an invalid base.if (c) p = &buf[10]; *p = 1;is not reported.char* p = &buf[8]; f(p);, wherefwrites*p, was reported before feat(stack-buffer): bound stack array indexes with ScalarEvolution on a promoted copy #264 only by the misplaced report on the line that forms the address. That report is gone, and the write infhas none. This PR restores the report only for accesses in the same function. The callee case is tracked in StackBufferOverflow: a callee's write past the end of its caller's stack array is not reported #266.f(&buf[8])passed directly was never reported.memset(p, …)) are not checked, as for a direct address.&g[10]is a constant expression, not an instruction, and is not seen. Neither isg[10]used directly.