diff --git a/changelog.d/7804-addr-class-scans-ext-crates.md b/changelog.d/7804-addr-class-scans-ext-crates.md new file mode 100644 index 0000000000..7e869e8226 --- /dev/null +++ b/changelog.d/7804-addr-class-scans-ext-crates.md @@ -0,0 +1,14 @@ +**The addr-class audit now scans `crates/perry-ext-*`** (#7272), bringing 18 previously-invisible sites into the gate. + +`scripts/addr_class_inventory.py` stopped at `perry-runtime/src` and `perry-stdlib/src`, while its sibling `gc_store_site_inventory.py` has globbed `crates/perry-ext-*/src` all along — so the two audits disagreed about what counts as first-party runtime code. + +That was not academic. #6826 moved the HTTP server out of `crates/perry-stdlib/src/http.rs` into `crates/perry-ext-http/`, and this gate then reported its 11 handle-floor sites as `baseline says 11, found 0 — lower it to 0`. The sites had not been fixed; they had walked out of the gate's field of view, and the ratchet's own bookkeeping invited someone to ratify that as progress. **A gate whose coverage shrinks silently when code moves** is the failure mode CLAUDE.md's "four ways a gate can be unable to fail" is about, with the twist that here the shrinkage announces itself as a win. + +Scanned files go from 826 to **1027**. The 18 newly-visible sites are recorded, not rewritten: + +* **10 handle-floor**, baselined per file — `perry-ext-events` (3), `perry-ext-http/agent.rs` (3), `perry-ext-http/lib.rs` (2), `perry-ext-exponential-backoff` (1), `perry-ext-fastify` (1). Five of those are the HTTP server's, i.e. the ones the stale entry was about. +* **8 band-literal**, allowlisted with justifications — each is a wrapper asking "is this f64 payload a small registry handle rather than a heap pointer?" before dereferencing, which is exactly what `addr_class::is_handle_band` answers. They are re-typed rather than called because the ext crates reach perry-runtime through its public surface and `is_handle_band` is not on it. Exporting it is the real fix and belongs with #7448's `ptr_is_tracked_heap_object` export rather than buried in a scan-scope change. + +**Verified the widened gate can fail**, rather than assuming it: planting `if (obj as usize) < 0x100000 {` in `perry-ext-exponential-backoff` makes the audit exit 1 and name the file and line; removing it returns exit 0. Worth recording that a first attempt at that sabotage used `p < 0x10000` and did *not* trip the rule — the detector keys on specific shapes, so "I added a magic number and nothing happened" is not evidence the scan is dark. + +The sibling `gc_store_site_inventory.py` and `check_file_size.sh` both stay green. diff --git a/scripts/addr_class_allowlist.txt b/scripts/addr_class_allowlist.txt index 6399fe472a..91e95a9061 100644 --- a/scripts/addr_class_allowlist.txt +++ b/scripts/addr_class_allowlist.txt @@ -152,3 +152,16 @@ crates/perry-runtime/src/closure/dispatch/ | * | pre-existing GcHeader probe pre crates/perry-runtime/src/bun_compat/string_width.rs | 0xE0000..=0xE007F | Unicode "Tags" codepoint block (U+E0000..U+E007F) tested against a char, not a handle-band address crates/perry-runtime/src/bun_compat/width_tables.rs | * | pure Unicode East-Asian-width codepoint-range table; every hex literal is a Unicode code point (e.g. U+F0000 / U+100000 SPUA-A/B planes), never a handle-band address crates/perry-runtime/src/array/collection_tag_tests.rs | * | unit tests for the #7765 receiver-tag gate: they read AND re-stamp `GcHeader.obj_type` on an address they allocated themselves, which is the whole subject under test. `try_read_gc_header` cannot serve them (it hands out a shared reference, and the recycling test must WRITE the tag to model `arena_alloc_gc` handing the bytes to the next owner). + +# #7272: band literals in `crates/perry-ext-*`, which entered this gate's scope +# with the widened scan roots. Each is a wrapper deciding "is this f64 payload a +# small registry handle rather than a heap pointer?" before dereferencing -- +# the same question `addr_class::is_handle_band` answers, re-typed. They are +# recorded rather than rewritten because the ext crates reach perry-runtime +# through its public surface and `is_handle_band` is not on it; exporting it is +# the actual fix and belongs with #7448's `ptr_is_tracked_heap_object` export, +# not buried in a scan-scope change. +crates/perry-ext-events/src/lib.rs | const EVENT_EMITTER_HANDLE_ID_END | #7272: the crate's own handle-id range end, a registry bound rather than a heap-address band test +crates/perry-ext-net/src/jsvalue.rs | * | #7272: socket/server handle-vs-pointer discrimination before dereference; re-types the handle band instead of calling addr_class::is_handle_band, which perry-runtime does not export +crates/perry-ext-ratelimit/src/lib.rs | (obj as usize) >= 0x100000 | #7272: same handle-vs-pointer guard before an ObjectHeader read +crates/perry-ext-slugify/src/lib.rs | (obj as usize) < 0x100000 | #7272: same handle-vs-pointer guard before an ObjectHeader read diff --git a/scripts/addr_class_inventory.py b/scripts/addr_class_inventory.py index 101cef9323..d16f55e2b6 100644 --- a/scripts/addr_class_inventory.py +++ b/scripts/addr_class_inventory.py @@ -44,6 +44,19 @@ "crates/perry-stdlib/src", ) +# #7272: `crates/perry-ext-*/src` is first-party runtime code and is scanned +# too, matching the sibling gate `gc_store_site_inventory.py`, whose +# `iter_scan_roots()` has globbed these all along. +# +# The two audits disagreeing was not academic. #6826 moved the HTTP server out +# of `perry-stdlib/src/http.rs` into `crates/perry-ext-http/`, and this gate +# reported the file's 11 handle-floor sites as "found 0 — lower it to 0". They +# had not been fixed; they had walked out of the gate's field of view, and the +# ratchet's own bookkeeping invited someone to ratify that as progress. A gate +# whose coverage shrinks silently when code moves is the shape CLAUDE.md's +# "four ways a gate can be unable to fail" is about. +EXT_CRATE_GLOB = "perry-ext-*" + # The module that owns the band constants/predicates, and the collector # internals that legitimately manipulate GcHeader layout directly. EXCLUDED_PREFIXES = ( @@ -215,8 +228,16 @@ def scan_text(rel_path: str, text: str) -> list[Finding]: def collect_inventory() -> tuple[list[Finding], int]: findings: list[Finding] = [] files_scanned = 0 - for root in SCAN_ROOTS: - for path in sorted((REPO_ROOT / root).rglob("*.rs")): + scan_dirs = [REPO_ROOT / root for root in SCAN_ROOTS] + scan_dirs.extend( + src + for src in ( + ext / "src" for ext in sorted((REPO_ROOT / "crates").glob(EXT_CRATE_GLOB)) + ) + if src.is_dir() + ) + for root in scan_dirs: + for path in sorted(root.rglob("*.rs")): rel_path = path.relative_to(REPO_ROOT).as_posix() # Skip parked/hidden trees (e.g. `.value.parked/`) — not compiled. if any(part.startswith(".") for part in rel_path.split("/")): diff --git a/scripts/addr_class_ratchet_baseline.txt b/scripts/addr_class_ratchet_baseline.txt index 16ace45fdd..3511cb4ac7 100644 --- a/scripts/addr_class_ratchet_baseline.txt +++ b/scripts/addr_class_ratchet_baseline.txt @@ -272,3 +272,19 @@ lone-valid-obj-ptr | crates/perry-runtime/src/util_mime.rs | 1 lone-valid-obj-ptr | crates/perry-runtime/src/util_style_text.rs | 1 lone-valid-obj-ptr | crates/perry-runtime/src/value/dyn_index.rs | 2 lone-valid-obj-ptr | crates/perry-runtime/src/value/to_string.rs | 1 + +# #7272: `crates/perry-ext-*` entered this gate's scope. These ten sites are +# not new code — they were invisible because the scan roots stopped at +# perry-runtime/perry-stdlib while the sibling gate +# (gc_store_site_inventory.py) had globbed the ext crates all along. +# +# Five of them are the HTTP server's, which #6826 moved out of +# crates/perry-stdlib/src/http.rs. This file used to carry an entry for that +# path, and once the file vanished the gate reported "baseline says 11, found +# 0 -- lower it to 0": an invitation to ratify a coverage loss as a fix. They +# are baselined here at their real counts instead. +handle-floor | crates/perry-ext-events/src/lib.rs | 3 +handle-floor | crates/perry-ext-exponential-backoff/src/lib.rs | 1 +handle-floor | crates/perry-ext-fastify/src/server.rs | 1 +handle-floor | crates/perry-ext-http/src/agent.rs | 3 +handle-floor | crates/perry-ext-http/src/lib.rs | 2