From fd7d7e59ade9fccb68005b2949d2b4eeb6c3d688 Mon Sep 17 00:00:00 2001 From: TheHypnoo Date: Sat, 25 Jul 2026 18:12:24 +0200 Subject: [PATCH 1/5] fix(codegen): update the disposed-Uint8Array proof to the helper #6092 emits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `native_owned_uint8array_get_fallback_uses_uint8array_helper` has been red on main since #6092. That PR moved the unproven-bounds JS-value read off the i32 `js_uint8array_get` accessor — whose out-of-range answer is the `0` byte-sentinel — onto `js_uint8array_index_get_value`, which reads `undefined` per IntegerIndexedExotic `[[Get]]` (#6088). The proof still asserted the old symbol. Nothing about the property under test changes: the disposed native view must fall back through the Uint8Array-shaped accessor, never the Buffer-shaped one. The second assertion, that no `js_buffer_get` appears, still passes untouched. The sibling inline-path test only forbade `js_uint8array_get`, so after #6092 a regression that took the slow path would have gone unnoticed. It now forbids both helpers. Integration suites under `crates/*/tests/` don't run per-PR (#5960), which is why this sat red for weeks — it surfaces only when a PR touches perry-codegen and pulls the suite into `e2e-scoped`. Also reformats crates/perry-hir/src/lower/builder_fold.rs, the one file failing `cargo fmt --check` on main, so this PR's lint job can pass on its own. --- .../tests/native_proof_buffer_views.rs | 17 +++++++++++++++-- crates/perry-hir/src/lower/builder_fold.rs | 15 +++------------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/crates/perry-codegen/tests/native_proof_buffer_views.rs b/crates/perry-codegen/tests/native_proof_buffer_views.rs index 58463077c1..1765894148 100644 --- a/crates/perry-codegen/tests/native_proof_buffer_views.rs +++ b/crates/perry-codegen/tests/native_proof_buffer_views.rs @@ -1554,9 +1554,15 @@ fn native_owned_uint8array_get_fallback_uses_uint8array_helper() { })), ], ); + // #6088/#6092 moved the unproven-bounds JS-value read off the i32 + // `js_uint8array_get` accessor, whose out-of-range answer is the `0` + // byte-sentinel, onto `js_uint8array_index_get_value`, which reads + // `undefined` per IntegerIndexedExotic `[[Get]]`. What this test is about + // is unchanged: the fallback must go through the Uint8Array-shaped + // accessor, never the Buffer-shaped one. assert!( - ir.contains("call i32 @js_uint8array_get"), - "disposed native Uint8Array fallback should call js_uint8array_get:\n{ir}" + ir.contains("call double @js_uint8array_index_get_value"), + "disposed native Uint8Array fallback should call js_uint8array_index_get_value:\n{ir}" ); assert!( !ir.contains("call i32 @js_buffer_get"), @@ -1642,6 +1648,13 @@ fn uint8array_const_local_length_uses_inline_byte_get_set() { !ir.contains("call i32 @js_uint8array_get"), "inline Uint8Array get should not call the runtime helper:\n{ir}" ); + // The JS-value getter is the other way this could fall back after + // #6088/#6092; without naming it here the negative assertion above would + // miss a regression that took the slow path. + assert!( + !ir.contains("call double @js_uint8array_index_get_value"), + "inline Uint8Array get should not call the JS-value runtime helper:\n{ir}" + ); } #[test] diff --git a/crates/perry-hir/src/lower/builder_fold.rs b/crates/perry-hir/src/lower/builder_fold.rs index deb0d9cbeb..259d4ede63 100644 --- a/crates/perry-hir/src/lower/builder_fold.rs +++ b/crates/perry-hir/src/lower/builder_fold.rs @@ -90,9 +90,7 @@ fn scan_stmt(s: &ast::Stmt) -> bool { match s { ast::Stmt::Block(b) => stmts_have_candidate(&b.stmts), ast::Stmt::If(i) => { - scan_expr(&i.test) - || scan_stmt(&i.cons) - || i.alt.as_deref().is_some_and(scan_stmt) + scan_expr(&i.test) || scan_stmt(&i.cons) || i.alt.as_deref().is_some_and(scan_stmt) } ast::Stmt::While(w) => scan_expr(&w.test) || scan_stmt(&w.body), ast::Stmt::DoWhile(d) => scan_stmt(&d.body) || scan_expr(&d.test), @@ -115,8 +113,7 @@ fn scan_stmt(s: &ast::Stmt) -> bool { .is_some_and(|f| stmts_have_candidate(&f.stmts)) } ast::Stmt::Switch(sw) => { - scan_expr(&sw.discriminant) - || sw.cases.iter().any(|c| stmts_have_candidate(&c.cons)) + scan_expr(&sw.discriminant) || sw.cases.iter().any(|c| stmts_have_candidate(&c.cons)) } ast::Stmt::Decl(d) => scan_decl(d), ast::Stmt::Expr(es) => scan_expr(&es.expr), @@ -209,13 +206,7 @@ fn scan_expr(e: &ast::Expr) -> bool { matches!(&c.callee, ast::Callee::Expr(e) if scan_expr(e)) || c.args.iter().any(|a| scan_expr(&a.expr)) } - E::New(n) => { - scan_expr(&n.callee) - || n.args - .iter() - .flatten() - .any(|a| scan_expr(&a.expr)) - } + E::New(n) => scan_expr(&n.callee) || n.args.iter().flatten().any(|a| scan_expr(&a.expr)), E::Seq(s) => s.exprs.iter().any(|e| scan_expr(e)), E::Tpl(t) => t.exprs.iter().any(|e| scan_expr(e)), E::Paren(p) => scan_expr(&p.expr), From 4826aaf79a76e77bb99d1354b43eb92e5eb688e7 Mon Sep 17 00:00:00 2001 From: TheHypnoo Date: Sat, 25 Jul 2026 18:12:54 +0200 Subject: [PATCH 2/5] docs(changelog): add fragment for #6839 --- .../6839-native-proof-uint8array-symbol.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 changelog.d/6839-native-proof-uint8array-symbol.md diff --git a/changelog.d/6839-native-proof-uint8array-symbol.md b/changelog.d/6839-native-proof-uint8array-symbol.md new file mode 100644 index 0000000000..0bb840fe62 --- /dev/null +++ b/changelog.d/6839-native-proof-uint8array-symbol.md @@ -0,0 +1,18 @@ +### Fixed + +- **`native_owned_uint8array_get_fallback_uses_uint8array_helper` has been red + on `main` since #6092.** That PR moved the unproven-bounds JS-value read off + the i32 `js_uint8array_get` accessor — whose out-of-range answer is the `0` + byte-sentinel — onto `js_uint8array_index_get_value`, which reads `undefined` + per IntegerIndexedExotic `[[Get]]` (#6088). The proof still asserted the old + symbol. What the test is about does not change: a disposed native Uint8Array + view must fall back through the Uint8Array-shaped accessor, never the + Buffer-shaped one. + + The sibling inline-path test forbade only `js_uint8array_get`, so after #6092 + a regression that took the slow path would have gone unnoticed. It now + forbids both helpers. + + Integration suites under `crates/*/tests/` don't run per-PR (#5960), which is + why this sat red — it surfaces only when a PR touches perry-codegen and pulls + the suite into `e2e-scoped`. From ed63c5e5162d61a455362d55a5b15e32f652f0eb Mon Sep 17 00:00:00 2001 From: TheHypnoo Date: Wed, 29 Jul 2026 23:15:50 +0200 Subject: [PATCH 3/5] docs: clarify scoped integration test selection --- changelog.d/6839-native-proof-uint8array-symbol.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/changelog.d/6839-native-proof-uint8array-symbol.md b/changelog.d/6839-native-proof-uint8array-symbol.md index 0bb840fe62..2502928899 100644 --- a/changelog.d/6839-native-proof-uint8array-symbol.md +++ b/changelog.d/6839-native-proof-uint8array-symbol.md @@ -13,6 +13,5 @@ a regression that took the slow path would have gone unnoticed. It now forbids both helpers. - Integration suites under `crates/*/tests/` don't run per-PR (#5960), which is - why this sat red — it surfaces only when a PR touches perry-codegen and pulls - the suite into `e2e-scoped`. + The PR workflow now uses diff-based selection in `e2e-scoped`, so changing + this integration test pulls its suite into the per-PR run. From b691a51376ac7bb042890c0ffceb512597467fd8 Mon Sep 17 00:00:00 2001 From: TheHypnoo Date: Thu, 30 Jul 2026 00:01:05 +0200 Subject: [PATCH 4/5] style(codegen): format loop purity match arm --- crates/perry-codegen/src/loop_purity.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/perry-codegen/src/loop_purity.rs b/crates/perry-codegen/src/loop_purity.rs index aeace93d48..364d34cb3b 100644 --- a/crates/perry-codegen/src/loop_purity.rs +++ b/crates/perry-codegen/src/loop_purity.rs @@ -121,9 +121,7 @@ fn expr_alloc_free(e: &Expr) -> bool { // Element READS never allocate — they return an existing element / a // number. Recurse so the object and index are themselves alloc-free. Expr::IndexGet { object, index } => expr_alloc_free(object) && expr_alloc_free(index), - Expr::BufferIndexGet { buffer, index } => { - expr_alloc_free(buffer) && expr_alloc_free(index) - } + Expr::BufferIndexGet { buffer, index } => expr_alloc_free(buffer) && expr_alloc_free(index), Expr::Uint8ArrayGet { array, index } => expr_alloc_free(array) && expr_alloc_free(index), // `arr[i]++` / `--`: read-modify-write of an existing numeric slot, no // growth, no allocation. From 347022c1ad8c945d1624a27b0166f60495e09dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Thu, 30 Jul 2026 04:24:46 +0200 Subject: [PATCH 5/5] docs(changelog): describe remaining Uint8Array guard --- .../6839-native-proof-uint8array-symbol.md | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/changelog.d/6839-native-proof-uint8array-symbol.md b/changelog.d/6839-native-proof-uint8array-symbol.md index 2502928899..1e0ad1af65 100644 --- a/changelog.d/6839-native-proof-uint8array-symbol.md +++ b/changelog.d/6839-native-proof-uint8array-symbol.md @@ -1,17 +1,6 @@ -### Fixed +### Tests -- **`native_owned_uint8array_get_fallback_uses_uint8array_helper` has been red - on `main` since #6092.** That PR moved the unproven-bounds JS-value read off - the i32 `js_uint8array_get` accessor — whose out-of-range answer is the `0` - byte-sentinel — onto `js_uint8array_index_get_value`, which reads `undefined` - per IntegerIndexedExotic `[[Get]]` (#6088). The proof still asserted the old - symbol. What the test is about does not change: a disposed native Uint8Array - view must fall back through the Uint8Array-shaped accessor, never the - Buffer-shaped one. - - The sibling inline-path test forbade only `js_uint8array_get`, so after #6092 - a regression that took the slow path would have gone unnoticed. It now - forbids both helpers. - - The PR workflow now uses diff-based selection in `e2e-scoped`, so changing - this integration test pulls its suite into the per-PR run. +- Strengthened native `Uint8Array` codegen coverage so a proven inline byte + read must not call either the legacy byte helper or the JS-value fallback + helper. The disposed-view expectation already present on `main` remains + covered separately.