From dfd0b1c9492371dd56b0fa039e9f0d0e209570c9 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 7 Nov 2026 13:32:53 -0800 Subject: [PATCH] JavaScript: a finally block keeps the try block's completion value, and host strings count toward the heap In script and eval code a finally block that ends normally leaves the completion value of its try or catch block (ECMA-262 5.1 section 12.14), so try { 1; } finally { 2; } completes with 1. A new GetCompletion instruction saves the value when the finally block starts, and SetCompletion sets it back when the block ends normally; a break, continue or throw out of the finally block still replaces it. A string returned by a host function is charged to the heap, and one that does not fit after a collection ends the run with the heap limit. --- crates/js/src/bytecode.rs | 5 ++++- crates/js/src/compiler.rs | 9 +++++++++ crates/js/src/interp.rs | 22 +++++++++++++++++----- crates/js/src/interp_tests.rs | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/crates/js/src/bytecode.rs b/crates/js/src/bytecode.rs index 4ecb582d0..b23d9a525 100644 --- a/crates/js/src/bytecode.rs +++ b/crates/js/src/bytecode.rs @@ -142,6 +142,9 @@ pub(crate) enum Op { ForInNext(u32), /// Pops the value of an expression statement into the completion value. SetCompletion, + /// Pushes the completion value, which a `finally` block in script and eval code keeps and + /// sets back with `SetCompletion` when it ends normally. + GetCompletion, } /// What code is: a function body, a script, or eval code. @@ -237,7 +240,7 @@ fn effect(op: Op, finally_tables: &[Vec]) -> Result { Op::GetVar(_) | Op::TypeofVar(_) | Op::DeleteVar(_) | Op::IncVar { .. } | Op::RefVar(_) => e(0, 1), Op::GetRef(_) => e(1, 1), Op::PutRef(_) => e(2, 1), - Op::NewObject | Op::NewArray | Op::Closure(_) | Op::RegExp(_) => e(0, 1), + Op::NewObject | Op::NewArray | Op::Closure(_) | Op::RegExp(_) | Op::GetCompletion => e(0, 1), Op::Pop | Op::PushWith | Op::PushCatch(_) | Op::SetCompletion => e(1, 0), Op::PopUnder(n) => e(n.checked_add(1).ok_or("operand too large")?, 1), Op::Dup => e(1, 2), diff --git a/crates/js/src/compiler.rs b/crates/js/src/compiler.rs index 6e16437a5..ae470be44 100644 --- a/crates/js/src/compiler.rs +++ b/crates/js/src/compiler.rs @@ -708,7 +708,16 @@ impl<'a> FnCompiler<'a> { self.emit(Op::Undefined); self.push_number(0.0); self.bind(fin_entry); + // In script and eval code the finally block keeps the completion value of the try or + // catch block when it ends normally (ECMA-262 5.1 section 12.14). + let keeps_completion = self.kind != CodeKind::Function; + if keeps_completion { + self.emit(Op::GetCompletion); + } self.stmts(fin)?; + if keeps_completion { + self.emit(Op::SetCompletion); + } let dispatch: Vec