From c127c676d8908e942f96df3e2c970ef59ddbd10c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 11 Jul 2026 15:20:37 +0200 Subject: [PATCH] fix(lint): close the addr-class blind spots with a ratchet (#6279) The addr-class gate exists to catch the Linux-only handle-deref segfault class (#1843, #4004, #4665, #4800, #6271). It missed #6271 and would have missed most of the 44 crashes found in #6280, because its two rules only see: band-literal a re-typed CORRECT boundary (0x100000, 0xF0000, ...) gcheader-cast `as *const GcHeader` outside gc/ Neither fires on the shapes that actually shipped the bugs. Two new rules: handle-floor a hand-rolled address floor BELOW HANDLE_BAND_MAX -- `< 0x10000` / `>= 0x1000`. A WRONG literal is invisible to band-literal, which only knows the right ones. This is exactly what js_object_freeze and js_object_delete_field used (both fixed in #6280). Only fires when the literal is compared against something address-shaped, so an ordinary 64 KB buffer size is not flagged. lone-valid-obj-ptr is_valid_obj_ptr as the ONLY guard before a deref. Its own doc says that is insufficient on Linux/Windows/Android/iOS -- "pair it with is_handle_band". That is precisely #6271's shape: no band literal, no GcHeader cast, so neither old rule could see it. A band predicate anywhere in the enclosing guard clears the finding. Enforced by a COUNT ratchet, not the line-substring allowlist: there are 483 + 87 pre-existing sites, and pinning those to line text would rot on the first refactor and give false comfort. A file may never GAIN a site (that fails the gate); fixing sites means lowering its number, and the gate reports stale counts so the baseline burns down. Converting a site to an addr_class predicate is always safety-monotonic -- it can only reject MORE addresses, never deref more. The ratcheted rules deliberately bypass the allowlist. Several allowlist entries are broad (`path | * | justification`), so letting them apply would let a NEW violation slip into an already-allowlisted file -- the exact blind spot this closes. Verified: injecting a new violation of either rule into an allowlisted file fails the gate. Also documents, in addr_class.rs, the fix that does NOT work. HEAP_MIN is 0x1000 on Linux while the doc right above it claims 1 MB, which looks like a missing zero and is very tempting to "fix". I tried it: raising the floor to HANDLE_BAND_MAX regressed Object.defineProperty on native handles (started throwing TypeError) and the Proxy apply trap (lost its arguments), caught by test_gap_handle_band_object_ops and test_gap_proxy_reflect on Linux. The permissive floor is load-bearing -- callers pass handle ids through it on purpose. Those dependents must be migrated first. Recording that so the next person does not spend the afternoon rediscovering it. Scripts-only apart from a comment-only doc block, so there is no runtime risk. Gate self-tests cover both new rules, including the negative cases: a size literal must not be flagged, and a correctly PAIRED guard must pass (otherwise the rule would block the very fix it is asking for). --- crates/perry-runtime/src/value/addr_class.rs | 14 +- scripts/addr_class_inventory.py | 296 ++++++++++++++++++- scripts/addr_class_ratchet_baseline.txt | 282 ++++++++++++++++++ 3 files changed, 586 insertions(+), 6 deletions(-) create mode 100644 scripts/addr_class_ratchet_baseline.txt diff --git a/crates/perry-runtime/src/value/addr_class.rs b/crates/perry-runtime/src/value/addr_class.rs index bf0ad73010..a0b33d79e9 100644 --- a/crates/perry-runtime/src/value/addr_class.rs +++ b/crates/perry-runtime/src/value/addr_class.rs @@ -144,7 +144,19 @@ pub fn is_stream_id_band(id: usize) -> bool { /// (`0x1000`) is BELOW the handle band, so this predicate alone does NOT /// reject small handles there — pair it with [`is_handle_band`] (or use /// [`try_read_gc_header`], which does both) when the input can carry a -/// handle id. +/// handle id. `scripts/addr_class_inventory.py` ratchets this: the +/// `lone-valid-obj-ptr` rule fails the build on any NEW unpaired call site. +/// +/// #6279 — DO NOT "fix" this by raising `HEAP_MIN` to [`HANDLE_BAND_MAX`]. +/// It looks like a one-character typo (the doc above says 1 MB, the constant +/// says 4 KB) and it is tempting, but the permissive floor is load-bearing: +/// several callers pass a handle-band id through here on purpose and rely on +/// it answering `true` so they can route the value onward. Raising the floor +/// was tried and measurably regressed `Object.defineProperty` on native +/// handles (started throwing TypeError) and the Proxy `apply` trap (lost its +/// arguments) — caught by `test_gap_handle_band_object_ops` and +/// `test_gap_proxy_reflect` on Linux. Those dependents must be migrated to an +/// explicit band check FIRST; only then can this floor be raised. #[inline(always)] pub(crate) fn is_valid_obj_ptr(ptr: *const u8) -> bool { let addr = ptr as u64; diff --git a/scripts/addr_class_inventory.py b/scripts/addr_class_inventory.py index 9a338c40e7..d51c3bf0be 100644 --- a/scripts/addr_class_inventory.py +++ b/scripts/addr_class_inventory.py @@ -62,6 +62,49 @@ GC_HEADER_CAST_RE = re.compile(r"as\s+\*(?:const|mut)\s+(?:crate::gc::)?GcHeader\b") +# HANDLE FLOOR (#6279) — a hand-rolled address floor that is BELOW +# `HANDLE_BAND_MAX` (0x100000). `0x1000` / `0x10000` are an order of magnitude +# too low, so the fetch (0x40000..0xE0000), zlib (0xE0000..0xF0000) and proxy +# (0xF0000..) handle bands sail straight through into a dereference. This is +# the *wrong-literal* case that BAND_LITERAL_RE structurally cannot see: that +# rule only knows the CORRECT boundaries, so an invented floor is invisible to +# it. Two real crashes had exactly this shape (js_object_freeze's `> 0x10000` +# and js_object_delete_field's `< 0x10000`, both fixed in #6280). +# +# Only flag the literal when it is compared against something address-shaped — +# `0x10000` is also a perfectly ordinary 64 KB buffer size, and those are not +# our problem. +HANDLE_FLOOR_RE = re.compile( + r"(?:\bptr\b|\baddr\b|\bbits\b|_ptr\b|_addr\b|_bits\b|as\s+usize|as\s+u64)" + r"[^;]{0,60}?(?:<|>|<=|>=)\s*(?:crate::gc::GC_HEADER_SIZE\s*\+\s*)?0x1_?0?000\b" + r"|0x1_?0?000\b\s*(?:<|<=)\s*[A-Za-z_]*(?:ptr|addr|bits)\b", + re.IGNORECASE, +) + +# LONE is_valid_obj_ptr (#6279) — `is_valid_obj_ptr` used as the ONLY guard +# before a dereference. Its own doc says it is not sufficient: +# +# the platform HEAP_MIN floor on Linux/Android/iOS/Windows (0x1000) is BELOW +# the handle band, so this predicate alone does NOT reject small handles +# there — pair it with is_handle_band +# +# That is exactly the shape of #6271 (`gz.on("data")` deref'ing a zlib stream +# handle): no band literal and no GcHeader cast, so neither of the original two +# rules could see it. A band predicate anywhere in the surrounding guard clears +# the finding. +VALID_OBJ_PTR_RE = re.compile(r"\bis_valid_obj_ptr\s*\(") +BAND_PREDICATE_RE = re.compile( + r"is_above_handle_band|is_handle_band|is_small_handle|is_proxy_id_band" + r"|try_read_gc_header" +) +# How many lines above the call may satisfy the pairing requirement. +BAND_PREDICATE_LOOKBACK = 5 + +DEFAULT_RATCHET_BASELINE = REPO_ROOT / "scripts" / "addr_class_ratchet_baseline.txt" + +# Rules governed by the count ratchet rather than the line-substring allowlist. +RATCHETED_RULES = ("handle-floor", "lone-valid-obj-ptr") + LINE_COMMENT_RE = re.compile(r"//.*$") @@ -101,12 +144,26 @@ def scan_text(rel_path: str, text: str) -> list[Finding]: findings: list[Finding] = [] if any(rel_path.startswith(prefix) for prefix in EXCLUDED_PREFIXES): return findings - for line_no, raw in enumerate(text.splitlines(), 1): + lines = text.splitlines() + for idx, raw in enumerate(lines): + line_no = idx + 1 code = strip_comment(raw) if BAND_LITERAL_RE.search(code): findings.append(Finding(rel_path, line_no, "band-literal", raw)) if GC_HEADER_CAST_RE.search(code): findings.append(Finding(rel_path, line_no, "gcheader-cast", raw)) + if HANDLE_FLOOR_RE.search(code): + findings.append(Finding(rel_path, line_no, "handle-floor", raw)) + if VALID_OBJ_PTR_RE.search(code) and "fn is_valid_obj_ptr" not in code: + # A band predicate anywhere in the enclosing guard clears it. + start = max(0, idx - BAND_PREDICATE_LOOKBACK) + context = "\n".join( + strip_comment(l) for l in lines[start : idx + 2] + ) + if not BAND_PREDICATE_RE.search(context): + findings.append( + Finding(rel_path, line_no, "lone-valid-obj-ptr", raw) + ) return findings @@ -178,6 +235,80 @@ def expect(cond: bool, message: str) -> None: runtime = "crates/perry-runtime/src/foo.rs" + # --- handle-floor rule (#6279) ------------------------------------------ + # An address compared against a floor BELOW HANDLE_BAND_MAX is the bug. + for src in ( + "if (obj as usize) < 0x10000 {\n", + "if ptr.is_null() || (ptr as usize) < 0x1000 {\n", + "if raw_addr >= crate::gc::GC_HEADER_SIZE + 0x1000 {\n", + "} else if top16 == 0 && bits >= 0x1000 {\n", + ): + expect( + any(f.rule == "handle-floor" for f in scan_text(runtime, src)), + f"handle-floor should flag: {src.strip()}", + ) + + # 0x10000 as a plain SIZE (a 64 KB buffer, a chunk cap) is not an address + # guard and must not be flagged — otherwise the rule is unusable noise. + for src in ( + "const CHUNK: usize = 0x10000;\n", + "let mut buf = vec![0u8; 0x10000];\n", + "if len > 0x10000 {\n", + ): + expect( + not any(f.rule == "handle-floor" for f in scan_text(runtime, src)), + f"handle-floor must NOT flag a size literal: {src.strip()}", + ) + + # The CORRECT boundary is not a handle-floor finding (band-literal owns it). + expect( + not any( + f.rule == "handle-floor" + for f in scan_text(runtime, "if (ptr as usize) < 0x100000 {\n") + ), + "handle-floor must not fire on the correct HANDLE_BAND_MAX boundary", + ) + + # Comment-only mentions are ignored, same as the other rules. + expect( + not any( + f.rule == "handle-floor" + for f in scan_text(runtime, "// the old floor was ptr < 0x10000\n") + ), + "handle-floor must ignore comments", + ) + + # --- lone-valid-obj-ptr rule (#6279) ------------------------------------ + lone = " if is_valid_obj_ptr(ptr as *const u8) {\n (*ptr).class_id\n" + expect( + any(f.rule == "lone-valid-obj-ptr" for f in scan_text(runtime, lone)), + "lone-valid-obj-ptr should flag an unpaired is_valid_obj_ptr guard", + ) + # Paired with a band predicate -> cleared. This is the fix shape, so the rule + # must accept it or it would just block people from fixing the bug. + paired = ( + " if crate::value::addr_class::is_above_handle_band(ptr as usize)\n" + " && is_valid_obj_ptr(ptr as *const u8)\n {\n" + ) + expect( + not any(f.rule == "lone-valid-obj-ptr" for f in scan_text(runtime, paired)), + "lone-valid-obj-ptr must accept a guard paired with a band predicate", + ) + # try_read_gc_header does both checks itself. + trg = " if let Some(h) = try_read_gc_header(ptr) {\n let _ = is_valid_obj_ptr(ptr);\n" + expect( + not any(f.rule == "lone-valid-obj-ptr" for f in scan_text(runtime, trg)), + "lone-valid-obj-ptr must accept try_read_gc_header", + ) + # The definition itself is not a call site. + expect( + not any( + f.rule == "lone-valid-obj-ptr" + for f in scan_text(runtime, "pub fn is_valid_obj_ptr(ptr: *const u8) -> bool {\n") + ), + "lone-valid-obj-ptr must not flag the definition", + ) + # Band literals in code are caught; comment-only mentions are not. hits = scan_text(runtime, "if addr < 0x100000 {\n") expect( @@ -260,10 +391,76 @@ def expect(cond: bool, message: str) -> None: return 0 +def load_ratchet_baseline(path: Path) -> dict[tuple[str, str], int]: + """Parse `rule | path | count` lines: KNOWN pre-existing sites per (rule, file). + + A COUNT baseline, not a line-substring allowlist: these sites move on every + refactor, so pinning them to line text would rot immediately and give false + comfort. The contract is a ratchet — a file may never gain a site, and every + fix lowers its number. + """ + + baseline: dict[tuple[str, str], int] = {} + if not path.is_file(): + return baseline + errors: list[str] = [] + for line_no, raw in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): + line = raw.strip() + if not line or line.startswith("#"): + continue + parts = [part.strip() for part in line.split("|", 2)] + if len(parts) != 3 or parts[0] not in RATCHETED_RULES or not parts[2].isdigit(): + errors.append( + f"{path.name}:{line_no}: expected 'rule | path | count' with rule in " + f"{RATCHETED_RULES}, got: {raw}" + ) + continue + baseline[(parts[0], parts[1])] = int(parts[2]) + if errors: + for error in errors: + print(error, file=sys.stderr) + raise SystemExit(2) + return baseline + + +def check_ratchet( + findings: list[Finding], baseline: dict[tuple[str, str], int] +) -> tuple[list[Finding], list[str]]: + """Split ratcheted findings into regressions (a file gained sites) and debt.""" + + per_key: dict[tuple[str, str], list[Finding]] = {} + for f in findings: + if f.rule in RATCHETED_RULES: + per_key.setdefault((f.rule, f.rel_path), []).append(f) + + regressions: list[Finding] = [] + for key, hits in sorted(per_key.items()): + if len(hits) > baseline.get(key, 0): + # A count ratchet cannot know WHICH line is new, so surface them all + # rather than fingering an arbitrary one. + regressions.extend(hits) + + stale: list[str] = [] + for (rule, rel_path), allowed in sorted(baseline.items()): + actual = len(per_key.get((rule, rel_path), [])) + if actual < allowed: + stale.append( + f" {rule} | {rel_path}: baseline says {allowed}, found {actual} " + f"— lower it to {actual}" + ) + return regressions, stale + + def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser() parser.add_argument("--self-test", action="store_true") parser.add_argument("--allowlist", type=Path, default=DEFAULT_ALLOWLIST) + parser.add_argument("--baseline", type=Path, default=DEFAULT_RATCHET_BASELINE) + parser.add_argument( + "--write-baseline", + action="store_true", + help="regenerate the ratchet baseline from the current tree", + ) parser.add_argument( "--list-unused-allowlist", action="store_true", @@ -275,7 +472,61 @@ def main(argv: list[str] | None = None) -> int: findings, files_scanned = collect_inventory() entries = load_allowlist(args.allowlist) - findings, suppressed = apply_allowlist(findings, entries) + + # The line-substring allowlist governs band-literal / gcheader-cast only. It + # must NOT be able to suppress a ratcheted finding: several of its entries are + # broad (`path | * | justification`), so letting them apply here would let a + # brand-new violation slip into an already-allowlisted file — the very blind + # spot #6279 is about. + ratcheted = [f for f in findings if f.rule in RATCHETED_RULES] + other = [f for f in findings if f.rule not in RATCHETED_RULES] + other, suppressed = apply_allowlist(other, entries) + + if args.write_baseline: + counts: dict[tuple[str, str], int] = {} + for f in ratcheted: + key = (f.rule, f.rel_path) + counts[key] = counts.get(key, 0) + 1 + header = [ + "# addr-class ratchet baseline (#6279).", + "#", + "# Pre-existing sites for the two rules that CANNOT be fixed in one pass:", + "#", + "# handle-floor a hand-rolled address floor BELOW HANDLE_BAND_MAX", + "# (0x1000 / 0x10000). Does not reject the fetch, zlib", + "# or proxy handle bands, so it can deref a handle and", + "# segfault on Linux. macOS hides it behind a 2 TB floor.", + "#", + "# lone-valid-obj-ptr is_valid_obj_ptr used as the ONLY guard before a", + "# deref. Its own doc says that is not sufficient on", + "# Linux/Windows/Android/iOS — pair it with a band", + "# predicate. This is the exact shape of #6271.", + "#", + "# Counts, not line matches: these sites move constantly and a line-pinned", + "# allowlist would rot on the first refactor. The contract is a RATCHET —", + "# a file may never gain a site (that fails the gate), and fixing one means", + "# lowering its number here. The gate tells you when a count is stale.", + "#", + "# Converting a site to an addr_class predicate is always safety-monotonic:", + "# it can only reject MORE addresses, never dereference more.", + "#", + "# Regenerate: python3 scripts/addr_class_inventory.py --write-baseline", + "", + ] + body = [ + f"{rule} | {path} | {count}" + for (rule, path), count in sorted(counts.items()) + ] + args.baseline.write_text("\n".join(header + body) + "\n", encoding="utf-8") + totals: dict[str, int] = {} + for (rule, _), count in counts.items(): + totals[rule] = totals.get(rule, 0) + count + summary = ", ".join(f"{v} {k}" for k, v in sorted(totals.items())) + print(f"Wrote {args.baseline} ({summary}).") + return 0 + + baseline = load_ratchet_baseline(args.baseline) + regressions, stale = check_ratchet(ratcheted, baseline) if args.list_unused_allowlist: for entry in entries: @@ -285,7 +536,32 @@ def main(argv: list[str] | None = None) -> int: f"{entry.path_prefix} | {entry.substring}" ) - if findings: + failed = False + + if regressions: + failed = True + by_key: dict[tuple[str, str], list[Finding]] = {} + for f in regressions: + by_key.setdefault((f.rule, f.rel_path), []).append(f) + print( + "addr-class RATCHET FAILED — a file gained a site in a rule that is\n" + "frozen at its current count. Use the predicates in\n" + "crates/perry-runtime/src/value/addr_class.rs (is_handle_band /\n" + "is_above_handle_band / try_read_gc_header) instead of a hand-rolled\n" + "address floor or a bare is_valid_obj_ptr guard: neither rejects the\n" + "fetch/zlib/proxy handle bands on Linux, and dereferencing a handle\n" + "segfaults there while macOS silently hides it (#1843, #4004, #4665,\n" + "#4800, #6271).\n" + ) + for (rule, rel_path), hits in sorted(by_key.items()): + allowed = baseline.get((rule, rel_path), 0) + print(f" [{rule}] {rel_path}: {len(hits)} site(s), baseline allows {allowed}") + for f in hits: + print(f" line {f.line_no}: {f.line.strip()}") + print() + + if other: + failed = True print( "Address-classification audit failed; use the predicates/constants in\n" "crates/perry-runtime/src/value/addr_class.rs (is_handle_band /\n" @@ -293,13 +569,23 @@ def main(argv: list[str] | None = None) -> int: "of re-typing band literals or casting to GcHeader, or add a justified\n" "entry to scripts/addr_class_allowlist.txt:" ) - for finding in findings: + for finding in other: print(f" {finding.render()}") + + if failed: return 1 + if stale: + print("Ratchet baseline is stale (sites were fixed but not recorded):") + for msg in stale: + print(msg) + print("Run: python3 scripts/addr_class_inventory.py --write-baseline\n") + + held = sum(baseline.values()) print( f"Address-classification audit passed " - f"({files_scanned} files scanned, {suppressed} allowlisted)." + f"({files_scanned} files scanned, {suppressed} allowlisted, " + f"{held} known sites held by the ratchet)." ) return 0 diff --git a/scripts/addr_class_ratchet_baseline.txt b/scripts/addr_class_ratchet_baseline.txt new file mode 100644 index 0000000000..b3ab787316 --- /dev/null +++ b/scripts/addr_class_ratchet_baseline.txt @@ -0,0 +1,282 @@ +# addr-class ratchet baseline (#6279). +# +# Pre-existing sites for the two rules that CANNOT be fixed in one pass: +# +# handle-floor a hand-rolled address floor BELOW HANDLE_BAND_MAX +# (0x1000 / 0x10000). Does not reject the fetch, zlib +# or proxy handle bands, so it can deref a handle and +# segfault on Linux. macOS hides it behind a 2 TB floor. +# +# lone-valid-obj-ptr is_valid_obj_ptr used as the ONLY guard before a +# deref. Its own doc says that is not sufficient on +# Linux/Windows/Android/iOS — pair it with a band +# predicate. This is the exact shape of #6271. +# +# Counts, not line matches: these sites move constantly and a line-pinned +# allowlist would rot on the first refactor. The contract is a RATCHET — +# a file may never gain a site (that fails the gate), and fixing one means +# lowering its number here. The gate tells you when a count is stale. +# +# Converting a site to an addr_class predicate is always safety-monotonic: +# it can only reject MORE addresses, never dereference more. +# +# Regenerate: python3 scripts/addr_class_inventory.py --write-baseline + +handle-floor | crates/perry-runtime/src/array/alloc.rs | 2 +handle-floor | crates/perry-runtime/src/array/concat_reverse.rs | 1 +handle-floor | crates/perry-runtime/src/array/flat_clone.rs | 4 +handle-floor | crates/perry-runtime/src/array/generic.rs | 4 +handle-floor | crates/perry-runtime/src/array/header.rs | 6 +handle-floor | crates/perry-runtime/src/array/indexing.rs | 4 +handle-floor | crates/perry-runtime/src/array/iter_methods.rs | 2 +handle-floor | crates/perry-runtime/src/array/iter_object.rs | 1 +handle-floor | crates/perry-runtime/src/array/iterator.rs | 2 +handle-floor | crates/perry-runtime/src/array/push_pop.rs | 1 +handle-floor | crates/perry-runtime/src/async_hooks.rs | 1 +handle-floor | crates/perry-runtime/src/bigint.rs | 4 +handle-floor | crates/perry-runtime/src/box.rs | 2 +handle-floor | crates/perry-runtime/src/buffer/access.rs | 1 +handle-floor | crates/perry-runtime/src/buffer/cmp.rs | 2 +handle-floor | crates/perry-runtime/src/buffer/copy_bytes.rs | 1 +handle-floor | crates/perry-runtime/src/buffer/encode.rs | 6 +handle-floor | crates/perry-runtime/src/buffer/from.rs | 10 +handle-floor | crates/perry-runtime/src/buffer/iter.rs | 1 +handle-floor | crates/perry-runtime/src/buffer/query.rs | 5 +handle-floor | crates/perry-runtime/src/buffer/transcode.rs | 2 +handle-floor | crates/perry-runtime/src/buffer/u8_codec.rs | 7 +handle-floor | crates/perry-runtime/src/buffer/validate.rs | 1 +handle-floor | crates/perry-runtime/src/builtins/arithmetic.rs | 1 +handle-floor | crates/perry-runtime/src/builtins/console.rs | 7 +handle-floor | crates/perry-runtime/src/builtins/formatting.rs | 4 +handle-floor | crates/perry-runtime/src/builtins/formatting/collection_equality.rs | 1 +handle-floor | crates/perry-runtime/src/builtins/formatting/collections.rs | 1 +handle-floor | crates/perry-runtime/src/builtins/formatting/identity_equality.rs | 1 +handle-floor | crates/perry-runtime/src/builtins/formatting/prototype_equality.rs | 1 +handle-floor | crates/perry-runtime/src/builtins/formatting/typed_array_equality.rs | 1 +handle-floor | crates/perry-runtime/src/builtins/globals.rs | 9 +handle-floor | crates/perry-runtime/src/builtins/numbers.rs | 2 +handle-floor | crates/perry-runtime/src/builtins/table.rs | 1 +handle-floor | crates/perry-runtime/src/child_process/fork.rs | 1 +handle-floor | crates/perry-runtime/src/child_process/reactor.rs | 1 +handle-floor | crates/perry-runtime/src/child_process/registry.rs | 1 +handle-floor | crates/perry-runtime/src/child_process/v8_serde.rs | 1 +handle-floor | crates/perry-runtime/src/child_process/value_util.rs | 1 +handle-floor | crates/perry-runtime/src/closure/dispatch/validate.rs | 1 +handle-floor | crates/perry-runtime/src/closure/dynamic_props.rs | 1 +handle-floor | crates/perry-runtime/src/cluster.rs | 1 +handle-floor | crates/perry-runtime/src/collection_iter_object.rs | 1 +handle-floor | crates/perry-runtime/src/date.rs | 3 +handle-floor | crates/perry-runtime/src/dgram.rs | 1 +handle-floor | crates/perry-runtime/src/dns.rs | 4 +handle-floor | crates/perry-runtime/src/exception.rs | 2 +handle-floor | crates/perry-runtime/src/fs/dirent.rs | 2 +handle-floor | crates/perry-runtime/src/fs/filehandle.rs | 4 +handle-floor | crates/perry-runtime/src/fs/mod.rs | 2 +handle-floor | crates/perry-runtime/src/fs/stream.rs | 1 +handle-floor | crates/perry-runtime/src/fs/validate.rs | 2 +handle-floor | crates/perry-runtime/src/iterator_helpers.rs | 1 +handle-floor | crates/perry-runtime/src/json/mod.rs | 2 +handle-floor | crates/perry-runtime/src/json/replacer.rs | 1 +handle-floor | crates/perry-runtime/src/json/reviver.rs | 1 +handle-floor | crates/perry-runtime/src/json/stringify.rs | 2 +handle-floor | crates/perry-runtime/src/jsx.rs | 4 +handle-floor | crates/perry-runtime/src/map.rs | 3 +handle-floor | crates/perry-runtime/src/native_abi.rs | 1 +handle-floor | crates/perry-runtime/src/native_arena.rs | 1 +handle-floor | crates/perry-runtime/src/native_handle.rs | 1 +handle-floor | crates/perry-runtime/src/net_validate.rs | 1 +handle-floor | crates/perry-runtime/src/node_inspector.rs | 1 +handle-floor | crates/perry-runtime/src/node_repl.rs | 1 +handle-floor | crates/perry-runtime/src/node_stream.rs | 1 +handle-floor | crates/perry-runtime/src/node_stream_constructors/introspection.rs | 1 +handle-floor | crates/perry-runtime/src/node_stream_event_emitter.rs | 1 +handle-floor | crates/perry-runtime/src/node_stream_json.rs | 1 +handle-floor | crates/perry-runtime/src/node_stream_readable_read.rs | 1 +handle-floor | crates/perry-runtime/src/node_stream_readwrite.rs | 2 +handle-floor | crates/perry-runtime/src/node_submodules/blob.rs | 1 +handle-floor | crates/perry-runtime/src/node_submodules/consumers.rs | 1 +handle-floor | crates/perry-runtime/src/node_submodules/diagnostics.rs | 1 +handle-floor | crates/perry-runtime/src/node_submodules/fs_promises.rs | 1 +handle-floor | crates/perry-runtime/src/node_submodules/test.rs | 2 +handle-floor | crates/perry-runtime/src/node_submodules/timers.rs | 1 +handle-floor | crates/perry-runtime/src/node_submodules/trace_events.rs | 1 +handle-floor | crates/perry-runtime/src/node_submodules/zlib.rs | 2 +handle-floor | crates/perry-runtime/src/node_v8.rs | 2 +handle-floor | crates/perry-runtime/src/node_vm.rs | 2 +handle-floor | crates/perry-runtime/src/object/alloc.rs | 4 +handle-floor | crates/perry-runtime/src/object/arguments.rs | 1 +handle-floor | crates/perry-runtime/src/object/array_object_ops.rs | 1 +handle-floor | crates/perry-runtime/src/object/assert.rs | 4 +handle-floor | crates/perry-runtime/src/object/async_generator_queue.rs | 1 +handle-floor | crates/perry-runtime/src/object/buffer_dispatch.rs | 2 +handle-floor | crates/perry-runtime/src/object/dataview_proto_thunks.rs | 1 +handle-floor | crates/perry-runtime/src/object/delete_rest.rs | 1 +handle-floor | crates/perry-runtime/src/object/descriptor_state.rs | 1 +handle-floor | crates/perry-runtime/src/object/descriptors.rs | 2 +handle-floor | crates/perry-runtime/src/object/field_get_set/accessors.rs | 2 +handle-floor | crates/perry-runtime/src/object/field_get_set/enumeration.rs | 4 +handle-floor | crates/perry-runtime/src/object/field_get_set/field_ops.rs | 4 +handle-floor | crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs | 3 +handle-floor | crates/perry-runtime/src/object/field_get_set/has_property.rs | 2 +handle-floor | crates/perry-runtime/src/object/field_get_set/ic_miss.rs | 4 +handle-floor | crates/perry-runtime/src/object/field_set_by_name.rs | 11 +handle-floor | crates/perry-runtime/src/object/global_this/array_error.rs | 1 +handle-floor | crates/perry-runtime/src/object/global_this/ctor_thunks.rs | 1 +handle-floor | crates/perry-runtime/src/object/global_this/typed_array.rs | 4 +handle-floor | crates/perry-runtime/src/object/instanceof.rs | 8 +handle-floor | crates/perry-runtime/src/object/mod.rs | 1 +handle-floor | crates/perry-runtime/src/object/native_call_method.rs | 3 +handle-floor | crates/perry-runtime/src/object/native_call_method/collection_methods.rs | 2 +handle-floor | crates/perry-runtime/src/object/native_call_method/common_methods.rs | 1 +handle-floor | crates/perry-runtime/src/object/native_call_method/handle_methods.rs | 2 +handle-floor | crates/perry-runtime/src/object/native_call_method/primitive_methods.rs | 1 +handle-floor | crates/perry-runtime/src/object/native_module.rs | 1 +handle-floor | crates/perry-runtime/src/object/native_module/namespace_builders.rs | 1 +handle-floor | crates/perry-runtime/src/object/native_module/web_locks.rs | 1 +handle-floor | crates/perry-runtime/src/object/object_literal_ops.rs | 1 +handle-floor | crates/perry-runtime/src/object/object_ops/accessors.rs | 1 +handle-floor | crates/perry-runtime/src/object/object_ops/define_properties.rs | 1 +handle-floor | crates/perry-runtime/src/object/object_ops/define_property.rs | 3 +handle-floor | crates/perry-runtime/src/object/object_ops/descriptor_helpers.rs | 8 +handle-floor | crates/perry-runtime/src/object/object_ops/has_own.rs | 4 +handle-floor | crates/perry-runtime/src/object/object_ops/keys_array.rs | 6 +handle-floor | crates/perry-runtime/src/object/object_ops/prototype.rs | 1 +handle-floor | crates/perry-runtime/src/object/object_ops_frozen.rs | 8 +handle-floor | crates/perry-runtime/src/object/polymorphic_index.rs | 2 +handle-floor | crates/perry-runtime/src/object/property_key.rs | 1 +handle-floor | crates/perry-runtime/src/object/prototype_chain.rs | 2 +handle-floor | crates/perry-runtime/src/object/prototype_helpers.rs | 1 +handle-floor | crates/perry-runtime/src/object/reflect_support.rs | 3 +handle-floor | crates/perry-runtime/src/object/to_string_tag.rs | 10 +handle-floor | crates/perry-runtime/src/object/typed_array_define.rs | 1 +handle-floor | crates/perry-runtime/src/object/typed_array_proto_thunks.rs | 1 +handle-floor | crates/perry-runtime/src/object/util_types.rs | 2 +handle-floor | crates/perry-runtime/src/object/with_env.rs | 1 +handle-floor | crates/perry-runtime/src/os/signal.rs | 1 +handle-floor | crates/perry-runtime/src/path.rs | 1 +handle-floor | crates/perry-runtime/src/perf_hooks.rs | 2 +handle-floor | crates/perry-runtime/src/process.rs | 2 +handle-floor | crates/perry-runtime/src/process/env_misc.rs | 3 +handle-floor | crates/perry-runtime/src/process/node_module.rs | 2 +handle-floor | crates/perry-runtime/src/promise/combinators.rs | 1 +handle-floor | crates/perry-runtime/src/proxy.rs | 5 +handle-floor | crates/perry-runtime/src/readline_helpers.rs | 1 +handle-floor | crates/perry-runtime/src/regex.rs | 2 +handle-floor | crates/perry-runtime/src/set.rs | 4 +handle-floor | crates/perry-runtime/src/string/compare.rs | 2 +handle-floor | crates/perry-runtime/src/string/locale.rs | 1 +handle-floor | crates/perry-runtime/src/string/mod.rs | 1 +handle-floor | crates/perry-runtime/src/string/raw.rs | 1 +handle-floor | crates/perry-runtime/src/symbol.rs | 3 +handle-floor | crates/perry-runtime/src/symbol/constructors.rs | 2 +handle-floor | crates/perry-runtime/src/symbol/get.rs | 6 +handle-floor | crates/perry-runtime/src/symbol/iterator.rs | 2 +handle-floor | crates/perry-runtime/src/symbol/properties.rs | 4 +handle-floor | crates/perry-runtime/src/text.rs | 2 +handle-floor | crates/perry-runtime/src/thread.rs | 13 +handle-floor | crates/perry-runtime/src/timer.rs | 1 +handle-floor | crates/perry-runtime/src/tls.rs | 1 +handle-floor | crates/perry-runtime/src/tty.rs | 1 +handle-floor | crates/perry-runtime/src/typed_feedback.rs | 3 +handle-floor | crates/perry-runtime/src/typed_feedback/guards.rs | 1 +handle-floor | crates/perry-runtime/src/typedarray/access.rs | 5 +handle-floor | crates/perry-runtime/src/typedarray/construct.rs | 4 +handle-floor | crates/perry-runtime/src/typedarray/mod.rs | 5 +handle-floor | crates/perry-runtime/src/typedarray_props.rs | 2 +handle-floor | crates/perry-runtime/src/url/abort.rs | 1 +handle-floor | crates/perry-runtime/src/url/search_params.rs | 1 +handle-floor | crates/perry-runtime/src/util_call_sites.rs | 1 +handle-floor | crates/perry-runtime/src/util_inherits.rs | 2 +handle-floor | crates/perry-runtime/src/util_parse_args.rs | 1 +handle-floor | crates/perry-runtime/src/util_promisify.rs | 1 +handle-floor | crates/perry-runtime/src/util_style_text.rs | 1 +handle-floor | crates/perry-runtime/src/value/dyn_index.rs | 2 +handle-floor | crates/perry-runtime/src/value/dynamic_arith.rs | 2 +handle-floor | crates/perry-runtime/src/value/dynamic_object.rs | 2 +handle-floor | crates/perry-runtime/src/value/equality.rs | 2 +handle-floor | crates/perry-runtime/src/value/nanbox.rs | 1 +handle-floor | crates/perry-runtime/src/value/to_string.rs | 3 +handle-floor | crates/perry-runtime/src/wasi.rs | 2 +handle-floor | crates/perry-runtime/src/weakref.rs | 2 +handle-floor | crates/perry-stdlib/src/axios.rs | 1 +handle-floor | crates/perry-stdlib/src/container/mod.rs | 1 +handle-floor | crates/perry-stdlib/src/container/types.rs | 1 +handle-floor | crates/perry-stdlib/src/crypto/kdf.rs | 4 +handle-floor | crates/perry-stdlib/src/crypto/keys.rs | 1 +handle-floor | crates/perry-stdlib/src/crypto/random.rs | 1 +handle-floor | crates/perry-stdlib/src/crypto/util.rs | 4 +handle-floor | crates/perry-stdlib/src/crypto/x509.rs | 3 +handle-floor | crates/perry-stdlib/src/domain.rs | 1 +handle-floor | crates/perry-stdlib/src/events.rs | 1 +handle-floor | crates/perry-stdlib/src/exponential_backoff.rs | 1 +handle-floor | crates/perry-stdlib/src/fetch/dispatch.rs | 4 +handle-floor | crates/perry-stdlib/src/fetch/mod.rs | 1 +handle-floor | crates/perry-stdlib/src/http.rs | 11 +handle-floor | crates/perry-stdlib/src/jsonwebtoken.rs | 1 +handle-floor | crates/perry-stdlib/src/querystring.rs | 5 +handle-floor | crates/perry-stdlib/src/readline.rs | 1 +handle-floor | crates/perry-stdlib/src/sqlite/options.rs | 3 +handle-floor | crates/perry-stdlib/src/streams.rs | 6 +handle-floor | crates/perry-stdlib/src/streams/byob.rs | 1 +handle-floor | crates/perry-stdlib/src/streams/subclass.rs | 1 +handle-floor | crates/perry-stdlib/src/streams/transform.rs | 1 +handle-floor | crates/perry-stdlib/src/string_decoder.rs | 4 +handle-floor | crates/perry-stdlib/src/tls.rs | 1 +handle-floor | crates/perry-stdlib/src/webcrypto/aes.rs | 4 +handle-floor | crates/perry-stdlib/src/webcrypto/hmac.rs | 1 +handle-floor | crates/perry-stdlib/src/webcrypto/jwk.rs | 1 +handle-floor | crates/perry-stdlib/src/webcrypto/supports.rs | 1 +handle-floor | crates/perry-stdlib/src/webcrypto/util.rs | 5 +handle-floor | crates/perry-stdlib/src/worker_threads.rs | 1 +handle-floor | crates/perry-stdlib/src/zlib.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/array/subclass.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/buffer/access.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/closure/dynamic_props.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/collection_iter.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/error.rs | 3 +lone-valid-obj-ptr | crates/perry-runtime/src/intl.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/intl/ctor_guard.rs | 2 +lone-valid-obj-ptr | crates/perry-runtime/src/object/class_registry/class_meta.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/class_registry/construct.rs | 6 +lone-valid-obj-ptr | crates/perry-runtime/src/object/class_registry/prototype_objects.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/descriptors.rs | 2 +lone-valid-obj-ptr | crates/perry-runtime/src/object/field_get_set.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/field_get_set/accessors.rs | 3 +lone-valid-obj-ptr | crates/perry-runtime/src/object/field_get_set/enumeration.rs | 3 +lone-valid-obj-ptr | crates/perry-runtime/src/object/field_get_set/field_ops.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs | 2 +lone-valid-obj-ptr | crates/perry-runtime/src/object/field_get_set/has_property.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/field_get_set/ic_miss.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/field_set_by_name.rs | 3 +lone-valid-obj-ptr | crates/perry-runtime/src/object/global_this/array_error.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/global_this/fetch_globals.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/global_this/typed_array.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/iterator_prototypes.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/native_call_method.rs | 3 +lone-valid-obj-ptr | crates/perry-runtime/src/object/native_call_method/common_methods.rs | 2 +lone-valid-obj-ptr | crates/perry-runtime/src/object/native_call_method/primitive_methods.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/native_call_method/proto_dispatch.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/native_this_alias.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/object_literal_ops.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/object_ops/define_properties.rs | 2 +lone-valid-obj-ptr | crates/perry-runtime/src/object/object_ops/descriptor_helpers.rs | 2 +lone-valid-obj-ptr | crates/perry-runtime/src/object/object_ops/from_entries.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/object_ops/has_own.rs | 2 +lone-valid-obj-ptr | crates/perry-runtime/src/object/object_ops/keys_array.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/object_ops/prototype.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/polymorphic_index.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/object/prototype_chain.rs | 2 +lone-valid-obj-ptr | crates/perry-runtime/src/object/util_types.rs | 4 +lone-valid-obj-ptr | crates/perry-runtime/src/process/env_misc.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/proxy.rs | 2 +lone-valid-obj-ptr | crates/perry-runtime/src/symbol/get.rs | 2 +lone-valid-obj-ptr | crates/perry-runtime/src/symbol/properties.rs | 3 +lone-valid-obj-ptr | crates/perry-runtime/src/typed_feedback.rs | 6 +lone-valid-obj-ptr | crates/perry-runtime/src/typedarray/access.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/url/url_class.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/util_call_sites.rs | 1 +lone-valid-obj-ptr | crates/perry-runtime/src/util_inherits.rs | 1 +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