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 000000000..0bb840fe6 --- /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`. diff --git a/crates/perry-codegen/tests/native_proof_buffer_views.rs b/crates/perry-codegen/tests/native_proof_buffer_views.rs index 58463077c..176589414 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 deb0d9cbe..259d4ede6 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),