feat: 释放方法多错合并为 SuppressedError 链

This commit is contained in:
2026-10-09 20:04:45 +00:00
parent ae73fb8da5
commit 328ccafbc0
5 changed files with 122 additions and 45 deletions
Generated
+19 -19
View File
@@ -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",
+1 -1
View File
@@ -23,7 +23,7 @@ members = [
]
[workspace.package]
version = "0.13.17"
version = "0.13.18"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+3
View File
@@ -237,6 +237,9 @@ pub struct Vm {
/// 作用域水位栈:DISPOSE_MARK 压入当前释放栈深度,DISPOSE_POP 弹出并只释放
/// 水位以上条目。与释放栈同生共死(run 边界一并清空)。
pub(crate) dispose_marks: Vec<usize>,
/// 释放循环在飞标记:`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` 读取。仅在一次
+95 -24
View File
@@ -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<bool, String> {
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<JsValue> = 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,33 +725,45 @@ 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<JsValue> = 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);
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))
// 误弹外层作用域的水位。
if truncated {
@@ -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> {
+3
View File
@@ -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 调用期间有效,