flan/test/valgrind.supp
Joseph Ferano d1464ee266 Memcheck is told an arena reset happened, and the agentless session is pinned
Two loose ends.

The arena was invisible to memcheck. free-all is retain-capacity, so from
malloc's point of view nothing died and round two of a reset arena could read
a byte it never wrote, print round one's value, and draw no report.
flan_arena_proc now issues memcheck's MAKE_MEM_UNDEFINED over the whole
capacity beside its registry call. Measured on the same machine: the control
produced ERROR SUMMARY 0 before and 6 errors from 4 contexts after, with
--track-origins naming the client request. It is a control in
test_valgrind.ml now rather than a printed note.

The macro is vendored, not included, and the argument is measurement: the
machine that runs the sweep has valgrind and not valgrind-devel, so a guarded
#include would compile to nothing exactly where it matters and the control
would go quiet with no diagnostic. There is also nowhere to put an -I --
flan_rt.c is cat'd into an OCaml string literal and handed to clang in a
scratch directory. The __x86_64__ guard is load-bearing: the same runtime is
built for wasm32-wasi and emscripten.

Cost outside valgrind: 23 instructions on the free-all path only, about 1ns
per reset over fifty million of them, against a run-to-run spread wider than
the effect. Nothing on alloc, resize or free. valgrind.supp still holds no
suppressions; the corpus stayed clean across the change, which is its own
finding.

merged_serve's warning path deserved a test and has one. The discriminating
fact is not the log line but the policy: two_process kills its child and
fails where merged_serve warns and serves anyway, and nothing held that
second answer in place. dev-noagent.flan plus the last block of test_dev.ml
assert the session still answers describe after the wait runs out. Verified
by reverting the policy: the block reports rather than passing. It costs the
full ten seconds and there is no way to spend less. HANDOFF-f1.md is deleted.
2026-09-13 17:55:00 +07:00

54 lines
3.1 KiB
Plaintext

# Memcheck suppressions for the Flan corpus. See test/test_valgrind.ml.
#
# This file is empty of suppressions, and that is a finding rather than an
# oversight. The sweep was run over forty-nine programs with
# --gen-suppressions=all before this file existed, checked and again with
# --no-bounds-checks, and memcheck produced nothing to suppress: no false
# positives, and no true ones either. Every entry below the line would have
# been written from valgrind's own --gen-suppressions output plus a reason;
# none was needed.
#
# The four complaints that were expected, and why none of them appeared:
#
# 1. The arena's alignment padding, and the gap between its offset and its
# capacity. Expected to show as uninitialised reads. It cannot, and the
# reason is structural rather than lucky: the arena is a single
# malloc(cap), so every byte in it — handed out, padding, or still past
# the offset — is one allocation to memcheck. It never learns that a
# sub-object ended, so it has nothing to complain about and equally
# nothing to catch. Recorded as a coverage ceiling in BUILT.md, not as a
# clean bill of health.
#
# What *is* caught is definedness rather than addressability, and that
# half now covers the arena too: flan_arena_proc's free-all issues
# memcheck's MAKE_MEM_UNDEFINED over the whole capacity, so a read of
# bytes a previous round wrote before the reset reports. test_valgrind.ml
# asserts it as a control — it produced nothing before the runtime change
# and six errors after. The corpus stayed clean across that change, and
# the sharp end of that is stale-region.flan, map-stale-region.flan and
# pool-stale-region.flan: all three read through a pointer into an arena
# that has been reset, all three are now reading bytes memcheck knows are
# undefined, and none of them reports — because the epoch trap fires
# first. The runtime's own guard beats the read. Interior overruns are
# still invisible, for the structural reason above.
#
# 2. Hand-written LLVM IR. Expected to confuse the tool. It does not, and it
# could not: memcheck instruments the binary, so it never sees IR, never
# sees a frontend attribute, and cannot tell Emit's output from clang's.
# This is the whole reason it was reachable here when MSan was not.
#
# 3. `zeroed` storage. A zeroed defvar is in .bss, which memcheck treats as
# defined because it is — the kernel supplies zeroes. No complaint, and
# correctly so.
#
# 4. Struct padding in a Map key. The compiler emits a per-key-type hash and
# equality pair that walks fields rather than bytes, so the holes are
# never read. test_valgrind.ml asserts this directly with a key carrying
# two seven-byte holes: it must not report, and a suppression here would
# have destroyed the one control that proves the pair is correct.
#
# The rule for adding to this file: paste valgrind's own --gen-suppressions
# text, and write above it why the report is not a bug. A suppression without
# a reason is how a real defect gets silenced later, and an empty file is a
# better outcome than a speculative one.