Repository navigation
feat!: reuse freed memory only after a sweep and delete the analysis - #49
Open
owenthcarey wants to merge 4 commits into
Open
owenthcarey wants to merge 4 commits into
owenthcarey wants to merge 4 commits into
Conversation
A stray comment opener in weavec_sweep.c broke the Linux half of the sweep (-Wcomment under -Werror). clang-tidy: LibrarySpec.cpp parameter names and braces, unit-test findings, and modernize-use-designated-initializers off for the unit tests' positional expectation tables. Prettier on cli.md and guarantees.md.
…a phone's page skipInternal is used only by the Darwin region walk (-Wunused-function on Linux). The home page's demo is a grid item whose code block's longest line set the column's width, so the page overflowed at phone width and the header covered the search button (the mobile browser test).
glibc's inline fortify wrappers pass the rest of the object, not a field's size, so the tests of (F) spell the fortified call out as Darwin's headers produce it (RFC 0036 amendment 9, documented in the guarantees). With _FORTIFY_SOURCE=3 on glibc, longjmp is __longjmp_chk, and glibc's own memcpy wrapper replaces the case header's model of it (the case builds with -U_FORTIFY_SOURCE).
This branch has not been deployed
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 0036, Deterministic heap reuse. Under v0.16.0, a use after free was caught only while the freed block sat in a 16 MiB quarantine; once the program allocated enough, the block went to a new owner and the stale pointer corrupted it silently. Now a released heap block is reused only after a sweep finds no pointer to it anywhere in the program. This PR also deletes the advisory ownership analysis, which on untuned code reported nothing worth fixing (62 of 69 findings false) and caught none of the bugs the guards miss.
The RFC's first draft also made spatial safety and use after return deterministic, through bounds from each pointer's provenance and locals moved into arena "frame blocks". That draft was built on this branch and measured at twice v0.16.0's run time (QuickJS 10.8×), so this revision keeps only the cheap parts. The RFC's Alternatives section records the per-mechanism measurement, and those features move to a later RFC that starts from a cost budget.
What a guarded program now gets:
strcpypast an 8-byte field into the next field traps asintra-object-overflow; v0.16.0 dropped the size.__SANITIZE_ADDRESS__and__has_feature(address_sanitizer), and the runtime implements ASan's poisoning interface, so code written to be ASan-clean is WeaveC-clean. In the corpus this removed jansson's deliberate hash over-read and SQLite's misuse-test trap, and pools that poison for ASan are now checked (use-after-poison).strto*/ato*parsers no longer require a terminator they never read.Measured (darwin-arm64; full table in the RFC's Measured gates)
Detection (G1–G3). Every determinism case this RFC catches stops, and every twin runs clean. The detection suites stop 105 of 109 bugs (v0.16.0: 104), and no twin stops. Verify mode reports no
weavec.provenon any case.Untuned programs (G4). CPython 3.13.7, SQLite 3.50.4 and coreutils 9.5 configure as with the reference compiler and pass as it does, with two exceptions:
test_faulthandler, whosetest_read_nullreads null on purpose and gets WeaveC's report;rawmemchrover-read, which ASan stops too.tr, SQLite'scapi3c,test_ast,test_functoolsandtest_regrtest, which failed under v0.16.0, now pass.Corpus (G5). Every config builds and passes its suite in trap and report mode with no trap. Injections stop 33 of 39, the same six misses as v0.16.0.
Run time (G6). The geometric mean is 1.05× v0.16.0's (1.61× the reference compiler). Every benchmark is within 1.25× its v0.16.0 ratio except QuickJS (see below).
Memory (G7). Peak RSS is within the limit on all 13 benchmarks: brotli 162 MiB (v0.16.0: 441), cmark 365 (473), libgit2 170 (117; limit 181).
Pauses (G8). The longest sweep with 256 MiB of live heap is 33 ms (limit 150).
Build (G9). Compile CPU is 0.86× v0.16.0's; no project is above 1.07×.
Not met, carried forward: QuickJS run time (G6). QuickJS runs at 1.31× its v0.16.0 ratio. Under
__SANITIZE_ADDRESS__it sends every object tomalloc(8.3 million allocations instead of 76,000), so each object is tracked separately. Built the same way (-D__SANITIZE_ADDRESS__for v0.16.0), v0.16.0 takes 3.2 s and this tree 2.72 s; with-fno-weavec-asan-compatit is 1.06× v0.16.0's.Not measured: linux-x86_64. The local Docker daemon does not respond, so the sweep's Linux half (signal-stopped threads,
/proc/self/mapsroots) is first exercised by this PR's CI. The RFC stays Accepted until Linux passes.Design notes
The sweep (§3.4,
runtime/weavec_sweep.c). It stops every other thread (Darwin:thread_suspend; Linux: a signal whose handler waits), scans registers, stacks, writable mappings and live heap blocks conservatively, and recycles quarantined slots that nothing marked.These two changes cut the fixed cost per sweep from 12–25 ms to about 1 ms (amendment 3).
Pacing (§3.3, amendment 1). A sweep is due at the soft budget
max(Q, live / 4), but waits until eight of the last sweep's durations have passed; the hard budgetmax(2 Q, live / 2)sweeps at once.Reuse. Recycling builds free lists in address order (amendment 4). Released slots of 64 KiB or more drop their pages at once, with a fresh mapping on Darwin, because
MADV_FREEstayed resident (amendment 5).WEAVEC_RT_STATS=1now also prints peak live and quarantined bytes.Frames (§2, amendment 8). RFC 0035's native frames are back, with
StackLifetime-based sharing. Sharing applies only above 512 bytes, so a use after scope in a small frame cannot land in a later local's bytes.Removed from the first draft: provenance bounds, escape checks and the escape registry, pointer slots, frame blocks and capture logs, the
setjmphook, the globals table and__weavec_rt_bounds.Amendments. All eight amendments in the RFC override its body. The rescope is described at the top of the RFC.
Size. The library is 7,199 lines (budget 9,000) and the runtime 4,999 (budget 6,000). This PR adds 7.8K lines and deletes 85.5K.
Breaking changes:
The advisory analysis is deleted, with no aliases:
weavectool andweavec --whole-program;-fweavec-diagnose,-fweavec-budget=,-fweavec-unit-budget=,-fweavec-dump-analysisand-fweavec-analysis-stats=;The
-W…weavec…flags now reach Clang as unknown warning options.weavec.his version 1.0 and keeps onlyWEAVEC_UNSAFE. Code that uses another annotation no longer compiles.Units are compiled as ASan would compile them (
__SANITIZE_ADDRESS__is defined).-fno-weavec-asan-compatturns this off.Optimising glibc builds get
-D_FORTIFY_SOURCE=3unless the command line sets a level.Report kinds:
intra-object-overflowanduse-after-poisonare new;buffer-overflowis no longer reported.The runtime's entry points and its Darwin dispatch table (version 4) changed, so rebuild every object with this
weavec-cc.A program that frees heavily can hold more memory between sweeps (up to the hard budget). A block that a stale word still points to stays allocated.
On Linux the sweep uses
SIGPWR(orWEAVEC_RT_SWEEP_SIGNAL).Test plan
ctest -R '^runtime$': 27 tests, includingpacing,library-data-is-scannedand thesweep-*tests, in release and dev builds.lit build/dev/test: 47 tests.scripts/run-cases.py: 533 pass and 11XFAILin trap mode; verify mode reports noweavec.proven.ctest --preset dev: 39 of 39.scripts/check-hygiene.py,scripts/check-format.sh,scripts/check-cmake-format.sh, and clang-tidy on the changed C++ files.npm testandnpm run buildindocs/(71 pages validated).scripts/corpus-gate.py --full(G5),--quickratchet re-recorded, and--benchagainst v0.16.0 (G6, G7).ci-tidy(this PR's CI).