Repository navigation
feat!: guard through shadow memory and confirm errors - #45
Merged
Merged
Conversation
Implements RFC 0034 (docs/rfcs/0034-fast-enforcement.md), which amends RFCs 0032 and 0033: the model (prove, check, guard, record) stays and each part becomes cheaper. - Guards are declared calls that LLVM passes canonicalize, merge and expand into inline checks against a whole-address-space shadow (one byte per 16-byte granule, with exact encodings for stack and global objects); range caches and the inline guard bodies are deleted. - A local is registered only where a guard can reach it; library calls whose need no term states go through checked wrappers. - A definite error fails the build only when a path replay confirms it; unconfirmed candidates are warnings. - The analysis is bounded by a work budget per run and per unit, plus memory caps; the transfer-count budgets are gone. - Term arithmetic is exact over long long, clamped only at its result, and stack objects write their shadow a run at a time. Gates (amendment 16): F1, F3, F4 met; F2 met but for opus's build time; F5 and F7 carried forward. 725 cases pass in plain, --asan and --checks verify runs; 48 blind programs added (test/cases/detection-blind). BREAKING CHANGE: -fweavec-budget and -fweavec-unit-budget now count analysis work instead of CFG block transfers (defaults 20,000,000 per run and 400 per site, at least 20,000,000, per unit); a definite error that its replay does not confirm is a warning; the guard helpers are declarations the backend lowers, so the prelude and libweavec_chk.a change with the runtime.
owenthcarey
force-pushed
the
rfc0034-fast-enforcement
branch
from
October 5, 2026 23:50
6b876a4 to
2e249bf
Compare
The shadow and the arena's reservations are excluded from core dumps (MADV_DONTDUMP) and each committed part of the arena is included again: on Linux a trapping program dumped 17.6 TB of zeros into apport and every bug case timed out. A function that calls an inlinable loose function enters its objects loose, and a loose object has the whole of a partial last granule: an inlined callee's compound literal shared one on linux-arm64. RFC 0034 amendment 19.
An archive weavec-cc built is recognised on ELF too (Mach-O's extra underscore was stripped from every name); the runtime asks the C library for a foreign block's usable size unless libweavec_alloc.a is linked, so zero-initialisation reaches the whole block on Linux; the loose-partial runtime test owns both granules it checks.
A stack object is untracked once its scope ends (RFC 0032 §4): case 34 stopped on arm64 only because the dead buffer lay one past a live array. On linux-x86-64 it does not; amendment 16's F6 count is 40, not 41.
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
Implements RFC 0034, Fast enforcement, confirmed errors and a bounded analysis. The model stays the same: prove, check, guard, record. What changes is how each part is paid for.
After RFC 0033 the default build was correct on untuned code, but it cost more than AddressSanitizer: Lua ran at 6.0× the reference compiler's time, QuickJS at 18.5×, curl at 23× and libdeflate at 24.6×. Six of ten fresh projects did not build. Now:
fresh34projects build with default flags and pass their own test suites in trap and report mode, with no trap and no definite error: QuickJS, LMDB, Janet, brotli, xz, libdeflate, zlib-ng, curl, cmark and libgit2.test/cases/detection/stop before the faulty access (54 were required), and no fixed twin stops. On a new blind set of 48 programs (test/cases/detection-blind/), the default build stops 41 and ASan 44, with no false stop.Design notes
Guards are calls the backend lowers (§1). The prelude declares the guards instead of defining them. LLVM passes (
lib/Frontend/GuardPasses.cpp) do the rest:GuardCanonicalizeputs guards in a form LLVM's own redundancy elimination and LICM can remove.GuardMergemerges the guards along one access path.GuardExpandmakes what is left an inline shadow check. Functions with more than 1,024 guards get a compact form.GlobalPaddingpads registered globals to whole granules.Range caches, the epoch, the inline guard bodies and their
_oolcopies are deleted.Shadow memory (§2, amendment 1). One byte per 16-byte granule covers the whole address space. Heap objects get a live prefix and a slot tail. Stack and global objects get an exact encoding: a last-granule byte, logarithmic run bytes, and one-past and mixed markers. Everything else goes to one out-of-line slow path that rejects untracked memory in constant time.
Registration where a guard can reach (§4, amendments 2–3). Registered locals and parameters are aligned to granules. A library call's arguments are registered only when the callee takes a callback, returns a pointer the caller uses, or guards that argument.
Library calls (§5, amendments 4, 13, 14). Call arguments are guarded through the shadow. A copy whose length no term can state goes through a checked wrapper, which checks the destination's room, the source's bytes and that they don't overlap.
%.Nsarguments are covered by the call'sliveguard.Confirmed errors (§6, amendment 7). Each candidate definite error is replayed along single paths with path conditions. It fails the build only if a feasible path reaches the site and every feasible path that does has the violation. Unconfirmed candidates are warnings.
A bounded analysis (§7, amendments 8 and 10). The analysis counts work against a budget:
Per-definition CFGs and the record JSON are dropped as soon as they are used. With that, libgit2's link fell from 7.8 GB.
Fixes found on real code.
setjmprewinds only on its second return (amendment 15).long longand is clamped only at its result, which fixes flac'smemset(p, 0, count + 8).The RFC's Implementation amendments (1–18) record every departure from the text and the gate results.
Not met, carried forward: build cost (F5). These are build CPU over the reference compiler's, with a limit of 5×. 34 of the 41 configs measured are within it. Over it:
sqlite3.c63 CPU s, under its 120 s target)curl's
libtests.cpeaks at 2,271 MiB, against 1,250 MiB for the reference compiler. Every cheaper budget measured failed G15 (functions over budget) or Lua's no-runtime bound.Not met, carried forward: run time (F7 and G14). QuickJS is at 4.00× (limit 4). The geometric mean is 2.55× (limit 2.5). G14: zlib 1.62× and cJSON 1.59× (limit 1.5); Lua 2.43× meets its bound. Without the runtime the three measure 1.09, 1.00 and 1.15.
Ratchet.
expected.jsonwas rewritten from the final full run, because the work budget moves proofs in both directions. Two new warning sites in cJSON's tests were triaged false, in a class already triaged there.Breaking changes:
-fweavec-budgetand-fweavec-unit-budgetnow count analysis work instead of CFG block transfers.libweavec_chk.aand the runtime change together. Rebuild objects.Test plan
sourcewrapper option, the ledger's budget fields and the check emitter's exact terms.test/Driver/driver-werror.candweavec-archive-note.c;test/Emission/guard-passes-*.c,checked-wrappers-runtime.c,runtime-oracle-checked-wrappers.candstack-objects-callback-argument.c;-disable-llvm-passes.test/cases/detection-blind/(48 programs with fixed twins);semantics/runtime/cases for parameter neighbours,%.Nsdangling, copy sources and signed term sums;soundness/cases for escaped locals and cursors over several arrays.scripts/run-cases.pypasses 725 of 725 in trap,--asanand--checks verifymodes.scripts/corpus-gate.py):--fulldefault set: G9–G12, G15 (0.89% of functions over budget), R4 and D1 pass.--set fresh34: F1 passes.--set sealed34: all five build and pass; F2 fails only on opus's build ratio.scripts/check-hygiene.pypasses, with line budgets of 22,000 engine and 72,000 library lines (amendment 17).scripts/check-format.shpasses.Checklist
scripts/format.sh). clang-tidy (Homebrew LLVM) is clean on the changed files.clang-analyzer-security.ArrayBoundis disabled, with a comment, because it hung on LLVM's hung-off operands.docs/architecture.md, the roadmap,AGENTS.mdand the corpus README.lib/Core.