From 0be128da381f98ccf04a943aa7007acfc3c51a7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 9 Aug 2026 19:09:19 +0200 Subject: [PATCH 1/4] docs(repsel): pin why the delete barrier's per-module scope is sound for proven-this (#7143) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Investigated #7143: `has_shape_barrier_sites()` is computed per module, and a proven-`this` receiver is aliased across modules by construction, so a `delete` performed in a module that never declares the class sets no flag the declaring module's Phase 5a admission can see. Confirmed no miscompile exists — every routing site that can call a `$pshape` clone independently re-derives soundness at the point it matters (a runtime keys-token pointer check, or genuine Phase 3b containment), never by trusting the per-module admission fact. Verified with a two-module reproducer compiled end to end (output matches node exactly) and a new IR-level ratchet test mirroring the existing #7142 tower guard test. --- changelog.d/7143-delete-barrier-scope.md | 53 +++++++++++++ .../src/collectors/proven_this.rs | 61 +++++++++++++++ .../collectors/proven_this_routing_tests.rs | 76 +++++++++++++++++++ .../perry-codegen/src/collectors/ptr_shape.rs | 9 ++- test-files/fixtures/issue_7143_pkg/shared.ts | 28 +++++++ ..._issue_7143_delete_barrier_cross_module.ts | 21 +++++ 6 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 changelog.d/7143-delete-barrier-scope.md create mode 100644 test-files/fixtures/issue_7143_pkg/shared.ts create mode 100644 test-files/test_issue_7143_delete_barrier_cross_module.ts diff --git a/changelog.d/7143-delete-barrier-scope.md b/changelog.d/7143-delete-barrier-scope.md new file mode 100644 index 0000000000..f9129af480 --- /dev/null +++ b/changelog.d/7143-delete-barrier-scope.md @@ -0,0 +1,53 @@ +### repsel: document and pin why the `delete` shape barrier's per-module scope is sound for proven-`this` (#7143) + +Investigated #7143 ("the Phase 5a `delete` shape barrier is module-scoped, +but a proven-`this` receiver is aliased across modules by construction"). +Confirmed the asymmetry the issue describes: `ModuleDispatchFacts::has_shape_barrier_sites()` +is computed per module (`collect_module_dispatch_facts`), and Phase 5a's +proven-`this` admission (`collectors/proven_this.rs::method_proven_this`) +consults only its OWN module's copy — a `delete`/`Reflect.deleteProperty` on +a class instance in a module that imports the class, rather than declaring +it, sets no flag the declaring module's admission decision can see. + +**No miscompile exists.** Built a two-module reproducer +(`test-files/test_issue_7143_delete_barrier_cross_module.ts` + +`test-files/fixtures/issue_7143_pkg/shared.ts`) matching the issue's own +suggested shape — module A declares `class C { a; b; c }` plus a method +reading `this.c`, admits a `$pshape` clone since it contains no `delete` +itself; module B holds an instance A handed it, deletes `b` off it (which +relocates `c`'s packed slot via `perry-runtime`'s keys-array compaction), +then calls back into module A, which dispatches `inst.readC()` on the +now-mutated object. The compiled binary's output matches +`node --experimental-strip-types` exactly (`3`, `3`, `3`), and the +`--trace llvm` IR shows why: EVERY routing site that can call a `$pshape` +clone independently re-derives soundness at the point it matters, rather +than trusting the per-module admission fact — + +- `method_direct.fast` (`lower_call/method_override.rs`) sits behind + `js_typed_feedback_method_direct_call_guard` / `js_method_direct_shape_guard`, + whose contract includes a raw pointer compare of the receiver's live + `keys_array` against the class's canonical keys token. `delete`'s only + code path for a class instance with a keys array + (`perry-runtime/src/object/delete_rest.rs::js_object_delete_field`, shared + by `Reflect.deleteProperty`) always clones a FRESH keys array, so this + compare can never pass on a post-delete receiver, from any module. +- The Phase 3b guard-free `Ptr` receiver arm needs no runtime check: + its containment proof (`collectors/ptr_shape.rs` rule 2) already rules out + any alias to the object existing anywhere, so there is nothing for a + cross-module `delete` to reach through. +- The #7142 class-id dispatch-tower case + (`dynamic_dispatch.rs::emit_tower_pshape_call`) already carries its own + explicit keys-token re-check, added specifically for this reason — its doc + comment already cited #7143 by number. + +Landed as a documentation + regression-test PR, not a bug fix: added a +"`delete` is aliased across modules by construction" section to +`collectors/proven_this.rs` stating this invariant explicitly (module-wide +barrier facts are a cost heuristic for Phase 5a, never the correctness +mechanism — a future 4th routing site must independently re-derive a +dominating runtime check or genuine containment), a cross-reference from +`collectors/ptr_shape.rs`'s module-wide barrier rule, a new +`guarded_pshape_call_site_is_preceded_by_a_keys_token_guard` IR ratchet in +`proven_this_routing_tests.rs` pinning the `method_direct.fast` guard +dominance (mirroring the existing tower ratchet), and the two-file +reproducer above as a permanent parity-suite fixture. diff --git a/crates/perry-codegen/src/collectors/proven_this.rs b/crates/perry-codegen/src/collectors/proven_this.rs index e1d44d8162..d12e670fa6 100644 --- a/crates/perry-codegen/src/collectors/proven_this.rs +++ b/crates/perry-codegen/src/collectors/proven_this.rs @@ -49,6 +49,67 @@ //! Recovering the numeric claim needs a whole-program no-external-store proof; //! that is deferred. //! +//! ## `delete` is aliased across modules by construction (#7143) — closed, not a bug +//! +//! [`method_proven_this`] below consults `ModuleDispatchFacts::has_shape_barrier_sites`, +//! which `collect_module_dispatch_facts` +//! (`collectors/scalar_method_dispatch.rs`) computes **per module**. A +//! `delete` / `Reflect.deleteProperty` in a module +//! that never declares `class_name` sets no flag this admission check can +//! see. Unlike a Phase 3b `Ptr` LOCAL — whose containment (rule 2, +//! `ptr_shape.rs`) proves no alias to the object can exist ANYWHERE, in this +//! module or any other — a proven `this` is the caller's object and is +//! aliased by construction: it can be handed to another module, deleted +//! from there, and handed back. +//! +//! This is sound anyway. Every routing site that can call a `$pshape` clone +//! re-derives the guarantee itself, at the point it actually matters, rather +//! than trusting this admission-time fact to have seen the whole program: +//! +//! * `method_direct.fast` (`lower_call/method_override.rs`) sits behind +//! `js_method_direct_shape_guard` / `js_typed_feedback_method_direct_call_guard`, +//! whose contract includes `receiver.keys_array == expected_keys` — a raw +//! POINTER compare (`typed_feedback/guards.rs`). The only code path +//! `js_object_delete_field` has for a `GC_TYPE_OBJECT` instance with a +//! keys array clones a FRESH keys array and repoints `keys_array` at it +//! (`perry-runtime/src/object/delete_rest.rs`; `Reflect.deleteProperty` +//! shares the same function) — for ANY key, declared or not, from ANY +//! module. The pointer compare can therefore never pass on a post-delete +//! instance, regardless of what this admission check saw. +//! * The Phase 3b guard-free `Ptr` receiver arm needs no runtime +//! check at all, because rule 2's containment already rules out the alias +//! existing in the first place: creating one — `let other = o`, passing +//! `o` to ANY function, same module or not — is itself a disqualifying +//! use the containment walk sees directly. There is no alias left for a +//! `delete` anywhere to reach the object through. +//! * #7142's class-id dispatch-tower case +//! (`lower_call/property_get/dynamic_dispatch.rs::emit_tower_pshape_call`) +//! carries its own explicit re-check +//! (`class_field_inline_guard::emit_proven_shape_recheck`) for exactly +//! this reason — that function's doc comment cites this issue by number: a +//! static, module-scoped proof would have been "exactly the wrong +//! instrument" for a receiver that can be aliased across modules. +//! +//! So `has_shape_barrier_sites()` here is a **cost-control heuristic** — +//! whether emitting a clone is even worth it, given the module's own code +//! may never take a fast path to it — never the mechanism that makes routing +//! to one safe. A future 4th routing site must independently re-derive one +//! of the two guarantees above (a dominating keys-token recheck, or genuine +//! containment); it must NOT rely on this fact having seen a `delete` that, +//! by construction, may have happened in a module this one never looked at. +//! +//! Confirmed empirically, not just by proof-reading: +//! `test-files/test_issue_7143_delete_barrier_cross_module.ts` (+ +//! `test-files/fixtures/issue_7143_pkg/shared.ts`) is exactly this shape — a +//! `delete` in the importing module, then a call back into the declaring +//! module on the mutated instance — and the emitted `--trace llvm` IR shows +//! the `$pshape` call dominated by `js_typed_feedback_method_direct_call_guard` +//! as described above; the compiled binary's output matches +//! `node --experimental-strip-types` exactly. The +//! `guarded_pshape_call_site_is_preceded_by_a_keys_token_guard` test in +//! `proven_this_routing_tests.rs` pins the same invariant at the IR level so +//! a future change to the routing sites can't silently drop it. +//! //! Gated by `PERRY_PTR_SHAPE_THIS` (default on; `0`/`off`/`false` disables — //! keyed into the object cache). Also honours `PERRY_PTR_SHAPE_LOCALS`, since //! Phase 5a is an extension of the same `Ptr` proof. diff --git a/crates/perry-codegen/src/collectors/proven_this_routing_tests.rs b/crates/perry-codegen/src/collectors/proven_this_routing_tests.rs index 4aa9a5590d..2e7b296f0a 100644 --- a/crates/perry-codegen/src/collectors/proven_this_routing_tests.rs +++ b/crates/perry-codegen/src/collectors/proven_this_routing_tests.rs @@ -477,6 +477,82 @@ fn typed_clone_fallback_routes_to_proven_this_clone() { ); } +/// The text of the `define` block whose signature line contains +/// `name_contains` — same "def line, take until the closing brace" idiom +/// [`proven_this_clone_binds_its_receiver_slot`] uses to isolate a callee's +/// body, applied to a CALLER instead. +fn function_body(ir: &str, name_contains: &str) -> String { + let def_line = ir + .lines() + .position(|l| l.starts_with("define") && l.contains(name_contains)) + .unwrap_or_else(|| panic!("no `define` line containing {name_contains:?} in:\n{ir}")); + ir.lines() + .skip(def_line) + .take_while(|l| *l != "}") + .collect::>() + .join("\n") +} + +/// Soundness ratchet (#7143): `method_direct.fast`'s `$pshape` call site is +/// preceded, in its own function, by the keys-token shape guard. +/// +/// `guarded_site_module`'s `probe(c: Counter)` receiver (`c`) is a plain +/// typed PARAMETER — proven-`this`'s aliased-by-construction case, not a +/// Phase 3b `Ptr` local (no provenance, no containment). #7143 raised +/// exactly this shape: `ModuleDispatchFacts::has_shape_barrier_sites()` is +/// computed per module (`collect_module_dispatch_facts`) and can never see a +/// `delete` performed on `c`'s referent through an alias held by some OTHER +/// module — so if THIS call site's soundness rested on that admission-time +/// fact, a cross-module `delete` would let it read through stale fixed-slot +/// offsets. +/// +/// It does not rest on that fact. `emit_guarded_direct_method_call` +/// (`lower_call/method_override.rs`) unconditionally emits +/// `js_typed_feedback_method_direct_call_guard` (or, under `shape_only_guard`, +/// `js_method_direct_shape_guard`) BEFORE any block that can reach the clone +/// — both compare the receiver's live `keys_array` pointer against the +/// class's canonical `@perry_class_keys_*` token, and `delete`'s only code +/// path for a class instance always repoints `keys_array` at a freshly +/// cloned array (`perry-runtime/src/object/delete_rest.rs`), from ANY +/// module. See `collectors/proven_this.rs`'s "`delete` is aliased across +/// modules by construction" section for the full argument; this pins the +/// IR shape it depends on, the same way +/// `tower_route_is_guarded_by_the_class_keys_token` pins it for the #7142 +/// tower site. +/// +/// This checks TEXTUAL precedence within `probe`'s body rather than walking +/// block dominance (`tower_route_is_guarded_by_the_class_keys_token`'s +/// approach) because `probe` calls two methods sequentially and the typed-f64 +/// arm nests the clone's call another level deep behind its OWN per-argument +/// guard — precedence is the invariant that survives that nesting, and +/// `probe` is this fixture's only method-dispatching function, so it is also +/// the only place a `$pshape` callee name can appear in a `call` line. +#[test] +fn guarded_pshape_call_site_is_preceded_by_a_keys_token_guard() { + let ir = emit(&guarded_site_module(), false); + let calls = pshape_call_targets(&ir); + assert!( + !calls.is_empty(), + "nothing to check — no `$pshape` call emitted:\n{ir}" + ); + let probe = function_body(&ir, "__probe("); + for target in &calls { + let call_pos = probe.find(&format!("@{target}(")).unwrap_or_else(|| { + panic!("{target} is called somewhere in the module but not from `probe`:\n{ir}") + }); + let prefix = &probe[..call_pos]; + let guarded = prefix.contains("call i32 @js_typed_feedback_method_direct_call_guard(") + || prefix.contains("call i32 @js_method_direct_shape_guard("); + assert!( + guarded, + "{target}: no keys-token guard call precedes it in `probe` — a \ + post-`delete` receiver (deleted from through an alias in another \ + module, #7143) would reach this clone's stale fixed-slot loads \ + unguarded:\n{probe}" + ); + } +} + /// Regression (#7128), Phase 3b guard-free site: a shape-proven LOCAL whose /// method also has a typed-receiver clone must still route to the proven-`this` /// clone. diff --git a/crates/perry-codegen/src/collectors/ptr_shape.rs b/crates/perry-codegen/src/collectors/ptr_shape.rs index abc1ec8f1f..12efaf294f 100644 --- a/crates/perry-codegen/src/collectors/ptr_shape.rs +++ b/crates/perry-codegen/src/collectors/ptr_shape.rs @@ -84,7 +84,14 @@ //! conservative first-increment rule from the RFC §5.2 discussion and //! still covers the common barrier-free module. (`eval` needs no kill: //! Perry never executes a runtime code string — see -//! `perry-hir/src/eval_classifier.rs`.) +//! `perry-hir/src/eval_classifier.rs`.) This same per-module fact is also +//! consulted by Phase 5a's proven-`this` admission +//! (`collectors/proven_this.rs`), where it is a cost heuristic ONLY — +//! unlike here, where rule 2's containment is the actual correctness +//! argument, Phase 5a's receiver is aliased across modules by +//! construction, so its call sites carry their own runtime protection +//! instead (`collectors/proven_this.rs`'s "`delete` is aliased across +//! modules by construction" section, #7143). //! //! **One exemption** (#7139, `collectors/cjs_scaffolding.rs`): the two //! `defineProperty` sites every `cjs_wrap`-compiled CommonJS module diff --git a/test-files/fixtures/issue_7143_pkg/shared.ts b/test-files/fixtures/issue_7143_pkg/shared.ts new file mode 100644 index 0000000000..bd54669a13 --- /dev/null +++ b/test-files/fixtures/issue_7143_pkg/shared.ts @@ -0,0 +1,28 @@ +// #7143 repro fixture: module A. Declares class C (three declared fields) +// and a method that reads a declared field through `this` — eligible for the +// Phase 5a proven-`this` `$pshape` clone as long as THIS module contains no +// `delete`/`Reflect.deleteProperty`/other §5.2 shape-barrier expression. +// +// `readViaA` is the call site that matters: `inst` is a plain typed +// parameter (NOT a locally-`new`'d Ptr local), so the receiver is +// aliased by construction — exactly the #7143 scenario — and the call +// routes through the GUARDED `method_direct.fast` site +// (`lower_call/method_override.rs`), not the containment-proof Phase 3b +// `Ptr` receiver arm. +export class C { + a: number = 1; + b: number = 2; + c: number = 3; + + readC(): number { + return this.c; + } +} + +export function makeC(): C { + return new C(); +} + +export function readViaA(inst: C): number { + return inst.readC(); +} diff --git a/test-files/test_issue_7143_delete_barrier_cross_module.ts b/test-files/test_issue_7143_delete_barrier_cross_module.ts new file mode 100644 index 0000000000..d1dd87ac7e --- /dev/null +++ b/test-files/test_issue_7143_delete_barrier_cross_module.ts @@ -0,0 +1,21 @@ +// #7143: the Phase 5a `delete` shape barrier is computed per-module +// (`collect_module_dispatch_facts`), but a proven-`this` receiver is +// aliased across modules by construction. This module (B) never mentions +// `delete`, so module A (fixtures/issue_7143_pkg/shared.ts) is free to admit +// a `readC$pshape` clone for `C.readC` and route `readViaA`'s call to it. +// This module DOES `delete` a declared field off an instance A handed it, +// then calls back into A's `readViaA`, which dispatches `inst.readC()` from +// inside module A on the now-mutated object. +import { + C, + makeC, + readViaA, +} from "./fixtures/issue_7143_pkg/shared.ts"; + +const inst = makeC(); +console.log("before delete:", readViaA(inst)); + +delete (inst as any).b; + +console.log("direct field read after delete:", (inst as any).c); +console.log("via module A after delete:", readViaA(inst)); From d28207d4b079be8574a3cb73078777d039e7537d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 9 Aug 2026 19:17:33 +0200 Subject: [PATCH 2/4] docs(changelog): key the fragment on PR #7722 Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix --- ...{7143-delete-barrier-scope.md => 7722-delete-barrier-scope.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{7143-delete-barrier-scope.md => 7722-delete-barrier-scope.md} (100%) diff --git a/changelog.d/7143-delete-barrier-scope.md b/changelog.d/7722-delete-barrier-scope.md similarity index 100% rename from changelog.d/7143-delete-barrier-scope.md rename to changelog.d/7722-delete-barrier-scope.md From fc3eb7c95354cc4739de797dbfe2e0e1db52cf37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 9 Aug 2026 19:17:35 +0200 Subject: [PATCH 3/4] chore: bump version to 0.5.1417 Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix --- CLAUDE.md | 2 +- Cargo.lock | 152 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 3 files changed, 78 insertions(+), 78 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1f6ca5b5c9..e98ba1d742 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1416 +**Current Version:** 0.5.1417 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index 521a8cf991..2981170c1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5547,7 +5547,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "base64", @@ -5607,14 +5607,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "serde", ] [[package]] name = "perry-audio-miniaudio" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "cc", "libc", @@ -5622,7 +5622,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "inkwell", @@ -5639,7 +5639,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "perry-hir", @@ -5647,7 +5647,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "perry-hir", @@ -5655,7 +5655,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "perry-dispatch", @@ -5664,7 +5664,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "perry-hir", @@ -5672,7 +5672,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "base64", @@ -5684,7 +5684,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "perry-hir", @@ -5692,7 +5692,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "async-trait", @@ -5721,14 +5721,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "serde", "serde_json", @@ -5736,7 +5736,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1416" +version = "0.5.1417" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5747,7 +5747,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "clap", @@ -5762,7 +5762,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "block2", "objc2", @@ -5772,7 +5772,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "argon2", "perry-ffi", @@ -5780,7 +5780,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", "reqwest", @@ -5789,7 +5789,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "bcrypt", "perry-ffi", @@ -5797,7 +5797,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", "rusqlite", @@ -5805,7 +5805,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", "scraper", @@ -5813,7 +5813,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", "perry-runtime", @@ -5821,7 +5821,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "chrono", "cron", @@ -5831,7 +5831,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "chrono", "perry-ffi", @@ -5839,7 +5839,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", "rust_decimal", @@ -5847,7 +5847,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", "serde_json", @@ -5855,7 +5855,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5863,7 +5863,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", "perry-runtime", @@ -5871,14 +5871,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "bytes", "http-body-util", @@ -5896,7 +5896,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "bytes", "lazy_static", @@ -5909,7 +5909,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "bytes", "h2", @@ -5933,7 +5933,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "lazy_static", "perry-ffi", @@ -5943,7 +5943,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "base64", "jsonwebtoken", @@ -5954,7 +5954,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "lru", "perry-ffi", @@ -5963,7 +5963,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "chrono", "perry-ffi", @@ -5971,7 +5971,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "bson", "futures-util", @@ -5983,7 +5983,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "chrono", "perry-ffi", @@ -5993,7 +5993,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "nanoid", "perry-ffi", @@ -6002,7 +6002,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "bytes", "perry-ffi", @@ -6015,7 +6015,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "const-oid 0.9.6", "der 0.7.10", @@ -6034,7 +6034,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "lettre", "perry-ffi", @@ -6044,7 +6044,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", "printpdf", @@ -6052,7 +6052,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", "sqlx", @@ -6061,7 +6061,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "governor", "perry-ffi", @@ -6069,7 +6069,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "fast_image_resize", "image", @@ -6079,14 +6079,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "lazy_static", "perry-ffi", @@ -6095,7 +6095,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", "perry-runtime", @@ -6104,7 +6104,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", "uuid", @@ -6112,7 +6112,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ffi", "regex", @@ -6122,7 +6122,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "futures-util", "lazy_static", @@ -6135,7 +6135,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "brotli", "flate2", @@ -6145,7 +6145,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "dashmap", "once_cell", @@ -6154,7 +6154,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "perry-api-manifest", @@ -6172,7 +6172,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "perry-diagnostics", @@ -6184,7 +6184,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "base64", @@ -6226,14 +6226,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6328,14 +6328,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "perry-hir", @@ -6344,14 +6344,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "base64", "itoa", @@ -6368,7 +6368,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "rand 0.10.1", "serde", @@ -6378,7 +6378,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "base64", "cairo-rs 0.22.0", @@ -6401,7 +6401,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "base64", "block2", @@ -6417,7 +6417,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "base64", "block2", @@ -6432,7 +6432,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1416" +version = "0.5.1417" [[package]] name = "perry-ui-test" @@ -6443,11 +6443,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1416" +version = "0.5.1417" [[package]] name = "perry-ui-tvos" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "base64", "block2", @@ -6463,7 +6463,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "base64", "block2", @@ -6479,7 +6479,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "block2", "libc", @@ -6492,7 +6492,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "base64", "libc", @@ -6509,14 +6509,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "anyhow", "base64", @@ -6532,7 +6532,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1416" +version = "0.5.1417" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 089d3ca889..8ffb5d7835 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,7 +315,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1416" +version = "0.5.1417" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" From 209e7e9d60a6394c1cc5ae34a24567cfeb004e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 9 Aug 2026 19:25:58 +0200 Subject: [PATCH 4/4] docs(repsel): condense the #7143 note to stay under the 2000-line cap Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix --- crates/perry-codegen/src/collectors/ptr_shape.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/perry-codegen/src/collectors/ptr_shape.rs b/crates/perry-codegen/src/collectors/ptr_shape.rs index 12efaf294f..e2a93074d7 100644 --- a/crates/perry-codegen/src/collectors/ptr_shape.rs +++ b/crates/perry-codegen/src/collectors/ptr_shape.rs @@ -84,14 +84,11 @@ //! conservative first-increment rule from the RFC §5.2 discussion and //! still covers the common barrier-free module. (`eval` needs no kill: //! Perry never executes a runtime code string — see -//! `perry-hir/src/eval_classifier.rs`.) This same per-module fact is also -//! consulted by Phase 5a's proven-`this` admission -//! (`collectors/proven_this.rs`), where it is a cost heuristic ONLY — -//! unlike here, where rule 2's containment is the actual correctness -//! argument, Phase 5a's receiver is aliased across modules by -//! construction, so its call sites carry their own runtime protection -//! instead (`collectors/proven_this.rs`'s "`delete` is aliased across -//! modules by construction" section, #7143). +//! `perry-hir/src/eval_classifier.rs`.) Phase 5a consults this same +//! per-module fact as a COST HEURISTIC only: its receiver is aliased across +//! modules by construction, so its call sites carry runtime keys-token +//! guards instead. Here, rule 2's containment IS the correctness argument +//! (#7143, `collectors/proven_this.rs`). //! //! **One exemption** (#7139, `collectors/cjs_scaffolding.rs`): the two //! `defineProperty` sites every `cjs_wrap`-compiled CommonJS module