diff --git a/Cargo.lock b/Cargo.lock index f5be0bd3..b2ef41f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.13.17" +version = "0.13.18" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.13.17" +version = "0.13.18" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.13.17" +version = "0.13.18" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.13.17" +version = "0.13.18" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.13.17" +version = "0.13.18" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.13.17" +version = "0.13.18" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.13.17" +version = "0.13.18" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.13.17" +version = "0.13.18" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.13.17" +version = "0.13.18" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.13.17" +version = "0.13.18" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.13.17" +version = "0.13.18" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.13.17" +version = "0.13.18" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.13.17" +version = "0.13.18" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.13.17" +version = "0.13.18" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.13.17" +version = "0.13.18" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.13.17" +version = "0.13.18" dependencies = [ "num-bigint", "num-traits", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.13.17" +version = "0.13.18" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.13.17" +version = "0.13.18" dependencies = [ "bumpalo", "dashmap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.13.17" +version = "0.13.18" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index a0d454d8..9a1d32ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.13.17" +version = "0.13.18" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_vm/src/vm/mod.rs b/crates/oxide_vm/src/vm/mod.rs index 0815f8fb..623b7d18 100644 --- a/crates/oxide_vm/src/vm/mod.rs +++ b/crates/oxide_vm/src/vm/mod.rs @@ -237,6 +237,9 @@ pub struct Vm { /// 作用域水位栈:DISPOSE_MARK 压入当前释放栈深度,DISPOSE_POP 弹出并只释放 /// 水位以上条目。与释放栈同生共死(run 边界一并清空)。 pub(crate) dispose_marks: Vec, + /// 释放循环在飞标记:`dispose_above`/`dispatch_dispose_pop` 期间置位,防释放 + /// 方法抛错触发的嵌套 `unwind` 重入 `dispose_above` 重复释放与合并。 + pub(crate) disposing: bool, /// 本次 native 调用的 spill 溢出实参区:`spill_stack[base..base+count)`。 /// 实参数超过寄存器窗口(253)时,窗口外的实参转存 spill 栈(GC 根), /// native 侧经 `VmHost::native_arg_count`/`native_arg_at` 读取。仅在一次 diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 9e684924..1f54d6f7 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -8,6 +8,14 @@ use oxide_types::private_key::{ }; use oxide_types::value::JsValue; +/// 单资源释放结局(规范 Dispose 语义):`Done` = 释放成功;`NoMethod` = 无释放方法 +/// (非对象 / 方法未定义 / 不可调用,跳过);`Err(v)` = 释放方法抛错(v 为原始异常值)。 +enum DisposeOutcome { + Done, + NoMethod, + Err(JsValue), +} + impl Vm { pub(crate) fn dispatch_new_expression(&mut self, rd: usize, a: usize, b: usize) -> Result { let constructor_reg = a; @@ -671,20 +679,44 @@ impl Vm { } /// 作用域出口:弹出水位,逆序释放水位以上资源并截断释放栈(规范 - /// DisposeResources)。释放方法自身抛错按第一错误向外传播。 + /// DisposeResources)。多个释放方法抛错时按规范折叠成 SuppressedError 链 + /// (后抛为 error、前值为 suppressed),循环不因错中断;收尾有合并错误时 + /// 写入在途异常并展开到最近处理器。 + /// + /// # 边界与前提 + /// - 正常完成穿越:completion 初值为 None(无在途异常)。 /// /// # 副作用 /// - 释放栈截断到水位、水位栈弹出;释放方法是任意 JS 调用(可再入, /// 嵌套 using 声明会在其内再压释放栈)。 + /// - 有合并错误时改写 `exception_value`/`pending_error_kind` 并触发 `unwind`。 pub(crate) fn dispatch_dispose_pop(&mut self) -> Result<(), String> { + // 重入守卫:释放方法抛错触发的嵌套 unwind 会重入本函数,置位时直接返回, + // 防重复释放与合并(先于弹水位,防重入误弹外层水位)。 + if self.disposing { + return Ok(()); + } let mark = match self.dispose_marks.pop() { Some(m) => m, None => return Ok(()), }; vm_trace!("DISPOSE_POP mark={} depth={}", mark, self.dispose_stack.len()); + self.disposing = true; + let mut completion: Option = None; while self.dispose_stack.len() > mark { let (value, hint) = self.dispose_stack.pop().expect("水位以上必有条目"); - self.dispose_value(value, hint)?; + match self.dispose_value(value, hint) { + DisposeOutcome::Done | DisposeOutcome::NoMethod => {} + DisposeOutcome::Err(exc) => { + completion = oxide_builtins::disposable_stack::merge_dispose_error(self, completion, exc); + } + } + } + self.disposing = false; + if let Some(exc) = completion { + self.exception_value = Some(exc); + self.pending_error_kind = Some(self.thrown_error_kind(exc)); + self.unwind()?; } Ok(()) } @@ -693,31 +725,43 @@ impl Vm { /// `>= depth` 的基线条目(保两栈一致,防异常路径基线泄漏)。 /// /// # 步骤 - /// 1. 逐条弹出并释放(`dispose_value`)。 - /// 2. 释放方法抛错时,新错误替代在途异常向外传播(`exception_value` 改写), - /// 剩余资源继续释放(其错误被抑制,多错合并归后续任务)。 - /// 3. 截断释放栈后,弹出水位栈中 `>= depth` 的基线条目。 + /// 1. 在途异常作 completion 种子(异常穿越时被折叠进 suppressed)。 + /// 2. 逐条弹出并释放(`dispose_value`);多个释放方法抛错时按规范折叠成 + /// SuppressedError 链(后抛为 error、前值为 suppressed),循环不因错中断。 + /// 3. 收尾写回 completion:有合并错误则替代在途异常并更新错误类型;无合并 + /// 错误则还原在途异常(类型不变)。 + /// 4. 截断释放栈后,弹出水位栈中 `>= depth` 的基线条目。 /// /// # 返回值 - /// `true` = 有释放方法抛错(新错误已写入 `exception_value`);`false` = 全部正常。 + /// `true` = 有释放方法抛错(合并错误已写入 `exception_value`);`false` = 全部正常。 /// /// # 副作用 /// - 释放栈截断到 `depth`、水位栈弹出 `>= depth` 的基线。 /// - 有释放方法抛错时改写 `exception_value`/`pending_error_kind`。 pub(crate) fn dispose_above(&mut self, depth: usize) -> bool { + // 重入守卫:释放方法抛错触发的嵌套 unwind 会重入本函数,置位时直接返回, + // 防重复释放与合并(合并链会被二次折叠)。 + if self.disposing { + return false; + } + self.disposing = true; + let mut completion: Option = self.exception_value; let mut threw = false; let truncated = self.dispose_stack.len() > depth; while self.dispose_stack.len() > depth { let (value, hint) = self.dispose_stack.pop().expect("水位以上必有条目"); - if let Err(e) = self.dispose_value(value, hint) { - let exc = self - .last_uncaught_value - .take() - .unwrap_or_else(|| oxide_builtins::error::create_from_text(self, &e)); - let kind = self.thrown_error_kind(exc); - self.exception_value = Some(exc); - self.pending_error_kind = Some(kind); - threw = true; + match self.dispose_value(value, hint) { + DisposeOutcome::Done | DisposeOutcome::NoMethod => {} + DisposeOutcome::Err(exc) => { + completion = oxide_builtins::disposable_stack::merge_dispose_error(self, completion, exc); + threw = true; + } + } + } + if let Some(exc) = completion { + self.exception_value = Some(exc); + if threw { + self.pending_error_kind = Some(self.thrown_error_kind(exc)); } } // 仅在实际截断释放栈时同步弹基线,防 no-op 调用(如 dispose_top_n(0)) @@ -727,6 +771,7 @@ impl Vm { self.dispose_marks.pop(); } } + self.disposing = false; threw } @@ -744,34 +789,60 @@ impl Vm { /// Symbol.dispose;异步先取 Symbol.asyncDispose,未定义回退 Symbol.dispose), /// 不可调用跳过;以资源自身为 this 零参调用。 /// + /// # 返回值 + /// `Done` = 释放成功;`NoMethod` = 无释放方法(跳过);`Err(v)` = 释放方法抛错 + /// (v 为原始异常值,经 `last_uncaught_value` 恢复,无则按错误文本建普通 Error)。 + /// /// # 边界与前提 + /// - 取方法 / 调用方法任一步抛错均归入 `Err`(getter 抛错与调用抛错同处置)。 /// - 异步方法结果为对象时须 Await 挂起,归后续任务;本期同步调用,结果丢弃。 - fn dispose_value(&mut self, value: JsValue, hint: u16) -> Result<(), String> { + fn dispose_value(&mut self, value: JsValue, hint: u16) -> DisposeOutcome { if !value.is_object() { - return Ok(()); + return DisposeOutcome::NoMethod; } let val_obj = unsafe { &*value.as_js_object_ptr() }; let dispose_key = encode_symbol_key(self.realm_id(), WELL_KNOWN_SYMBOL_DISPOSE); let method = if hint == 1 { let async_key = encode_symbol_key(self.realm_id(), WELL_KNOWN_SYMBOL_ASYNC_DISPOSE); - let async_method = self.ordinary_get(val_obj, async_key, value)?; + let async_method = match self.ordinary_get(val_obj, async_key, value) { + Ok(m) => m, + Err(e) => return self.dispose_thrown(e), + }; if async_method.is_undefined() { - self.ordinary_get(val_obj, dispose_key, value)? + match self.ordinary_get(val_obj, dispose_key, value) { + Ok(m) => m, + Err(e) => return self.dispose_thrown(e), + } } else { async_method } } else { - self.ordinary_get(val_obj, dispose_key, value)? + match self.ordinary_get(val_obj, dispose_key, value) { + Ok(m) => m, + Err(e) => return self.dispose_thrown(e), + } }; if !method.is_object() { - return Ok(()); + return DisposeOutcome::NoMethod; } let method_obj = unsafe { &*method.as_js_object_ptr() }; if !method_obj.is_function() { - return Ok(()); + return DisposeOutcome::NoMethod; } - let _ = self.call_function_sync(method, value, &[])?; - Ok(()) + match self.call_function_sync(method, value, &[]) { + Ok(_) => DisposeOutcome::Done, + Err(e) => self.dispose_thrown(e), + } + } + + /// 释放方法抛错时恢复原始异常值(经 `last_uncaught_value`,无则按错误文本建 + /// 普通 Error),归入 `Err` 结局。 + fn dispose_thrown(&mut self, e: String) -> DisposeOutcome { + let exc = self + .last_uncaught_value + .take() + .unwrap_or_else(|| oxide_builtins::error::create_from_text(self, &e)); + DisposeOutcome::Err(exc) } pub(crate) fn dispatch_for_of_init(&mut self, a: usize) -> Result<(), String> { diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index bb6ad81a..0248ef6c 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -132,6 +132,7 @@ impl Vm { spill_stack: Vec::new(), dispose_stack: Vec::new(), dispose_marks: Vec::new(), + disposing: false, native_overflow_base: 0, native_overflow_count: 0, try_stack: Vec::new(), @@ -295,6 +296,7 @@ impl Vm { spill_stack: Vec::new(), dispose_stack: Vec::new(), dispose_marks: Vec::new(), + disposing: false, native_overflow_base: 0, native_overflow_count: 0, try_stack: Vec::new(), @@ -523,6 +525,7 @@ impl Vm { // 被误释放或悬垂,随执行状态一并清空;水位栈同清,防陈旧水位误截断。 self.dispose_stack.clear(); self.dispose_marks.clear(); + self.disposing = false; self.native_overflow_base = 0; self.native_overflow_count = 0; // native pack 实参区上界与溢出描述符同构:仅一次 native 调用期间有效,