From 393d68d3b05a28969ffad0431cbb488f38b5872f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 12:15:25 +0200 Subject: [PATCH 01/14] fix(ci): restore release readiness gates --- .github/workflows/test.yml | 7 +- CLAUDE.md | 2 +- Cargo.lock | 152 +++++++++--------- Cargo.toml | 2 +- changelog.d/0000-release-readiness-gates.md | 5 + crates/perry-codegen/src/expr/property_set.rs | 4 +- crates/perry-runtime/src/eh.rs | 7 +- crates/perry-runtime/src/eh_walker.rs | 9 +- crates/perry-runtime/src/gc/oldgen.rs | 1 + crates/perry-runtime/src/gc/roots.rs | 1 + .../perry-runtime/src/gc/roots/stack_maps.rs | 9 +- .../gc/roots/stack_maps_unwind_contract.rs | 5 +- .../src/gc/roots/stack_maps_verify.rs | 1 + crates/perry-runtime/src/gc/trace.rs | 1 + crates/perry-runtime/src/json_tape.rs | 5 +- .../src/object/class_meta_registry.rs | 9 -- crates/perry-runtime/src/object/spill.rs | 14 +- crates/perry-runtime/src/object/tests.rs | 10 +- crates/perry-runtime/src/timer.rs | 37 +---- .../src/timer/drain_expired_tests.rs | 34 ++++ .../perry/src/commands/compile/build_cache.rs | 1 + .../src/commands/compile/shared_tokio.rs | 19 ++- crates/perry/src/commands/publish/mod.rs | 6 +- crates/perry/src/update_checker.rs | 13 -- scripts/addr_class_ratchet_baseline.txt | 4 +- scripts/global_sink_isolation.py | 24 ++- scripts/raw_handle_debt_baseline.txt | 2 +- scripts/raw_handle_debt_files.txt | 14 +- 28 files changed, 204 insertions(+), 194 deletions(-) create mode 100644 changelog.d/0000-release-readiness-gates.md create mode 100644 crates/perry-runtime/src/timer/drain_expired_tests.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 21199951f3..96c2d09437 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1318,7 +1318,12 @@ jobs: --target windows-aarch64 ` --no-auto-optimize ` -o target/windows-arm64-smoke.exe - $output = target/windows-arm64-smoke.exe + # In expression position PowerShell treats a relative path as a + # command name and does not search the working directory. Invoke the + # freshly linked binary explicitly so the ARM64 gate tests the + # artifact instead of failing in shell dispatch. + $smoke = (Resolve-Path 'target/windows-arm64-smoke.exe').Path + $output = & $smoke if ($output.Trim() -ne 'ok') { throw "unexpected ARM64 smoke output: '$output'" } diff --git a/CLAUDE.md b/CLAUDE.md index 7fd82f9a87..3c43c63040 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.1510 +**Current Version:** 0.5.1511 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index ad6dd57745..940143335c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5537,7 +5537,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "base64 0.22.1", @@ -5597,7 +5597,7 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-dispatch", "serde", @@ -5605,7 +5605,7 @@ dependencies = [ [[package]] name = "perry-audio-miniaudio" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "cc", "libc", @@ -5613,7 +5613,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "inkwell", @@ -5630,7 +5630,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "perry-hir", @@ -5638,7 +5638,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "perry-hir", @@ -5646,7 +5646,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "perry-dispatch", @@ -5655,7 +5655,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "perry-hir", @@ -5663,7 +5663,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "base64 0.22.1", @@ -5675,7 +5675,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "perry-hir", @@ -5683,7 +5683,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "async-trait", @@ -5712,14 +5712,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "serde", "serde_json", @@ -5727,7 +5727,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1510" +version = "0.5.1511" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5738,7 +5738,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "clap", @@ -5753,7 +5753,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "block2", "objc2", @@ -5763,7 +5763,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "argon2", "perry-ffi", @@ -5771,7 +5771,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", "reqwest", @@ -5780,7 +5780,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "bcrypt", "perry-ffi", @@ -5788,7 +5788,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", "rusqlite", @@ -5796,7 +5796,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", "scraper", @@ -5804,7 +5804,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", "perry-runtime", @@ -5812,7 +5812,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "chrono", "cron", @@ -5822,7 +5822,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "chrono", "perry-ffi", @@ -5830,7 +5830,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", "rust_decimal", @@ -5838,7 +5838,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", "serde_json", @@ -5846,7 +5846,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5854,7 +5854,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", "perry-runtime", @@ -5862,14 +5862,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "bytes", "http-body-util", @@ -5887,7 +5887,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "bytes", "lazy_static", @@ -5900,7 +5900,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "bytes", "h2", @@ -5924,7 +5924,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "lazy_static", "perry-ffi", @@ -5934,7 +5934,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "base64 0.22.1", "jsonwebtoken", @@ -5945,7 +5945,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "lru", "perry-ffi", @@ -5954,7 +5954,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "chrono", "perry-ffi", @@ -5962,7 +5962,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "bson", "futures-util", @@ -5974,7 +5974,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "chrono", "perry-ffi", @@ -5984,7 +5984,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "nanoid", "perry-ffi", @@ -5993,7 +5993,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "bytes", "perry-ffi", @@ -6006,7 +6006,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "const-oid 0.9.6", "der 0.7.10", @@ -6025,7 +6025,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "lettre", "perry-ffi", @@ -6035,7 +6035,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", "printpdf", @@ -6043,7 +6043,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", "sqlx", @@ -6052,7 +6052,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "governor", "perry-ffi", @@ -6060,7 +6060,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "fast_image_resize", "image", @@ -6070,14 +6070,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "lazy_static", "perry-ffi", @@ -6086,7 +6086,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", "perry-runtime", @@ -6095,7 +6095,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", "uuid", @@ -6103,7 +6103,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ffi", "regex", @@ -6113,7 +6113,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "futures-util", "lazy_static", @@ -6126,7 +6126,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "brotli", "flate2", @@ -6136,7 +6136,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "dashmap", "once_cell", @@ -6145,7 +6145,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "perry-api-manifest", @@ -6163,7 +6163,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "perry-diagnostics", @@ -6175,7 +6175,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "base64 0.22.1", @@ -6217,14 +6217,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6319,14 +6319,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "perry-hir", @@ -6335,14 +6335,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "base64 0.22.1", "itoa", @@ -6359,7 +6359,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "rand 0.10.1", "serde", @@ -6369,7 +6369,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "base64 0.22.1", "cairo-rs 0.22.0", @@ -6392,7 +6392,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "base64 0.22.1", "block2", @@ -6408,7 +6408,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "base64 0.22.1", "block2", @@ -6423,7 +6423,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1510" +version = "0.5.1511" [[package]] name = "perry-ui-test" @@ -6434,11 +6434,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1510" +version = "0.5.1511" [[package]] name = "perry-ui-tvos" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "base64 0.22.1", "block2", @@ -6454,7 +6454,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "base64 0.22.1", "block2", @@ -6470,7 +6470,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "block2", "libc", @@ -6483,7 +6483,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "base64 0.22.1", "libc", @@ -6500,14 +6500,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "anyhow", "base64 0.22.1", @@ -6523,7 +6523,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1510" +version = "0.5.1511" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 77391ac8fb..9db28d37c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,7 +315,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1510" +version = "0.5.1511" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/changelog.d/0000-release-readiness-gates.md b/changelog.d/0000-release-readiness-gates.md new file mode 100644 index 0000000000..57cacc0654 --- /dev/null +++ b/changelog.d/0000-release-readiness-gates.md @@ -0,0 +1,5 @@ +Restore the pre-release CI gates: eliminate Rust warning failures, make the +structural lint audits reflect realm-local storage and current GC store sites, +tighten raw-handle debt across allocation windows, split the oversized timer +module, and execute the Windows ARM64 smoke artifact through PowerShell's +explicit invocation path. diff --git a/crates/perry-codegen/src/expr/property_set.rs b/crates/perry-codegen/src/expr/property_set.rs index de87b5590d..4b94159c5c 100644 --- a/crates/perry-codegen/src/expr/property_set.rs +++ b/crates/perry-codegen/src/expr/property_set.rs @@ -1464,14 +1464,14 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // Guarded raw-f64 slots are pointer-free by typed // shape descriptor; non-number writes miss the // guard and use the boxed setter fallback. - // GC_STORE_AUDIT(POINTER_FREE): typed raw-f64 class - // slots contain numbers only. let blk = ctx.block(); let numeric_value = canonicalize_raw_f64_numeric_store_value( blk, &val_double, ); + // GC_STORE_AUDIT(POINTER_FREE): typed raw-f64 class + // slots contain numbers only. blk.store(DOUBLE, &numeric_value, &field_ptr); Some(numeric_value) } else { diff --git a/crates/perry-runtime/src/eh.rs b/crates/perry-runtime/src/eh.rs index 052185f983..45460fb134 100644 --- a/crates/perry-runtime/src/eh.rs +++ b/crates/perry-runtime/src/eh.rs @@ -66,7 +66,7 @@ extern "C" { fn _Unwind_SetIP(ctx: *mut UnwindContext, value: usize); fn _Unwind_GetCFA(ctx: *mut UnwindContext) -> usize; fn _Unwind_Backtrace( - trace: extern "C" fn(*mut UnwindContext, *mut core::ffi::c_void) -> UnwindReasonCode, + trace: unsafe extern "C" fn(*mut UnwindContext, *mut core::ffi::c_void) -> UnwindReasonCode, arg: *mut core::ffi::c_void, ) -> UnwindReasonCode; } @@ -113,7 +113,10 @@ fn selfcheck_frame_a() -> usize { #[inline(never)] fn selfcheck_frame_b() -> usize { - extern "C" fn count(_ctx: *mut UnwindContext, arg: *mut core::ffi::c_void) -> UnwindReasonCode { + unsafe extern "C" fn count( + _ctx: *mut UnwindContext, + arg: *mut core::ffi::c_void, + ) -> UnwindReasonCode { unsafe { *(arg as *mut usize) += 1 }; // _URC_NO_REASON: the ONLY value that lets _Unwind_Backtrace keep // walking — any other reason code stops the trace after one frame. diff --git a/crates/perry-runtime/src/eh_walker.rs b/crates/perry-runtime/src/eh_walker.rs index ca11754a7f..a44f940f85 100644 --- a/crates/perry-runtime/src/eh_walker.rs +++ b/crates/perry-runtime/src/eh_walker.rs @@ -918,17 +918,18 @@ mod tests { /// Collect frame PCs via the SYSTEM unwinder (_Unwind_Backtrace) — /// the oracle the owned walk must match. fn system_pcs(max: usize) -> Vec { + use crate::eh::UnwindContext; use core::ffi::{c_int, c_void}; unsafe extern "C" { fn _Unwind_Backtrace( - trace: extern "C" fn(*mut c_void, *mut c_void) -> c_int, + trace: unsafe extern "C" fn(*mut UnwindContext, *mut c_void) -> c_int, arg: *mut c_void, ) -> c_int; - fn _Unwind_GetIP(ctx: *mut c_void) -> u64; + fn _Unwind_GetIP(ctx: *mut UnwindContext) -> usize; } - extern "C" fn cb(ctx: *mut c_void, arg: *mut c_void) -> c_int { + unsafe extern "C" fn cb(ctx: *mut UnwindContext, arg: *mut c_void) -> c_int { let v = unsafe { &mut *(arg as *mut Vec) }; - unsafe { v.push(_Unwind_GetIP(ctx)) }; + unsafe { v.push(_Unwind_GetIP(ctx) as u64) }; 0 } let mut v: Vec = Vec::with_capacity(max); diff --git a/crates/perry-runtime/src/gc/oldgen.rs b/crates/perry-runtime/src/gc/oldgen.rs index af77f511aa..ab240b8851 100644 --- a/crates/perry-runtime/src/gc/oldgen.rs +++ b/crates/perry-runtime/src/gc/oldgen.rs @@ -1836,6 +1836,7 @@ pub(super) fn evacuate_tenured_nursery_objects_collecting( evacuated } +#[cfg(test)] pub(super) fn old_object_pages_all_selected( header: *mut GcHeader, total_size: usize, diff --git a/crates/perry-runtime/src/gc/roots.rs b/crates/perry-runtime/src/gc/roots.rs index b8a903a131..5382516714 100644 --- a/crates/perry-runtime/src/gc/roots.rs +++ b/crates/perry-runtime/src/gc/roots.rs @@ -272,6 +272,7 @@ pub(super) fn gc_register_mutable_root_scanner_named_with_source( }); } +#[cfg(test)] pub(super) fn gc_register_budgeted_mutable_root_scanner_with_source( scanner: MutableRootScanner, budgeted_scanner: BudgetedMutableRootScanner, diff --git a/crates/perry-runtime/src/gc/roots/stack_maps.rs b/crates/perry-runtime/src/gc/roots/stack_maps.rs index e142f53708..87e3c19f7a 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps.rs @@ -1340,10 +1340,7 @@ fn loaded_stack_map_section() -> Option<&'static [u8]> { mod unwind { use super::*; - #[repr(C)] - struct UnwindContext { - _private: [u8; 0], - } + type UnwindContext = crate::eh::UnwindContext; unsafe extern "C" { fn _Unwind_Backtrace( @@ -1724,8 +1721,8 @@ mod fp_chain { #[cfg(target_vendor = "apple")] fn stack_top() -> usize { unsafe extern "C" { - fn pthread_self() -> usize; - fn pthread_get_stackaddr_np(thread: usize) -> *mut c_void; + fn pthread_self() -> *mut c_void; + fn pthread_get_stackaddr_np(thread: *mut c_void) -> *mut c_void; } unsafe { pthread_get_stackaddr_np(pthread_self()) as usize } } diff --git a/crates/perry-runtime/src/gc/roots/stack_maps_unwind_contract.rs b/crates/perry-runtime/src/gc/roots/stack_maps_unwind_contract.rs index db420dd2aa..fe8f4ea0ad 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps_unwind_contract.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps_unwind_contract.rs @@ -26,10 +26,7 @@ use std::ffi::c_void; use std::hint::black_box; -#[repr(C)] -struct UnwindContext { - _private: [u8; 0], -} +use crate::eh::UnwindContext; unsafe extern "C" { fn _Unwind_Backtrace( diff --git a/crates/perry-runtime/src/gc/roots/stack_maps_verify.rs b/crates/perry-runtime/src/gc/roots/stack_maps_verify.rs index 4e9d4a0e0d..55a595ffe9 100644 --- a/crates/perry-runtime/src/gc/roots/stack_maps_verify.rs +++ b/crates/perry-runtime/src/gc/roots/stack_maps_verify.rs @@ -176,6 +176,7 @@ fn describe(out: &mut String, index: &StackMapIndex, root: &ResolvedRoot) { /// of the very data under suspicion. This is the same set the walker's /// `match_records` containment check consults, so a dump gated on it reads /// only what the walk already read. +#[cfg(any(target_arch = "aarch64", test))] fn map_vouches_for(index: &StackMapIndex, function_address: usize) -> bool { index .function_starts diff --git a/crates/perry-runtime/src/gc/trace.rs b/crates/perry-runtime/src/gc/trace.rs index d89fd72a2b..8c570588fe 100644 --- a/crates/perry-runtime/src/gc/trace.rs +++ b/crates/perry-runtime/src/gc/trace.rs @@ -886,6 +886,7 @@ thread_local! { /// across a collection: an unchanged value means block persistence force-marked /// nothing, so an object left unmarked by that collection was genuinely /// unreachable rather than merely a neighbour of something reachable. +#[cfg(test)] pub(crate) fn block_persist_force_mark_count() -> u64 { BLOCK_PERSIST_FORCE_MARKS.with(std::cell::Cell::get) } diff --git a/crates/perry-runtime/src/json_tape.rs b/crates/perry-runtime/src/json_tape.rs index 1c0c1ef7a0..8a5e0f7e79 100644 --- a/crates/perry-runtime/src/json_tape.rs +++ b/crates/perry-runtime/src/json_tape.rs @@ -1253,7 +1253,8 @@ pub unsafe fn alloc_lazy_array( // which can trigger, but the only live thing we hold across it is // `blob_handle`, which is rooted. let (tape_ptr, tape_allocation) = crate::json_tape_store::allocate(tape_entries); - let raw = alloc_lazy_header_bytes(); + let (raw, blob_str) = + blob_handle.across_const::(alloc_lazy_header_bytes); let hdr = raw as *mut LazyArrayHeader; (*hdr).cached_length = cached_length; (*hdr).magic = LAZY_ARRAY_MAGIC; @@ -1262,7 +1263,7 @@ pub unsafe fn alloc_lazy_array( // GC_STORE_AUDIT(POINTER_FREE): side-allocated tape bytes, not a heap edge — // no barrier, and deliberately absent from the LazyArray rewrite descriptor. (*hdr).tape = tape_ptr; - (*hdr).blob_str = blob_handle.get_raw_const_ptr::(); + (*hdr).blob_str = blob_str; (*hdr).materialized = std::ptr::null_mut(); (*hdr).materialized_elements = std::ptr::null_mut(); (*hdr).materialized_bitmap = std::ptr::null_mut(); diff --git a/crates/perry-runtime/src/object/class_meta_registry.rs b/crates/perry-runtime/src/object/class_meta_registry.rs index 14d9f3558e..a25febba36 100644 --- a/crates/perry-runtime/src/object/class_meta_registry.rs +++ b/crates/perry-runtime/src/object/class_meta_registry.rs @@ -94,15 +94,6 @@ pub(crate) fn get_parent_class_id(class_id: u32) -> Option { registry.as_ref().and_then(|r| r.get(&class_id).copied()) } -/// Test-only reset of the dense mirror, for suites that clear `CLASS_REGISTRY` -/// between cases. -#[cfg(test)] -pub(crate) fn parent_dense_clear() { - for slot in PARENT_DENSE.iter() { - slot.store(0, Ordering::Release); - } -} - /// class_id -> fetch-builtin parent kind (1 = Request, 2 = Response). Recorded /// when a class is registered (at module init / class-expression evaluation) /// whose parent value identifies as the global `Request`/`Response` diff --git a/crates/perry-runtime/src/object/spill.rs b/crates/perry-runtime/src/object/spill.rs index 03bed6666c..91e28dc3f3 100644 --- a/crates/perry-runtime/src/object/spill.rs +++ b/crates/perry-runtime/src/object/spill.rs @@ -180,16 +180,15 @@ pub(crate) fn reserve_object_spill(obj_ptr: usize, field_count: u32) { let scope = crate::gc::RuntimeHandleScope::new(); let obj_handle = scope.root_raw_mut_ptr(obj); - object_meta_ensure(obj); - - let obj = obj_handle.get_raw_mut_ptr::(); + let (_, obj) = obj_handle.across_mut::(|| object_meta_ensure(obj)); let meta = (*obj).meta; if (*meta).spill != 0 { return; } - let spill = crate::array::js_array_alloc_with_length_exact(field_count); - let obj = obj_handle.get_raw_mut_ptr::(); + let (spill, obj) = obj_handle.across_mut::(|| { + crate::array::js_array_alloc_with_length_exact(field_count) + }); let meta = (*obj).meta; if (*meta).spill == 0 { (*meta).spill = spill as u64; @@ -285,8 +284,9 @@ fn spill_set_slow(obj_ptr: usize, field_index: usize, vbits: u64) { // `js_array_set` can never trigger array growth/forwarding — // `meta.spill` always points at the live block (GC rewrites it // as a child edge on evacuation). - let new_spill = crate::array::js_array_alloc_with_length(new_cap); - let obj = obj_handle.get_raw_mut_ptr::(); + let (new_spill, obj) = obj_handle.across_mut::(|| { + crate::array::js_array_alloc_with_length(new_cap) + }); let meta = (*obj).meta; let old = (*meta).spill as *const crate::array::ArrayHeader; if !old.is_null() { diff --git a/crates/perry-runtime/src/object/tests.rs b/crates/perry-runtime/src/object/tests.rs index 972092a3c8..3f4089449d 100644 --- a/crates/perry-runtime/src/object/tests.rs +++ b/crates/perry-runtime/src/object/tests.rs @@ -1534,7 +1534,7 @@ fn array_receiver_is_never_read_as_a_class_id() { #[test] fn constructor_ref_method_value_resolves_static_over_instance_method() { // Unique id so the process-global registries don't collide with other tests. - const CLASS_ID: u32 = 0x7689; + const LEX_METHOD_TEST_CLASS_ID: u32 = 0x7689; const NAME: &[u8] = b"lex"; extern "C" fn static_lex_7689() -> f64 { @@ -1546,7 +1546,7 @@ fn constructor_ref_method_value_resolves_static_over_instance_method() { unsafe { super::class_registry::js_register_class_method( - CLASS_ID as i64, + LEX_METHOD_TEST_CLASS_ID as i64, NAME.as_ptr(), NAME.len() as i64, instance_lex_7689 as *const () as usize as i64, @@ -1555,7 +1555,7 @@ fn constructor_ref_method_value_resolves_static_over_instance_method() { 0, ); super::class_registry::js_register_class_static_method( - CLASS_ID as i64, + LEX_METHOD_TEST_CLASS_ID as i64, NAME.as_ptr(), NAME.len() as i64, static_lex_7689 as *const () as usize as i64, @@ -1564,7 +1564,7 @@ fn constructor_ref_method_value_resolves_static_over_instance_method() { ); } - let class_ref = super::native_module::class_constructor_ref_value(CLASS_ID); + let class_ref = super::native_module::class_constructor_ref_value(LEX_METHOD_TEST_CLASS_ID); let bound = super::native_module::js_class_method_bind(class_ref, NAME.as_ptr(), NAME.len()); let result = unsafe { crate::closure::js_native_call_value(bound, std::ptr::null(), 0) }; assert_eq!( @@ -1575,7 +1575,7 @@ fn constructor_ref_method_value_resolves_static_over_instance_method() { // The guard must not over-narrow: the PROTOTYPE ref names the instance // method, and an extracted `C.prototype.lex` must keep resolving it. - let proto_ref = super::native_module::class_prototype_ref_value(CLASS_ID); + let proto_ref = super::native_module::class_prototype_ref_value(LEX_METHOD_TEST_CLASS_ID); let bound_proto = super::native_module::js_class_method_bind(proto_ref, NAME.as_ptr(), NAME.len()); let result_proto = diff --git a/crates/perry-runtime/src/timer.rs b/crates/perry-runtime/src/timer.rs index b58601a5fd..7d3c712ff6 100644 --- a/crates/perry-runtime/src/timer.rs +++ b/crates/perry-runtime/src/timer.rs @@ -1895,42 +1895,7 @@ pub(crate) fn test_clear_timer_scanner_roots(promise_before: usize, promise_afte } #[cfg(test)] -mod drain_expired_tests { - use super::drain_expired_timers; - - /// The single-pass partition must preserve the original order of BOTH - /// halves: expired entries fire in creation order (same-deadline Node - /// semantics) and survivors keep queue order for the next tick. - #[test] - fn timer_drain_partition_preserves_order() { - // (id, cleared, expired) - let mut queue = vec![ - (1, false, true), - (2, false, false), - (3, true, true), - (4, false, true), - (5, false, false), - (6, true, false), - (7, false, true), - ]; - let expired = drain_expired_timers(&mut queue, |t| t.1, |t| t.2); - let expired_ids: Vec = expired.iter().map(|t| t.0).collect(); - let kept_ids: Vec = queue.iter().map(|t| t.0).collect(); - assert_eq!(expired_ids, vec![1, 4, 7], "expired keep creation order"); - assert_eq!(kept_ids, vec![2, 5], "survivors keep queue order"); - } - - #[test] - fn timer_drain_partition_empty_and_all_expired() { - let mut empty: Vec = Vec::new(); - assert!(drain_expired_timers(&mut empty, |_| false, |_| true).is_empty()); - - let mut queue = vec![10, 20, 30]; - let expired = drain_expired_timers(&mut queue, |_| false, |_| true); - assert_eq!(expired, vec![10, 20, 30]); - assert!(queue.is_empty()); - } -} +mod drain_expired_tests; #[cfg(test)] mod expired_batch_order_tests { diff --git a/crates/perry-runtime/src/timer/drain_expired_tests.rs b/crates/perry-runtime/src/timer/drain_expired_tests.rs new file mode 100644 index 0000000000..50146aad6a --- /dev/null +++ b/crates/perry-runtime/src/timer/drain_expired_tests.rs @@ -0,0 +1,34 @@ +use super::drain_expired_timers; + +/// The single-pass partition must preserve the original order of BOTH +/// halves: expired entries fire in creation order (same-deadline Node +/// semantics) and survivors keep queue order for the next tick. +#[test] +fn timer_drain_partition_preserves_order() { + // (id, cleared, expired) + let mut queue = vec![ + (1, false, true), + (2, false, false), + (3, true, true), + (4, false, true), + (5, false, false), + (6, true, false), + (7, false, true), + ]; + let expired = drain_expired_timers(&mut queue, |t| t.1, |t| t.2); + let expired_ids: Vec = expired.iter().map(|t| t.0).collect(); + let kept_ids: Vec = queue.iter().map(|t| t.0).collect(); + assert_eq!(expired_ids, vec![1, 4, 7], "expired keep creation order"); + assert_eq!(kept_ids, vec![2, 5], "survivors keep queue order"); +} + +#[test] +fn timer_drain_partition_empty_and_all_expired() { + let mut empty: Vec = Vec::new(); + assert!(drain_expired_timers(&mut empty, |_| false, |_| true).is_empty()); + + let mut queue = vec![10, 20, 30]; + let expired = drain_expired_timers(&mut queue, |_| false, |_| true); + assert_eq!(expired, vec![10, 20, 30]); + assert!(queue.is_empty()); +} diff --git a/crates/perry/src/commands/compile/build_cache.rs b/crates/perry/src/commands/compile/build_cache.rs index a8b0fdf2e8..6682f4c04e 100644 --- a/crates/perry/src/commands/compile/build_cache.rs +++ b/crates/perry/src/commands/compile/build_cache.rs @@ -110,6 +110,7 @@ const BUILD_CACHE_ENV_VARS: &[&str] = &[ /// would thrash the cache rather than protect it. Anything not listed here must /// be a build-cache input; `codegen_env_vars_are_build_cache_inputs` enforces /// that, so a new compile-time gate cannot repeat #7161's omission silently. +#[cfg(test)] const BUILD_CACHE_ENV_EXCLUSIONS: &[&str] = &[ // Diagnostics: emit an extra file / extra stderr, same object bytes. "PERRY_SAVE_LL", diff --git a/crates/perry/src/commands/compile/shared_tokio.rs b/crates/perry/src/commands/compile/shared_tokio.rs index 11dc686530..a059bb9f32 100644 --- a/crates/perry/src/commands/compile/shared_tokio.rs +++ b/crates/perry/src/commands/compile/shared_tokio.rs @@ -127,27 +127,26 @@ pub(crate) fn archive_member_names(path: &Path) -> std::io::Result> .parse() .unwrap_or(0); let mut consumed: u64 = 0; - let mut name = raw_name.clone(); - if let Some(len_str) = raw_name.strip_prefix("#1/") { + let name = if let Some(len_str) = raw_name.strip_prefix("#1/") { // BSD extended name (what Apple's `ar` and rustc emit on macOS): // the real name is the first `len` bytes of the payload. let len: usize = len_str.trim().parse().unwrap_or(0); let mut buf = vec![0u8; len.min(size as usize)]; file.read_exact(&mut buf)?; consumed += buf.len() as u64; - name = String::from_utf8_lossy(&buf) + String::from_utf8_lossy(&buf) .trim_end_matches('\0') - .to_string(); + .to_string() } else if raw_name == "//" { // GNU long-name string table. Read it; its entries resolve the // `/` names below. gnu_strtab = vec![0u8; size as usize]; file.read_exact(&mut gnu_strtab)?; consumed += size; - name = String::new(); + String::new() } else if let Some(off_str) = raw_name.strip_prefix('/') { if let Ok(off) = off_str.trim().parse::() { - name = gnu_strtab + gnu_strtab .get(off..) .map(|rest| { let end = rest @@ -156,14 +155,14 @@ pub(crate) fn archive_member_names(path: &Path) -> std::io::Result> .unwrap_or(rest.len()); String::from_utf8_lossy(&rest[..end]).to_string() }) - .unwrap_or_default(); + .unwrap_or_default() } else { // `/` alone is the symbol table — not a real member name. - name = String::new(); + String::new() } } else { - name = raw_name.trim_end_matches('/').to_string(); - } + raw_name.trim_end_matches('/').to_string() + }; if !name.is_empty() { names.push(name); } diff --git a/crates/perry/src/commands/publish/mod.rs b/crates/perry/src/commands/publish/mod.rs index 5aa4825310..8edcfea2fc 100644 --- a/crates/perry/src/commands/publish/mod.rs +++ b/crates/perry/src/commands/publish/mod.rs @@ -35,9 +35,9 @@ pub use args::PublishArgs; #[cfg(test)] pub(crate) use saved_config::IosSavedConfig; // consumed only by tests pub(crate) use saved_config::{ - check_beta_consent, config_path, is_interactive, load_config, load_config_checked, - prompt_input, report_beta_error, save_config, update_config_file, AndroidSavedConfig, - AppleSavedConfig, HarmonyosSavedConfig, PerryConfig, + check_beta_consent, config_path, is_interactive, load_config, prompt_input, report_beta_error, + save_config, update_config_file, AndroidSavedConfig, AppleSavedConfig, HarmonyosSavedConfig, + PerryConfig, }; pub(crate) use tarball::create_project_tarball_with_filters; diff --git a/crates/perry/src/update_checker.rs b/crates/perry/src/update_checker.rs index f936660ae5..b3fc8d8b7f 100644 --- a/crates/perry/src/update_checker.rs +++ b/crates/perry/src/update_checker.rs @@ -198,19 +198,6 @@ pub fn parse_rfc3339(s: &str) -> Option { chrono_parse_rfc3339(s).map(|t| t as i64) } -pub fn should_skip_check() -> bool { - if std::env::var("PERRY_NO_UPDATE_CHECK").is_ok_and(|v| v == "1" || v == "true") { - return true; - } - if std::env::var("CI").is_ok_and(|v| v == "true" || v == "1") { - return true; - } - if !std::io::stderr().is_terminal() { - return true; - } - false -} - pub fn is_cache_stale() -> bool { is_cache_stale_with(CACHE_MAX_AGE) } diff --git a/scripts/addr_class_ratchet_baseline.txt b/scripts/addr_class_ratchet_baseline.txt index f1765f9eee..27c8cc7288 100644 --- a/scripts/addr_class_ratchet_baseline.txt +++ b/scripts/addr_class_ratchet_baseline.txt @@ -26,7 +26,7 @@ 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/header.rs | 3 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 @@ -270,7 +270,7 @@ 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/dyn_index.rs | 1 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 diff --git a/scripts/global_sink_isolation.py b/scripts/global_sink_isolation.py index 307ef0d0c5..8b4c9916ae 100644 --- a/scripts/global_sink_isolation.py +++ b/scripts/global_sink_isolation.py @@ -31,6 +31,8 @@ * `thread_local!` -> safe by construction * `per_test_global!` -> per-thread in test builds, safe + * `RealmAtomicI64` / `RealmAtomicU64` -> immutable handle to a + `perry_thread_local!` backing slot, safe * a bare `static` -> HAZARD A hazard fails the build unless it is named in ALLOWLIST below with the issue @@ -194,6 +196,13 @@ def declaration_kind(sources: dict, ident: str, prefer=None): # the fix. Same for the global allocator. if re.fullmatch(r"(std::sync::)?(Mutex|RwLock)\s*<\s*\(\s*\)\s*>", declared): return "lock", path + # These wrappers hold no mutable process-global value: their only + # field is a `&'static HotKey`, and every load/store goes + # through that `perry_thread_local!` backing slot. Treating the + # immutable handle as the data falsely reports a cross-test sink + # while ignoring the realm-local slot that actually owns it. + if re.fullmatch(r"RealmAtomic(?:I64|U64)", declared): + return "thread_local", path if "#[global_allocator]" in text[max(0, found.start() - 80) : found.start()]: return "allocator", path return "static", path @@ -310,6 +319,7 @@ def audit(sources: dict, support_text: str, allowlist: dict, out=sys.stdout, flo crate::demo::test_clear_bare(); crate::demo::test_clear_tls(); crate::demo::test_clear_paren(); + crate::demo::test_clear_realm_atomic(); } """ @@ -320,6 +330,7 @@ def audit(sources: dict, support_text: str, allowlist: dict, out=sys.stdout, flo per_test_global!(static PAREN_TABLE: Mutex = Mutex::new(0)); static PURE_LOCK: Mutex<()> = Mutex::new(()); static BARE_TABLE: Mutex = Mutex::new(0); +static REALM_CACHE: RealmAtomicU64 = RealmAtomicU64::new(&REALM_CACHE_SLOT); thread_local! { static TLS_TABLE: RefCell = RefCell::new(0); } @@ -327,6 +338,7 @@ def audit(sources: dict, support_text: str, allowlist: dict, out=sys.stdout, flo pub(crate) fn test_clear_bare() { *BARE_TABLE.lock().unwrap() = 0; } pub(crate) fn test_clear_tls() { TLS_TABLE.with(|t| *t.borrow_mut() = 0); } pub(crate) fn test_clear_paren() { *PAREN_TABLE.lock().unwrap() = 0; let _g = PURE_LOCK.lock(); } +pub(crate) fn test_clear_realm_atomic() { REALM_CACHE.with_slot(|slot| slot.store(0)); } """ @@ -374,7 +386,15 @@ def self_test() -> int: if any("PURE_LOCK" in v for v in audit(fake, _FAKE_SUPPORT, {"BARE_TABLE": "#1"}, sink)): failures.append("a Mutex<()> serializer was reported as a hazard") - # 3d. THE FLOOR: a matcher that stops matching must not read as clean. + # 3d. RealmAtomic wrappers are immutable handles whose mutable value lives + # in a perry_thread_local! HotKey. The wrapper itself is not a shared sink. + kind, _ = declaration_kind(fake, "REALM_CACHE") + if kind != "thread_local": + failures.append("REALM_CACHE classified as %r, not 'thread_local'" % (kind,)) + if any("REALM_CACHE" in v for v in audit(fake, _FAKE_SUPPORT, {"BARE_TABLE": "#1"}, sink)): + failures.append("a RealmAtomic thread-local proxy was reported as a hazard") + + # 3e. THE FLOOR: a matcher that stops matching must not read as clean. floored = audit(fake, _FAKE_SUPPORT, {"BARE_TABLE": "#1"}, sink, floor=99) if not any("below the floor" in v for v in floored): failures.append("the classified-statics floor did not fire: %r" % (floored,)) @@ -410,7 +430,7 @@ def self_test() -> int: for failure in failures: print("SELF-TEST FAIL: %s" % failure, file=sys.stderr) - print("self-test: 15 checks, %d failures" % len(failures)) + print("self-test: 17 checks, %d failures" % len(failures)) return 1 if failures else 0 diff --git a/scripts/raw_handle_debt_baseline.txt b/scripts/raw_handle_debt_baseline.txt index 806adbfbe9..7f9d7e97a5 100644 --- a/scripts/raw_handle_debt_baseline.txt +++ b/scripts/raw_handle_debt_baseline.txt @@ -1 +1 @@ -998 +992 diff --git a/scripts/raw_handle_debt_files.txt b/scripts/raw_handle_debt_files.txt index 99fa343bc1..0e60b90833 100644 --- a/scripts/raw_handle_debt_files.txt +++ b/scripts/raw_handle_debt_files.txt @@ -64,13 +64,16 @@ 3 crates/perry-runtime/src/gc/tests/copying.rs 4 crates/perry-runtime/src/gc/tests/helper_stores.rs 47 crates/perry-runtime/src/gc/tests/runtime_roots/callback_scanners.rs +4 crates/perry-runtime/src/gc/tests/runtime_roots/fs_options_object.rs 3 crates/perry-runtime/src/gc/tests/runtime_roots/hook_dispatch_handles.rs +3 crates/perry-runtime/src/gc/tests/runtime_roots/json_shape_template.rs 1 crates/perry-runtime/src/gc/tests/runtime_roots/string_slice.rs 10 crates/perry-runtime/src/gc/tests/runtime_roots/transient_handles.rs 11 crates/perry-runtime/src/json/replacer.rs 26 crates/perry-runtime/src/json/reviver.rs 3 crates/perry-runtime/src/json/stringify.rs 1 crates/perry-runtime/src/json/stringify_scalars.rs +2 crates/perry-runtime/src/json/stringify_shape_template.rs 21 crates/perry-runtime/src/json_tape.rs 26 crates/perry-runtime/src/map.rs 2 crates/perry-runtime/src/module_require.rs @@ -103,13 +106,13 @@ 19 crates/perry-runtime/src/object/native_module/async_hooks_exports.rs 26 crates/perry-runtime/src/object/native_module/callable_exports.rs 6 crates/perry-runtime/src/object/object_literal_ops.rs -3 crates/perry-runtime/src/object/object_ops/define_property.rs +2 crates/perry-runtime/src/object/object_ops/define_property.rs 3 crates/perry-runtime/src/object/object_ops/descriptor_helpers.rs 4 crates/perry-runtime/src/object/object_ops/from_entries.rs 5 crates/perry-runtime/src/object/object_ops/keys_array.rs 6 crates/perry-runtime/src/object/polymorphic_index.rs -4 crates/perry-runtime/src/object/reflect_support.rs -3 crates/perry-runtime/src/object/spill.rs +3 crates/perry-runtime/src/object/reflect_support.rs +2 crates/perry-runtime/src/object/spill.rs 2 crates/perry-runtime/src/object/typed_array_define.rs 3 crates/perry-runtime/src/os.rs 14 crates/perry-runtime/src/os_network.rs @@ -132,7 +135,7 @@ 2 crates/perry-runtime/src/string/append.rs 7 crates/perry-runtime/src/string/concat.rs 1 crates/perry-runtime/src/string/mod.rs -10 crates/perry-runtime/src/string/split.rs +7 crates/perry-runtime/src/string/split.rs 2 crates/perry-runtime/src/symbol/iterator.rs 27 crates/perry-runtime/src/thread.rs 7 crates/perry-runtime/src/timer.rs @@ -151,6 +154,3 @@ 19 crates/perry-runtime/src/wasi.rs 6 crates/perry-runtime/src/weakref.rs 23 crates/perry-runtime/src/webassembly.rs -3 crates/perry-runtime/src/gc/tests/runtime_roots/json_shape_template.rs -2 crates/perry-runtime/src/json/stringify_shape_template.rs -4 crates/perry-runtime/src/gc/tests/runtime_roots/fs_options_object.rs From 9f5bbd6e57b0b5e4520ef76a1beb6726dba2962b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 12:19:41 +0200 Subject: [PATCH 02/14] chore: key release notes to PR 8087 --- ...release-readiness-gates.md => 8087-release-readiness-gates.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{0000-release-readiness-gates.md => 8087-release-readiness-gates.md} (100%) diff --git a/changelog.d/0000-release-readiness-gates.md b/changelog.d/8087-release-readiness-gates.md similarity index 100% rename from changelog.d/0000-release-readiness-gates.md rename to changelog.d/8087-release-readiness-gates.md From 5dab35b23faa99f3fee00e2a4419c5d978f6fef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 12:40:07 +0200 Subject: [PATCH 03/14] fix(audit): resolve realm-local wrapper types --- changelog.d/8087-release-readiness-gates.md | 33 ++++++- scripts/global_sink_isolation.py | 100 +++++++++++++++++++- 2 files changed, 125 insertions(+), 8 deletions(-) diff --git a/changelog.d/8087-release-readiness-gates.md b/changelog.d/8087-release-readiness-gates.md index 57cacc0654..7c66d61f06 100644 --- a/changelog.d/8087-release-readiness-gates.md +++ b/changelog.d/8087-release-readiness-gates.md @@ -1,5 +1,28 @@ -Restore the pre-release CI gates: eliminate Rust warning failures, make the -structural lint audits reflect realm-local storage and current GC store sites, -tighten raw-handle debt across allocation windows, split the oversized timer -module, and execute the Windows ARM64 smoke artifact through PowerShell's -explicit invocation path. +Restore the pre-release CI gates after several independent maintenance changes +had outpaced their checks. Test-only helpers and duplicated unwind context +declarations were still compiled in product targets, so the warning-denied jobs +failed even though normal builds succeeded; `eh.rs`, `eh_walker.rs`, and the GC +stack-map modules now share the real unwind ABI, while unused helpers in the GC +and CLI crates are limited to tests or removed. The structural audits had also +drifted: `property_set.rs` placed a valid pointer-free store marker outside the +inventory's bounded context, a test reused a class ID, and +`global_sink_isolation.py` treated immutable `RealmAtomic` handles as shared +state even though their mutable slots are `perry_thread_local!`. The audit now +resolves only the runtime's actual wrapper types, including qualified paths, +and rejects unrelated aliases. + +GC allocation windows in `json_tape.rs` and `object/spill.rs` now reacquire raw +pointers through rooted handles, lowering the raw-handle debt baseline instead +of raising it. The two timer drain tests moved to +`timer/drain_expired_tests.rs`, returning `timer.rs` below the 2,000-line gate. +Finally, the Windows ARM64 workflow resolves the linked executable and invokes +it with PowerShell's call operator, so the smoke gate runs the artifact rather +than looking for its relative path in `PATH`. + +Validation covered warning-denied runtime, product, and host-compatible +workspace checks; both CI clippy scopes; the pre-tag structural audit suite; +the raw-handle, store-site, class-ID, file-size, and global-sink self-tests and +real-tree audits; targeted moving-GC, unwind, timer, class, compile-cache, and +publish-config tests; workflow lint; the RustSec audit; and the Windows command +path through actionlint. The repository's gated release sweep and PR checks +provide the remaining platform-hosted coverage. diff --git a/scripts/global_sink_isolation.py b/scripts/global_sink_isolation.py index 8b4c9916ae..4b3e5c5f19 100644 --- a/scripts/global_sink_isolation.py +++ b/scripts/global_sink_isolation.py @@ -156,6 +156,68 @@ def find_fn_body(sources: dict, name: str): return None, None +def rust_module_path(path: Path) -> tuple[str, ...]: + """Best-effort Rust module path for a source file in the runtime tree.""" + path = Path(path) + try: + relative = path.relative_to(RUNTIME_SRC) + except ValueError: + relative = path + parts = list(relative.parts) + if not parts: + return () + if parts[-1] in ("lib.rs", "mod.rs"): + parts.pop() + elif parts[-1].endswith(".rs"): + parts[-1] = parts[-1][:-3] + return tuple(parts) + + +def is_runtime_realm_atomic(sources: dict, path: Path, declared: str) -> bool: + """Whether `declared` resolves to a real runtime RealmAtomic wrapper. + + Matching the final identifier alone would exempt a same-named local type + alias. Resolve the small set of Rust paths this source audit can prove and + require the target module to contain the wrapper's `struct` definition. + """ + match = re.fullmatch( + r"(?:(crate|self|super(?:::\s*super)*)::\s*)?" + r"((?:[a-z_][a-z0-9_]*::\s*)*)" + r"(RealmAtomic(?:I64|U64))", + declared, + ) + if not match: + return False + + prefix, middle, type_name = match.groups() + current = list(rust_module_path(path)) + if prefix == "crate": + target = [] + elif prefix == "self": + target = current + elif prefix and prefix.startswith("super"): + target = current + for _ in prefix.split("::"): + if not target: + return False + target.pop() + elif middle: + # A relative multi-segment path may depend on a `use` alias that this + # source-only resolver cannot prove. Fail closed. + return False + else: + target = current + + target.extend(part.strip() for part in middle.split("::") if part.strip()) + definition_re = re.compile( + r"\b(?:pub(?:\([^)]*\))?\s+)?struct\s+" + re.escape(type_name) + r"\b" + ) + return any( + rust_module_path(candidate) == tuple(target) and definition_re.search(text) + for candidate, text in sources.items() + ) + + def declaration_kind(sources: dict, ident: str, prefer=None): """'thread_local' | 'per_test' | 'static' | None, plus where. @@ -201,7 +263,7 @@ def declaration_kind(sources: dict, ident: str, prefer=None): # through that `perry_thread_local!` backing slot. Treating the # immutable handle as the data falsely reports a cross-test sink # while ignoring the realm-local slot that actually owns it. - if re.fullmatch(r"RealmAtomic(?:I64|U64)", declared): + if is_runtime_realm_atomic(sources, path, declared): return "thread_local", path if "#[global_allocator]" in text[max(0, found.start() - 80) : found.start()]: return "allocator", path @@ -320,10 +382,12 @@ def audit(sources: dict, support_text: str, allowlist: dict, out=sys.stdout, flo crate::demo::test_clear_tls(); crate::demo::test_clear_paren(); crate::demo::test_clear_realm_atomic(); + crate::demo::test_clear_qualified_realm_atomic(); } """ _FAKE_SRC = """ +struct RealmAtomicU64; per_test_global! { static PARTITIONED_TABLE: Mutex = Mutex::new(0); } @@ -341,12 +405,29 @@ def audit(sources: dict, support_text: str, allowlist: dict, out=sys.stdout, flo pub(crate) fn test_clear_realm_atomic() { REALM_CACHE.with_slot(|slot| slot.store(0)); } """ +_FAKE_CHILD_SRC = """ +static QUALIFIED_REALM_CACHE: super::RealmAtomicU64 = + super::RealmAtomicU64::new(&QUALIFIED_REALM_CACHE_SLOT); +pub(crate) fn test_clear_qualified_realm_atomic() { + QUALIFIED_REALM_CACHE.with_slot(|slot| slot.store(0)); +} +""" + +_FAKE_ALIAS_SRC = """ +type RealmAtomicU64 = FakeAtomic; +static SHADOWED_REALM_CACHE: RealmAtomicU64 = FakeAtomic::new(); +""" + def self_test() -> int: import io failures = [] - fake = {Path("fake.rs"): _FAKE_SRC} + fake = { + Path("object/mod.rs"): _FAKE_SRC, + Path("object/child.rs"): _FAKE_CHILD_SRC, + Path("alias.rs"): _FAKE_ALIAS_SRC, + } sink = io.StringIO() # 1. A bare process-global static is a violation; the partitioned and @@ -394,6 +475,16 @@ def self_test() -> int: if any("REALM_CACHE" in v for v in audit(fake, _FAKE_SUPPORT, {"BARE_TABLE": "#1"}, sink)): failures.append("a RealmAtomic thread-local proxy was reported as a hazard") + kind, _ = declaration_kind(fake, "QUALIFIED_REALM_CACHE") + if kind != "thread_local": + failures.append("qualified RealmAtomic proxy classified as %r, not 'thread_local'" % (kind,)) + + # A same-named alias is not proof that the value is backed by the runtime's + # perry_thread_local! HotKey. It must remain a process-global hazard. + kind, _ = declaration_kind(fake, "SHADOWED_REALM_CACHE") + if kind != "static": + failures.append("unrelated RealmAtomic alias classified as %r, not 'static'" % (kind,)) + # 3e. THE FLOOR: a matcher that stops matching must not read as clean. floored = audit(fake, _FAKE_SUPPORT, {"BARE_TABLE": "#1"}, sink, floor=99) if not any("below the floor" in v for v in floored): @@ -425,12 +516,15 @@ def self_test() -> int: kind, _ = declaration_kind(sources, "ARGUMENTS_OBJECTS") if kind != "thread_local": failures.append("ARGUMENTS_OBJECTS classified as %r on the real tree" % (kind,)) + kind, _ = declaration_kind(sources, "ITERATOR_PROTOTYPE_PTR") + if kind != "thread_local": + failures.append("qualified ITERATOR_PROTOTYPE_PTR classified as %r on the real tree" % (kind,)) except Violation as exc: failures.append("parsing the real tree failed: %s" % exc) for failure in failures: print("SELF-TEST FAIL: %s" % failure, file=sys.stderr) - print("self-test: 17 checks, %d failures" % len(failures)) + print("self-test: 20 checks, %d failures" % len(failures)) return 1 if failures else 0 From ba384d6255915a63c19f0ee02e8b44142c9cadc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 13:00:56 +0200 Subject: [PATCH 04/14] fix(release): serialize runtime workspace tests --- changelog.d/8087-release-readiness-gates.md | 6 +++++ .../tier01_cargo_workspace.sh | 25 +++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/changelog.d/8087-release-readiness-gates.md b/changelog.d/8087-release-readiness-gates.md index 7c66d61f06..b62d3e82b9 100644 --- a/changelog.d/8087-release-readiness-gates.md +++ b/changelog.d/8087-release-readiness-gates.md @@ -26,3 +26,9 @@ real-tree audits; targeted moving-GC, unwind, timer, class, compile-cache, and publish-config tests; workflow lint; the RustSec audit; and the Windows command path through actionlint. The repository's gated release sweep and PR checks provide the remaining platform-hosted coverage. + +Release-sweep tier 1 now mirrors that CI contract instead of running +`perry-runtime` inside a parallel workspace test: it excludes the runtime from +the normal workspace pass and invokes its release tests separately with +`RUST_TEST_THREADS=1`, preventing shared test-state races from masquerading as +release regressions. diff --git a/scripts/release_sweep_tiers/tier01_cargo_workspace.sh b/scripts/release_sweep_tiers/tier01_cargo_workspace.sh index 2a575c2289..44b3ccd295 100755 --- a/scripts/release_sweep_tiers/tier01_cargo_workspace.sh +++ b/scripts/release_sweep_tiers/tier01_cargo_workspace.sh @@ -1,13 +1,15 @@ #!/usr/bin/env bash # Tier 1 — cargo_workspace # -# Runs `cargo test --release --workspace` with the CLAUDE.md UI exclusions. +# Runs the release workspace tests with the CLAUDE.md UI exclusions, then runs +# `perry-runtime` separately with its mandatory single-threaded test harness. # Per the canonical command in that file: # -# cargo test --release --workspace \ +# cargo test --release --workspace --exclude perry-runtime \ # --exclude perry-ui-ios --exclude perry-ui-tvos --exclude perry-ui-watchos \ # --exclude perry-ui-visionos --exclude perry-ui-android \ # --exclude perry-ui-windows --exclude perry-ui-gtk4 +# RUST_TEST_THREADS=1 cargo test --release -p perry-runtime # # Linux/Windows hosts swap the host's UI crate back in (so perry-ui-gtk4 is # tested on Linux but not macOS, etc.) — same logic as tier 0. @@ -41,15 +43,24 @@ esac start="$(date +%s)" { echo "tier 1 cargo_workspace — host=$host" - echo "command: cargo test --release --workspace ${EXCLUDES[*]}" + echo "command: cargo test --release --workspace --exclude perry-runtime ${EXCLUDES[*]}" + echo "command: RUST_TEST_THREADS=1 cargo test --release -p perry-runtime" echo } > "$LOG" set +e -(cd "$REPO_ROOT" && cargo test --release --workspace "${EXCLUDES[@]}") >> "$LOG" 2>&1 -rc=$? +(cd "$REPO_ROOT" && cargo test --release --workspace --exclude perry-runtime "${EXCLUDES[@]}") \ + >> "$LOG" 2>&1 +workspace_rc=$? +(cd "$REPO_ROOT" && RUST_TEST_THREADS=1 cargo test --release -p perry-runtime) >> "$LOG" 2>&1 +runtime_rc=$? set -e +rc=0 +if [[ "$workspace_rc" -ne 0 || "$runtime_rc" -ne 0 ]]; then + rc=1 +fi + # Try to extract per-crate test counts from the log. # `cargo test` prints lines like "test result: ok. 12 passed; 0 failed; 0 ignored ..." # at the end of each crate's run. We sum those. @@ -66,12 +77,12 @@ end="$(date +%s)" dur="$((end - start))" cat > "$SUMMARY" < Date: Fri, 14 Aug 2026 13:49:21 +0200 Subject: [PATCH 05/14] fix(ci): initialize ARM64 Windows SDK paths --- .github/workflows/test.yml | 9 +++++++++ changelog.d/8087-release-readiness-gates.md | 8 +++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96c2d09437..3983d9b84a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1293,6 +1293,15 @@ jobs: uses: dtolnay/rust-toolchain@stable - uses: ./.github/actions/setup-llvm22 + - name: Set up ARM64 MSVC environment + # The native ARM64 runner has the Windows SDK installed, but its + # PowerShell environment does not include the SDK's ARM64 library + # directories. Populate LIB / INCLUDE / PATH so the final PE link can + # resolve system import libraries such as user32.lib. + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: amd64_arm64 + - uses: Swatinem/rust-cache@v2 with: shared-key: "${{ runner.os }}-${{ runner.arch }}-perry" diff --git a/changelog.d/8087-release-readiness-gates.md b/changelog.d/8087-release-readiness-gates.md index b62d3e82b9..35bc87c625 100644 --- a/changelog.d/8087-release-readiness-gates.md +++ b/changelog.d/8087-release-readiness-gates.md @@ -15,9 +15,11 @@ GC allocation windows in `json_tape.rs` and `object/spill.rs` now reacquire raw pointers through rooted handles, lowering the raw-handle debt baseline instead of raising it. The two timer drain tests moved to `timer/drain_expired_tests.rs`, returning `timer.rs` below the 2,000-line gate. -Finally, the Windows ARM64 workflow resolves the linked executable and invokes -it with PowerShell's call operator, so the smoke gate runs the artifact rather -than looking for its relative path in `PATH`. +Finally, the Windows ARM64 workflow initializes the ARM64 MSVC environment so +the linker can find the installed Windows SDK import libraries, then resolves +the linked executable and invokes it with PowerShell's call operator. The smoke +gate now reaches the final PE link and runs the artifact instead of relying on +an uninitialized SDK path or looking for the relative executable in `PATH`. Validation covered warning-denied runtime, product, and host-compatible workspace checks; both CI clippy scopes; the pre-tag structural audit suite; From 329ad94dd57c17d5bf1f8bbf6acd8bd9ee903172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 14:34:33 +0200 Subject: [PATCH 06/14] fix(gc): tolerate formatted statepoint pass const --- changelog.d/8087-release-readiness-gates.md | 4 ++++ scripts/gc_root_dominance_corpus.sh | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/changelog.d/8087-release-readiness-gates.md b/changelog.d/8087-release-readiness-gates.md index 35bc87c625..aa01665b69 100644 --- a/changelog.d/8087-release-readiness-gates.md +++ b/changelog.d/8087-release-readiness-gates.md @@ -34,3 +34,7 @@ Release-sweep tier 1 now mirrors that CI contract instead of running the normal workspace pass and invokes its release tests separately with `RUST_TEST_THREADS=1`, preventing shared test-state races from masquerading as release regressions. + +The native GC root-dominance corpus also reads the production statepoint pass +constant independently of rustfmt's one-line or wrapped layout, preserving the +single-source drift check when the Rust declaration is reformatted. diff --git a/scripts/gc_root_dominance_corpus.sh b/scripts/gc_root_dominance_corpus.sh index 126aa0eaa2..751433ccab 100755 --- a/scripts/gc_root_dominance_corpus.sh +++ b/scripts/gc_root_dominance_corpus.sh @@ -172,7 +172,14 @@ rs4gc_pass_string() { return 1 fi local value - value="$(sed -n 's/^pub(crate) const STATEPOINT_REWRITE_PASSES: &str = "\(.*\)";$/\1/p' "$src")" + # rustfmt may keep this declaration on one line or wrap the string onto the + # following line. Read the declaration as a record so formatting cannot + # silently disconnect this corpus from production's pass pipeline. + value="$(perl -0777 -ne ' + if (/^pub\(crate\) const STATEPOINT_REWRITE_PASSES: &str\s*=\s*"([^"]+)";/m) { + print "$1\n"; + } + ' "$src")" if [ -z "$value" ]; then echo "::error::could not read STATEPOINT_REWRITE_PASSES out of $src." >&2 echo "The native corpus reproduces production's statepoint rewrite, and it" >&2 From 5cabe2917416068db82e8d5535798b742804073f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 15:51:09 +0200 Subject: [PATCH 07/14] fix(ci): reserve disk for extension link gate --- .github/workflows/ext-link.yml | 29 ++++++++++++++++++++- changelog.d/8087-release-readiness-gates.md | 5 ++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ext-link.yml b/.github/workflows/ext-link.yml index 225ae0d919..723f47584f 100644 --- a/.github/workflows/ext-link.yml +++ b/.github/workflows/ext-link.yml @@ -58,7 +58,10 @@ jobs: RUSTC_WRAPPER: sccache SCCACHE_GHA_ENABLED: "false" SCCACHE_DIR: ${{ github.workspace }}/.sccache - SCCACHE_CACHE_SIZE: "8G" + # This gate also materializes release artifacts for every extension. Keep + # the local object cache bounded so it cannot crowd the link target off + # the hosted runner's small root disk. + SCCACHE_CACHE_SIZE: "2G" CARGO_INCREMENTAL: "0" steps: # This job compiles PR-controlled build scripts; don't leave the workflow @@ -67,6 +70,17 @@ jobs: with: persist-credentials: false + - name: Reclaim disk space for full extension link + # ubuntu-latest carries large SDKs this Rust-only job never uses. A + # cold all-extension release target plus an unbounded compiler cache + # can otherwise exhaust the root disk late in the build, commonly + # while compiling the final openssl_e2e test binary. + run: | + before="$(df -h / | tail -1 | awk '{print $4}')" + sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc + after="$(df -h / | tail -1 | awk '{print $4}')" + echo "Disk free: ${before} -> ${after}" + # Cheap gate: no toolchain, no cargo, no cache restore. Every step below # is skipped when the diff cannot change what the archives link. - name: Compute ext-link scope @@ -144,8 +158,21 @@ jobs: # than being inferred from exit status. The counting lives in the # script (`--count-linked`) so this file carries no inline Python — # a heredoc at column 0 silently breaks the YAML block scalar. + # Preserve Cargo's structured output for the linked-binary count, but + # render its diagnostics back into the Actions log on failure. Plain + # redirection previously hid the actual compiler/linker error and + # left only Cargo's "due to 1 previous error" summary on stderr. + set +e # shellcheck disable=SC2086 cargo test --release --no-run --message-format=json $args > /tmp/ext-link.json + cargo_status=$? + set -e + if [ "$cargo_status" -ne 0 ]; then + jq -r 'select(.reason == "compiler-message") | .message.rendered // empty' \ + /tmp/ext-link.json >&2 + df -h / >&2 + exit "$cargo_status" + fi linked="$(python3 scripts/ci_ext_link_scope.py --count-linked /tmp/ext-link.json)" echo "linked test binaries: $linked" diff --git a/changelog.d/8087-release-readiness-gates.md b/changelog.d/8087-release-readiness-gates.md index aa01665b69..6c2559f01f 100644 --- a/changelog.d/8087-release-readiness-gates.md +++ b/changelog.d/8087-release-readiness-gates.md @@ -38,3 +38,8 @@ release regressions. The native GC root-dominance corpus also reads the production statepoint pass constant independently of rustfmt's one-line or wrapped layout, preserving the single-source drift check when the Rust declaration is reformatted. + +The full extension release-link gate now reserves runner disk for its target by +removing unused hosted SDKs and bounding the local compiler cache. If Cargo does +fail, its captured structured diagnostics are rendered back into the Actions +log instead of being lost behind the final one-line compilation summary. From 2e0de08e5267e9beb6c80dc18cd87f87f9c8c50c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 16:17:57 +0200 Subject: [PATCH 08/14] fix(ci): isolate extension link feature graphs --- .github/workflows/ext-link.yml | 61 +++++++++------------ changelog.d/8087-release-readiness-gates.md | 10 ++-- 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/.github/workflows/ext-link.yml b/.github/workflows/ext-link.yml index 723f47584f..75928ebecb 100644 --- a/.github/workflows/ext-link.yml +++ b/.github/workflows/ext-link.yml @@ -58,10 +58,7 @@ jobs: RUSTC_WRAPPER: sccache SCCACHE_GHA_ENABLED: "false" SCCACHE_DIR: ${{ github.workspace }}/.sccache - # This gate also materializes release artifacts for every extension. Keep - # the local object cache bounded so it cannot crowd the link target off - # the hosted runner's small root disk. - SCCACHE_CACHE_SIZE: "2G" + SCCACHE_CACHE_SIZE: "8G" CARGO_INCREMENTAL: "0" steps: # This job compiles PR-controlled build scripts; don't leave the workflow @@ -70,17 +67,6 @@ jobs: with: persist-credentials: false - - name: Reclaim disk space for full extension link - # ubuntu-latest carries large SDKs this Rust-only job never uses. A - # cold all-extension release target plus an unbounded compiler cache - # can otherwise exhaust the root disk late in the build, commonly - # while compiling the final openssl_e2e test binary. - run: | - before="$(df -h / | tail -1 | awk '{print $4}')" - sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc - after="$(df -h / | tail -1 | awk '{print $4}')" - echo "Disk free: ${before} -> ${after}" - # Cheap gate: no toolchain, no cargo, no cache restore. Every step below # is skipped when the diff cannot change what the archives link. - name: Compute ext-link scope @@ -148,31 +134,34 @@ jobs: # failing `cargo test --no-run` IS the regression this job exists to # catch, so its exit status must abort the step under any shell. set -euo pipefail - args="" - while read -r pkg; do - [ -n "$pkg" ] || continue - args="$args -p $pkg" - done <<< "$PKGS" - # `--message-format=json` so the count below comes from cargo rather # than being inferred from exit status. The counting lives in the # script (`--count-linked`) so this file carries no inline Python — # a heredoc at column 0 silently breaks the YAML block scalar. - # Preserve Cargo's structured output for the linked-binary count, but - # render its diagnostics back into the Actions log on failure. Plain - # redirection previously hid the actual compiler/linker error and - # left only Cargo's "due to 1 previous error" summary on stderr. - set +e - # shellcheck disable=SC2086 - cargo test --release --no-run --message-format=json $args > /tmp/ext-link.json - cargo_status=$? - set -e - if [ "$cargo_status" -ne 0 ]; then - jq -r 'select(.reason == "compiler-message") | .message.rendered // empty' \ - /tmp/ext-link.json >&2 - df -h / >&2 - exit "$cargo_status" - fi + # + # Link each package in its own Cargo invocation. Combining every + # `-p` in one command feature-unifies their shared `perry-runtime`: + # selecting perry-ext-fetch then enables `external-fetch-symbols` in + # unrelated test binaries such as node-forge, which correctly do not + # link the fetch provider and fail with undefined `js_fetch_*` + # symbols. Per-package commands exercise the feature graph each + # extension actually ships while reusing the common target artifacts. + : > /tmp/ext-link.json + while read -r pkg; do + [ -n "$pkg" ] || continue + pkg_json="/tmp/ext-link-${pkg}.json" + echo "Linking ${pkg}..." + set +e + cargo test --release --no-run --message-format=json -p "$pkg" > "$pkg_json" + cargo_status=$? + set -e + cat "$pkg_json" >> /tmp/ext-link.json + if [ "$cargo_status" -ne 0 ]; then + jq -r 'select(.reason == "compiler-message") | .message.rendered // empty' \ + "$pkg_json" >&2 + exit "$cargo_status" + fi + done <<< "$PKGS" linked="$(python3 scripts/ci_ext_link_scope.py --count-linked /tmp/ext-link.json)" echo "linked test binaries: $linked" diff --git a/changelog.d/8087-release-readiness-gates.md b/changelog.d/8087-release-readiness-gates.md index 6c2559f01f..3bfa0ff211 100644 --- a/changelog.d/8087-release-readiness-gates.md +++ b/changelog.d/8087-release-readiness-gates.md @@ -39,7 +39,9 @@ The native GC root-dominance corpus also reads the production statepoint pass constant independently of rustfmt's one-line or wrapped layout, preserving the single-source drift check when the Rust declaration is reformatted. -The full extension release-link gate now reserves runner disk for its target by -removing unused hosted SDKs and bounding the local compiler cache. If Cargo does -fail, its captured structured diagnostics are rendered back into the Actions -log instead of being lost behind the final one-line compilation summary. +The full extension release-link gate now invokes Cargo once per extension so +one provider's runtime features cannot leak into unrelated test binaries. This +prevents `perry-ext-fetch`'s external-symbol mode from making node-forge expect +fetch symbols it does not link, while retaining target reuse across packages. +If Cargo does fail, its captured structured diagnostics are rendered back into +the Actions log instead of being lost behind the final compilation summary. From e783a9107c43d38eea673161af2a51b221dd8a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 16:34:54 +0200 Subject: [PATCH 09/14] fix(ci): classify typed-array codegen suite --- changelog.d/8087-release-readiness-gates.md | 4 ++++ scripts/ci_e2e_scope.py | 1 + 2 files changed, 5 insertions(+) diff --git a/changelog.d/8087-release-readiness-gates.md b/changelog.d/8087-release-readiness-gates.md index 3bfa0ff211..95e1385575 100644 --- a/changelog.d/8087-release-readiness-gates.md +++ b/changelog.d/8087-release-readiness-gates.md @@ -45,3 +45,7 @@ prevents `perry-ext-fetch`'s external-symbol mode from making node-forge expect fetch symbols it does not link, while retaining target reuse across packages. If Cargo does fail, its captured structured diagnostics are rendered back into the Actions log instead of being lost behind the final compilation summary. + +The scoped end-to-end inventory now classifies the typed-array local-length +specialization suite, keeping the codegen source-to-suite map complete as new +in-process regression coverage is added. diff --git a/scripts/ci_e2e_scope.py b/scripts/ci_e2e_scope.py index d52efe3375..b9b5b07991 100755 --- a/scripts/ci_e2e_scope.py +++ b/scripts/ci_e2e_scope.py @@ -137,6 +137,7 @@ "private_guard_declaring_class", "scalar_replaced_slot_roots", "shadow_slot_hygiene", + "spec_abi_typed_array_local_length", "static_symbol_hygiene", "temp_root_operand_temporaries", "typed_feedback", From cf3abc4add911441ab41bb8bd69584c8898afe1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 16:54:13 +0200 Subject: [PATCH 10/14] fix(argon2): enable OS randomness explicitly --- Cargo.lock | 1 + changelog.d/8087-release-readiness-gates.md | 4 ++++ crates/perry-ext-argon2/Cargo.toml | 1 + crates/perry-ext-argon2/src/lib.rs | 3 ++- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 940143335c..372795ac6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5767,6 +5767,7 @@ version = "0.5.1511" dependencies = [ "argon2", "perry-ffi", + "rand_core 0.6.4", ] [[package]] diff --git a/changelog.d/8087-release-readiness-gates.md b/changelog.d/8087-release-readiness-gates.md index 95e1385575..663a318eac 100644 --- a/changelog.d/8087-release-readiness-gates.md +++ b/changelog.d/8087-release-readiness-gates.md @@ -49,3 +49,7 @@ the Actions log instead of being lost behind the final compilation summary. The scoped end-to-end inventory now classifies the typed-array local-length specialization suite, keeping the codegen source-to-suite map complete as new in-process regression coverage is added. + +The Argon2 extension now enables `rand_core`'s OS-randomness feature directly, +so its salt generation builds in isolation instead of depending on another +workspace package to feature-unify `OsRng` into the graph. diff --git a/crates/perry-ext-argon2/Cargo.toml b/crates/perry-ext-argon2/Cargo.toml index efd62560b1..28048057f3 100644 --- a/crates/perry-ext-argon2/Cargo.toml +++ b/crates/perry-ext-argon2/Cargo.toml @@ -14,6 +14,7 @@ crate-type = ["staticlib", "rlib"] [dependencies] perry-ffi.workspace = true argon2 = "0.5" +rand_core = { version = "0.6", features = ["getrandom"] } [dev-dependencies] perry-ffi = { workspace = true, features = ["runtime-link"] } diff --git a/crates/perry-ext-argon2/src/lib.rs b/crates/perry-ext-argon2/src/lib.rs index 35b76dc9c1..c46609212e 100644 --- a/crates/perry-ext-argon2/src/lib.rs +++ b/crates/perry-ext-argon2/src/lib.rs @@ -4,12 +4,13 @@ //! perry-ffi v0.5.1's async surface — same recipe as bcrypt. use argon2::{ - password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, + password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; use perry_ffi::{ alloc_string, read_string, spawn_blocking, JsPromise, JsString, Promise, StringHeader, }; +use rand_core::OsRng; /// `argon2.hash(password) -> Promise` — Argon2id with /// default parameters. From 17d10686e0a3f09edb555cf8218d371c30af659e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 17:28:25 +0200 Subject: [PATCH 11/14] fix(ci): group extension links by runtime features --- .github/workflows/ext-link.yml | 59 +++++++++++++++------ changelog.d/8087-release-readiness-gates.md | 13 ++--- 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ext-link.yml b/.github/workflows/ext-link.yml index 75928ebecb..00c5a185d6 100644 --- a/.github/workflows/ext-link.yml +++ b/.github/workflows/ext-link.yml @@ -139,29 +139,58 @@ jobs: # script (`--count-linked`) so this file carries no inline Python — # a heredoc at column 0 silently breaks the YAML block scalar. # - # Link each package in its own Cargo invocation. Combining every - # `-p` in one command feature-unifies their shared `perry-runtime`: - # selecting perry-ext-fetch then enables `external-fetch-symbols` in - # unrelated test binaries such as node-forge, which correctly do not - # link the fetch provider and fail with undefined `js_fetch_*` - # symbols. Per-package commands exercise the feature graph each - # extension actually ships while reusing the common target artifacts. - : > /tmp/ext-link.json + # First check each package independently. This catches undeclared + # feature dependencies (for example Argon2 relying on another package + # to enable rand_core/getrandom) without paying for a full release + # link per package. + default_args=() + fetch_args=() + ws_args=() while read -r pkg; do [ -n "$pkg" ] || continue - pkg_json="/tmp/ext-link-${pkg}.json" - echo "Linking ${pkg}..." + echo "Checking ${pkg}'s independent feature graph..." + cargo check --release -p "$pkg" + case "$pkg" in + perry-ext-fetch) + fetch_args+=(-p "$pkg") + ;; + perry-ext-fastify|perry-ext-ws) + ws_args+=(-p "$pkg") + ;; + *) + default_args+=(-p "$pkg") + ;; + esac + done <<< "$PKGS" + + # Release-link packages in three feature-compatible groups. A single + # all-package command lets perry-ext-fetch's + # `external-fetch-symbols` and the WS providers' corresponding mode + # leak into unrelated test binaries, which then expect provider + # symbols they correctly do not link. Three groups preserve the real + # feature boundary while compiling the expensive runtime only a few + # times, keeping the gate inside its measured 120-minute backstop. + : > /tmp/ext-link.json + run_link_group() { + local group="$1" + shift + [ "$#" -gt 0 ] || return 0 + local group_json="/tmp/ext-link-${group}.json" + echo "Linking ${group} feature group..." set +e - cargo test --release --no-run --message-format=json -p "$pkg" > "$pkg_json" - cargo_status=$? + cargo test --release --no-run --message-format=json "$@" > "$group_json" + local cargo_status=$? set -e - cat "$pkg_json" >> /tmp/ext-link.json + cat "$group_json" >> /tmp/ext-link.json if [ "$cargo_status" -ne 0 ]; then jq -r 'select(.reason == "compiler-message") | .message.rendered // empty' \ - "$pkg_json" >&2 + "$group_json" >&2 exit "$cargo_status" fi - done <<< "$PKGS" + } + run_link_group default "${default_args[@]}" + run_link_group fetch "${fetch_args[@]}" + run_link_group ws "${ws_args[@]}" linked="$(python3 scripts/ci_ext_link_scope.py --count-linked /tmp/ext-link.json)" echo "linked test binaries: $linked" diff --git a/changelog.d/8087-release-readiness-gates.md b/changelog.d/8087-release-readiness-gates.md index 663a318eac..4612ba947f 100644 --- a/changelog.d/8087-release-readiness-gates.md +++ b/changelog.d/8087-release-readiness-gates.md @@ -39,12 +39,13 @@ The native GC root-dominance corpus also reads the production statepoint pass constant independently of rustfmt's one-line or wrapped layout, preserving the single-source drift check when the Rust declaration is reformatted. -The full extension release-link gate now invokes Cargo once per extension so -one provider's runtime features cannot leak into unrelated test binaries. This -prevents `perry-ext-fetch`'s external-symbol mode from making node-forge expect -fetch symbols it does not link, while retaining target reuse across packages. -If Cargo does fail, its captured structured diagnostics are rendered back into -the Actions log instead of being lost behind the final compilation summary. +The full extension release-link gate now checks every extension independently, +then links three feature-compatible groups so provider runtime features cannot +leak into unrelated test binaries. This prevents `perry-ext-fetch`'s +external-symbol mode from making node-forge expect fetch symbols it does not +link without multiplying the expensive release link across every package. If +Cargo does fail, its captured structured diagnostics are rendered back into the +Actions log instead of being lost behind the final compilation summary. The scoped end-to-end inventory now classifies the typed-array local-length specialization suite, keeping the codegen source-to-suite map complete as new From f3a5ac0d0711e699d6e5c11267d03021ee2f46c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 18:27:52 +0200 Subject: [PATCH 12/14] fix(gc): root allocated Headers during initialization --- .../perry-codegen/src/lower_call/builtin.rs | 11 +++++- .../src/lower_call/options/mod.rs | 35 +++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/crates/perry-codegen/src/lower_call/builtin.rs b/crates/perry-codegen/src/lower_call/builtin.rs index dae0331a30..e3d39a8677 100644 --- a/crates/perry-codegen/src/lower_call/builtin.rs +++ b/crates/perry-codegen/src/lower_call/builtin.rs @@ -1159,6 +1159,13 @@ pub(super) fn lower_builtin_new<'a>( // new Headers(init?) — init can be an object literal or another // Headers/array iterable. let h = ctx.block().call(DOUBLE, "js_headers_new", &[]); + // `js_headers_new` produces the only reference to the new handle. + // Initializer evaluation and string coercion can collect, so keep + // that emitted value in the surrounding constructor root group and + // re-read it at every use below. In particular, a dynamic header + // value may run `toString()` before `js_headers_set` consumes the + // handle (#8087's native statepoint corpus caught this window). + let h_root = group.adopt_emitted(ctx, rooting::Repr::Boxed, &h, !args.is_empty()); if !args.is_empty() { if let Some(props) = extract_options_fields(ctx, &args[0]) { for (k, vexpr) in &props { @@ -1168,6 +1175,7 @@ pub(super) fn lower_builtin_new<'a>( let val_ptr = ctx.block() .call(I64, "js_jsvalue_to_string", &[(DOUBLE, &value)]); + let h = group.reread_emitted(ctx, h_root); ctx.block().call( DOUBLE, "js_headers_set", @@ -1176,6 +1184,7 @@ pub(super) fn lower_builtin_new<'a>( } } else { let init = lower_expr(ctx, &args[0])?; + let h = group.reread_emitted(ctx, h_root); ctx.block().call( DOUBLE, "js_headers_init_from_value", @@ -1183,7 +1192,7 @@ pub(super) fn lower_builtin_new<'a>( ); } } - Ok(Some(h)) + Ok(Some(group.reread_emitted(ctx, h_root))) } "FormData" => { diff --git a/crates/perry-codegen/src/lower_call/options/mod.rs b/crates/perry-codegen/src/lower_call/options/mod.rs index 81dab2e45d..281d1d9fcb 100644 --- a/crates/perry-codegen/src/lower_call/options/mod.rs +++ b/crates/perry-codegen/src/lower_call/options/mod.rs @@ -21,6 +21,7 @@ use anyhow::Result; use perry_hir::Expr; use crate::expr::{lower_expr, FnCtx}; +use crate::rooting::{with_rooted_group, Repr}; use crate::types::{DOUBLE, I64}; mod abort; @@ -45,21 +46,25 @@ pub(in crate::lower_call) fn build_headers_from_object( ctx: &mut FnCtx<'_>, props: &[(String, Expr)], ) -> Result { - let h = ctx.block().call(DOUBLE, "js_headers_new", &[]); - for (k, vexpr) in props { - let key_expr = Expr::String(k.clone()); - let key_ptr = get_raw_string_ptr(ctx, &key_expr)?; - let value = lower_expr(ctx, vexpr)?; - let val_ptr = ctx - .block() - .call(I64, "js_jsvalue_to_string", &[(DOUBLE, &value)]); - ctx.block().call( - DOUBLE, - "js_headers_set", - &[(DOUBLE, &h), (I64, &key_ptr), (I64, &val_ptr)], - ); - } - Ok(h) + with_rooted_group(ctx, 1, |ctx, group| { + let h = ctx.block().call(DOUBLE, "js_headers_new", &[]); + let h_root = group.adopt_emitted(ctx, Repr::Boxed, &h, !props.is_empty()); + for (k, vexpr) in props { + let key_expr = Expr::String(k.clone()); + let key_ptr = get_raw_string_ptr(ctx, &key_expr)?; + let value = lower_expr(ctx, vexpr)?; + let val_ptr = ctx + .block() + .call(I64, "js_jsvalue_to_string", &[(DOUBLE, &value)]); + let h = group.reread_emitted(ctx, h_root); + ctx.block().call( + DOUBLE, + "js_headers_set", + &[(DOUBLE, &h), (I64, &key_ptr), (I64, &val_ptr)], + ); + } + Ok(group.reread_emitted(ctx, h_root)) + }) } /// Phase 3 compat: extract `{key: value, ...}` pairs from an options From 355cdd53ff40609766ef5272e7064659240d6728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 21:50:05 +0200 Subject: [PATCH 13/14] chore: drop the version bump from this PR The maintainer bumps the version at merge time, and there is a standing "no version bump for now" instruction on this branch's review. Reverts `[workspace.package] version` and the CLAUDE.md `Current Version:` line to 0.5.1510 and regenerates Cargo.lock. The lock's only remaining diff against main is the functional one this PR actually needs: `rand_core 0.6.4` under perry-ext-argon2. --- 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 3c43c63040..7fd82f9a87 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.1511 +**Current Version:** 0.5.1510 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index 372795ac6d..52f3bd40a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5537,7 +5537,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "base64 0.22.1", @@ -5597,7 +5597,7 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-dispatch", "serde", @@ -5605,7 +5605,7 @@ dependencies = [ [[package]] name = "perry-audio-miniaudio" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "cc", "libc", @@ -5613,7 +5613,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "inkwell", @@ -5630,7 +5630,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "perry-hir", @@ -5638,7 +5638,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "perry-hir", @@ -5646,7 +5646,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "perry-dispatch", @@ -5655,7 +5655,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "perry-hir", @@ -5663,7 +5663,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "base64 0.22.1", @@ -5675,7 +5675,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "perry-hir", @@ -5683,7 +5683,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "async-trait", @@ -5712,14 +5712,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "serde", "serde_json", @@ -5727,7 +5727,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1511" +version = "0.5.1510" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5738,7 +5738,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "clap", @@ -5753,7 +5753,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "block2", "objc2", @@ -5763,7 +5763,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "argon2", "perry-ffi", @@ -5772,7 +5772,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", "reqwest", @@ -5781,7 +5781,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "bcrypt", "perry-ffi", @@ -5789,7 +5789,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", "rusqlite", @@ -5797,7 +5797,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", "scraper", @@ -5805,7 +5805,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", "perry-runtime", @@ -5813,7 +5813,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "chrono", "cron", @@ -5823,7 +5823,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "chrono", "perry-ffi", @@ -5831,7 +5831,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", "rust_decimal", @@ -5839,7 +5839,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", "serde_json", @@ -5847,7 +5847,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5855,7 +5855,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", "perry-runtime", @@ -5863,14 +5863,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "bytes", "http-body-util", @@ -5888,7 +5888,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "bytes", "lazy_static", @@ -5901,7 +5901,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "bytes", "h2", @@ -5925,7 +5925,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "lazy_static", "perry-ffi", @@ -5935,7 +5935,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "base64 0.22.1", "jsonwebtoken", @@ -5946,7 +5946,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "lru", "perry-ffi", @@ -5955,7 +5955,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "chrono", "perry-ffi", @@ -5963,7 +5963,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "bson", "futures-util", @@ -5975,7 +5975,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "chrono", "perry-ffi", @@ -5985,7 +5985,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "nanoid", "perry-ffi", @@ -5994,7 +5994,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "bytes", "perry-ffi", @@ -6007,7 +6007,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "const-oid 0.9.6", "der 0.7.10", @@ -6026,7 +6026,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "lettre", "perry-ffi", @@ -6036,7 +6036,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", "printpdf", @@ -6044,7 +6044,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", "sqlx", @@ -6053,7 +6053,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "governor", "perry-ffi", @@ -6061,7 +6061,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "fast_image_resize", "image", @@ -6071,14 +6071,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "lazy_static", "perry-ffi", @@ -6087,7 +6087,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", "perry-runtime", @@ -6096,7 +6096,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", "uuid", @@ -6104,7 +6104,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ffi", "regex", @@ -6114,7 +6114,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "futures-util", "lazy_static", @@ -6127,7 +6127,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "brotli", "flate2", @@ -6137,7 +6137,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "dashmap", "once_cell", @@ -6146,7 +6146,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "perry-api-manifest", @@ -6164,7 +6164,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "perry-diagnostics", @@ -6176,7 +6176,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "base64 0.22.1", @@ -6218,14 +6218,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6320,14 +6320,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "perry-hir", @@ -6336,14 +6336,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "base64 0.22.1", "itoa", @@ -6360,7 +6360,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "rand 0.10.1", "serde", @@ -6370,7 +6370,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "base64 0.22.1", "cairo-rs 0.22.0", @@ -6393,7 +6393,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "base64 0.22.1", "block2", @@ -6409,7 +6409,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "base64 0.22.1", "block2", @@ -6424,7 +6424,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1511" +version = "0.5.1510" [[package]] name = "perry-ui-test" @@ -6435,11 +6435,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1511" +version = "0.5.1510" [[package]] name = "perry-ui-tvos" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "base64 0.22.1", "block2", @@ -6455,7 +6455,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "base64 0.22.1", "block2", @@ -6471,7 +6471,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "block2", "libc", @@ -6484,7 +6484,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "base64 0.22.1", "libc", @@ -6501,14 +6501,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "anyhow", "base64 0.22.1", @@ -6524,7 +6524,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1511" +version = "0.5.1510" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 9db28d37c6..77391ac8fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,7 +315,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1511" +version = "0.5.1510" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" From e7bf9719f17f6317d4c3c0b90d2763c3393dd2fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 22:05:47 +0200 Subject: [PATCH 14/14] fix(release): name the real failure shape in tier 1 A compile or link error exits nonzero without ever printing a `test result` line, so `total_failed` stays 0 and the tier reported "0 crate-suites failed of N" on a run that did fail. The exit codes were already in the message, but the count read as "nothing failed". Report that shape explicitly instead when no suite recorded a failure. --- scripts/release_sweep_tiers/tier01_cargo_workspace.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/release_sweep_tiers/tier01_cargo_workspace.sh b/scripts/release_sweep_tiers/tier01_cargo_workspace.sh index 44b3ccd295..65dc5d13bc 100755 --- a/scripts/release_sweep_tiers/tier01_cargo_workspace.sh +++ b/scripts/release_sweep_tiers/tier01_cargo_workspace.sh @@ -83,6 +83,14 @@ EOF if [[ "$rc" -eq 0 ]]; then sweep_tier_emit "$OUT" 1 "cargo_workspace" "PASS" "$dur" "$total_passed crate-suites passed" else + # A compile/link error exits nonzero without ever printing a `test result` + # line, so `total_failed` stays 0. Saying "0 crate-suites failed" there + # reads as "nothing failed" on a tier that did fail; name the real shape. + if [[ "$total_failed" -eq 0 ]]; then + detail="no suite reported a failure, so an invocation died before emitting results (compile/link)" + else + detail="$total_failed crate-suites failed of $((total_passed + total_failed))" + fi sweep_tier_emit "$OUT" 1 "cargo_workspace" "FAIL" "$dur" \ - "cargo test failed (workspace=$workspace_rc runtime=$runtime_rc; $total_failed crate-suites failed of $((total_passed + total_failed)))" + "cargo test failed (workspace=$workspace_rc runtime=$runtime_rc; $detail)" fi