You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ci/affected.t27: closure() reads set bytes it never wrote after set_add overflows (the t27b lab mismatch) #6663
The t27b lab run of master 099ac22 (run) has one JIT/interpreter mismatch:
specs/ci/affected.t27
MISMATCH negative_control_a_set_or_output_that_does_not_fit_runs_everything:
jit Ok(3), interpreter Err(Fault("1-byte read at stack+0xc of bytes never written"))
It first appeared in the lab run of 35b8468 (#6605), when the spec started to pass on the reference path.
Root cause: the spec reads memory it never wrote
The bug is in the spec. t27b is right to reject it.
The test's first call is select_affected(inp, 163, &set, 20, &out, 256), with var set : [256]u8 = undefined.
closure() adds the seed specs/c.t27\n, so sn = 12.
In the graph loop, set_add() cannot fit specs/b.t27\n into a cap of 20. It returns scap + 1 = 21.
The inner while (s < c) loop keeps going over the remaining graph lines and calls in_set(set, 21, ...). That call reads set[12..21], and those bytes were never written. Offset 12 is the stack+0xc in the fault.
The answer is still RUN_ALL, so on the JIT, which reads whatever is on the stack, and on zig, which fills undefined with 0xAA, the asserts happen to hold. t27b's interpreter tracks which bytes were written, so it faults on the read.
Minimised repro
The repro was checked on the t27b lab with the master binary, qemu-aarch64 t27b test mini.t27 --check:
module MiniUndefRead;
pub fn sum_prefix(set: []const u8, sn: usize) -> u32 {
var acc : u32 = 0;
var i : usize = 0;
while (i < sn) {
acc = acc + 1;
if (set[i] == 255) { acc = acc + 0; }
i += 1;
}
return acc;
}
test reads_a_byte_never_written {
var set : [4]u8 = undefined;
set[0] = 7;
assert sum_prefix(&set, 2) == 2;
}
MISMATCH reads_a_byte_never_written: jit Ok(2), interpreter Err(Fault("1-byte read at stack+0x1 of bytes never written"))
The repro does not depend on the cross-module own_language:: calls. One slice parameter over an undefined array is enough to trigger it.
Fix (.t27 only)
In closure(), stop the graph loop once the set has overflowed: while (s < c and sn <= scap). Checked on the t27b lab:
t27b test --check: 13 passed, 0 mismatches, 58 runtime asserts (56 before the fix).
reference t27c test-report: 13 of 13 pass.
t27c gen-c changes exactly one line of gen/c/ci/affected.c, and the master spec regenerates byte-for-byte to the committed file.
A note for t27b (no change proposed here)
t27b reports a read of never-written bytes as a JIT/interpreter mismatch, which is the same class as a lowering defect. Here the defect was the spec's. A separate verdict, for example "the spec reads undefined memory", would point the next reader at the spec instead of at t27b. That would be a cli/t27b change, so it needs the owner's owner-approved-foreign label, and it is not part of this issue.
Activity
Merge pull request #6665 from gHashTag/claude/affected-undefined-read…