A mid-object pointer passed across a real (non-inlined) call boundary loses its offset, and the
callee's dereference checks then report a spurious valid-deref violation. This is the largest
wrong-verdict family in the latest full run: 31 of 68 wrong verdicts, worth −496 score.
Reproducer
// a mid-object heap pointer crossing a real (non-inlined, because recursive) call boundary
#include <stdlib.h>
void peek(char *p, int n) {
if (n == 0) return;
char c = *p; // always within the 5-byte buffer
(void)c;
peek(p + 1, n - 1);
}
int main() {
char *s = malloc(5);
peek(s, 5); // touches s[0..4] only
free(s);
return 0;
}
peek(s, 5) touches s[0..4] of a 5-byte buffer and never leaves it, yet:
--backend CEGAR -> (Property valid-deref) (SafetyResult Unsafe)
Isolation
The failure needs both pointer arithmetic and a call boundary — either alone is handled correctly:
| variant |
pointer arithmetic |
call boundary |
verdict |
truth |
| reproducer above |
yes |
yes |
Unsafe |
SAFE |
peek(p, n-1) — pointer never advances |
no |
yes |
Safe |
SAFE |
same walk written as a loop in main |
yes |
no |
Safe |
SAFE |
The call boundary has to be real: a non-recursive helper gets inlined and the problem disappears.
Recursion is simply the cheapest way to keep a call un-inlined, since InlineProceduresPass refuses
a procedure that transitively reaches recursion. The recursion bound is not involved — the false
alarm is identical at --force-unroll-recursion −1, 3, 10 and 20.
Confirmed on the real task termination-recursive-malloc/rec_strcopy_malloc (expected true,
reported false(valid-deref)): rewriting its recursive rec_strcopy_helper as an equivalent loop,
with everything else unchanged, flips the verdict from Unsafe to Safe.
Affected families in the last full run
31 tasks: termination-15 (14), ldv-memsafety (4), list-ext-properties (2), memsafety (2),
uthash-2.0.2 (2), termination-memory-alloca, termination-recursive-malloc, memsafety-ext3.
Relation to the split model
ReferenceElimination.seedSplitParams seeds a callee's split pointer parameter as
p_base := p; p_offset := 0, which presumes the caller passed a plain offset-0 pointer. For
address-taken locals that case is refused loudly (see
PointerArithmeticTest."passing a split pointer as a function argument is still refused"). The
reproducer above uses heap pointers, which do not go through the split path — so here the same
wrong premise produces a silent false alarm instead of an exception.
Scope
Not a regression against 7.3.0: these tasks were TIMEOUT or ERROR there, so the family predates the
8.x line. Filed out of #564, where it accounts for most of the remaining wrong verdicts.
A mid-object pointer passed across a real (non-inlined) call boundary loses its offset, and the
callee's dereference checks then report a spurious
valid-derefviolation. This is the largestwrong-verdict family in the latest full run: 31 of 68 wrong verdicts, worth −496 score.
Reproducer
peek(s, 5)touchess[0..4]of a 5-byte buffer and never leaves it, yet:Isolation
The failure needs both pointer arithmetic and a call boundary — either alone is handled correctly:
peek(p, n-1)— pointer never advancesmainThe call boundary has to be real: a non-recursive helper gets inlined and the problem disappears.
Recursion is simply the cheapest way to keep a call un-inlined, since
InlineProceduresPassrefusesa procedure that transitively reaches recursion. The recursion bound is not involved — the false
alarm is identical at
--force-unroll-recursion−1, 3, 10 and 20.Confirmed on the real task
termination-recursive-malloc/rec_strcopy_malloc(expectedtrue,reported
false(valid-deref)): rewriting its recursiverec_strcopy_helperas an equivalent loop,with everything else unchanged, flips the verdict from Unsafe to Safe.
Affected families in the last full run
31 tasks:
termination-15(14),ldv-memsafety(4),list-ext-properties(2),memsafety(2),uthash-2.0.2(2),termination-memory-alloca,termination-recursive-malloc,memsafety-ext3.Relation to the split model
ReferenceElimination.seedSplitParamsseeds a callee's split pointer parameter asp_base := p; p_offset := 0, which presumes the caller passed a plain offset-0 pointer. Foraddress-taken locals that case is refused loudly (see
PointerArithmeticTest."passing a split pointer as a function argument is still refused"). Thereproducer above uses heap pointers, which do not go through the split path — so here the same
wrong premise produces a silent false alarm instead of an exception.
Scope
Not a regression against 7.3.0: these tasks were TIMEOUT or ERROR there, so the family predates the
8.x line. Filed out of #564, where it accounts for most of the remaining wrong verdicts.