mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 异常值通道配对——忽略调用槽配对、unwind 接住清槽、生成器参数初始化不覆盖异常值
This commit is contained in:
@@ -635,7 +635,10 @@ fn close_iterator<H: VmHost>(vm: &mut H, iterator: JsValue) {
|
||||
let return_si = vm.kernel_core().perm_interner().intern("return").0;
|
||||
if let Ok(ret) = vm.ordinary_get(iter_obj, return_si, iterator) {
|
||||
if ret.is_object() && unsafe { &*ret.as_js_object_ptr() }.is_function() {
|
||||
// return() 的抛错被忽略,其值不得外泄进槽覆盖在途异常。
|
||||
let saved_uncaught = vm.take_uncaught_value();
|
||||
let _ = vm.call_function_sync(ret, iterator, &[]);
|
||||
vm.restore_uncaught_value(saved_uncaught);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,7 +465,10 @@ pub(crate) fn close_iterator<H: VmHost>(vm: &mut H, iterator: JsValue) {
|
||||
let return_si = vm.kernel_core().perm_interner().intern("return").0;
|
||||
if let Ok(ret) = vm.ordinary_get(iter_obj, return_si, iterator) {
|
||||
if is_callable(ret) {
|
||||
// return() 的抛错被忽略,其值不得外泄进槽覆盖在途异常。
|
||||
let saved_uncaught = vm.take_uncaught_value();
|
||||
let _ = vm.call_function_sync(ret, iterator, &[]);
|
||||
vm.restore_uncaught_value(saved_uncaught);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +170,12 @@ pub trait VmHost {
|
||||
/// 取回在 String 展平调用边界上保留下来的原始抛出 JsValue,
|
||||
/// 使迭代器包装器能重新抛出原错误而非二次包装。
|
||||
fn take_uncaught_value(&mut self) -> Option<JsValue>;
|
||||
/// 恢复被忽略调用暂存的原始抛出值(与 [`Self::take_uncaught_value`] 配对)。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// 忽略调用(如 IteratorClose 的 `return()`)抛错时不得让自身值覆盖槽——
|
||||
/// 调用前暂存、调用后恢复,保证在途异常值跨忽略调用存活。
|
||||
fn restore_uncaught_value(&mut self, value: Option<JsValue>);
|
||||
|
||||
// 错误处理
|
||||
fn checked_object_ptr(&mut self, val: JsValue, error_msg: &str) -> Result<Option<*mut JsObject>, String>;
|
||||
|
||||
@@ -747,14 +747,24 @@ impl Vm {
|
||||
&& self.sub_modules[sub_idx].is_generator
|
||||
&& self.sub_modules[sub_idx].is_async
|
||||
{
|
||||
let gen_pc = self.pc;
|
||||
let gen = self.create_async_generator_object(callee, this_value, &args)?;
|
||||
self.regs[0] = gen;
|
||||
// 参数初始化抛错已就地展开(unwind 改写 pc 至 catch):异常值已
|
||||
// 写入 catch 参数,再写结果寄存器会覆盖之——仅在初始化成功(pc 未动)
|
||||
// 时交付生成器对象。
|
||||
if self.pc == gen_pc {
|
||||
self.regs[0] = gen;
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
// 生成器函数调用返回迭代器对象,不执行函数体。
|
||||
if sub_idx < self.sub_modules.len() && self.sub_modules[sub_idx].is_generator {
|
||||
let gen_pc = self.pc;
|
||||
let gen = self.create_generator_object(callee, this_value, &args)?;
|
||||
self.regs[0] = gen;
|
||||
// 参数初始化抛错已就地展开时不得覆盖 catch 参数(同上)。
|
||||
if self.pc == gen_pc {
|
||||
self.regs[0] = gen;
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
// 异步函数调用返回 capability promise,立即同步执行 body 到首个 await。
|
||||
|
||||
@@ -1579,7 +1579,10 @@ fn close_agg_iterator(vm: &mut Vm, iterator: JsValue) {
|
||||
let iter_obj = unsafe { &*iterator.as_js_object_ptr() };
|
||||
if let Ok(ret) = vm.ordinary_get(iter_obj, return_si, iterator) {
|
||||
if oxide_builtins::iterator::is_callable(ret) {
|
||||
// return() 的抛错被忽略,其值不得外泄进槽覆盖在途异常。
|
||||
let saved_uncaught = vm.last_uncaught_value.take();
|
||||
let _ = vm.call_function_sync(ret, iterator, &[]);
|
||||
vm.last_uncaught_value = saved_uncaught;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2123,6 +2123,9 @@ impl oxide_runtime_api::VmHost for Vm {
|
||||
fn take_uncaught_value(&mut self) -> Option<JsValue> {
|
||||
self.last_uncaught_value.take()
|
||||
}
|
||||
fn restore_uncaught_value(&mut self, value: Option<JsValue>) {
|
||||
self.last_uncaught_value = value;
|
||||
}
|
||||
fn property_key_si(&mut self, val: JsValue) -> u32 {
|
||||
// Object-key conversion (to_string_full) failures degrade to the empty key here:
|
||||
// this trait path is used by Reflect/Object builtins; computed property access
|
||||
|
||||
@@ -45,14 +45,24 @@ impl Vm {
|
||||
.collect();
|
||||
// 异步生成器函数调用返回异步生成器迭代器对象。
|
||||
if is_generator && is_async {
|
||||
let gen_pc = self.pc;
|
||||
let gen = self.create_async_generator_object(callee, this_value, &args)?;
|
||||
self.regs[0] = gen;
|
||||
// 参数初始化抛错已就地展开(unwind 改写 pc 至 catch):异常值已
|
||||
// 写入 catch 参数,再写结果寄存器会覆盖之——仅在初始化成功(pc 未动)
|
||||
// 时交付生成器对象。
|
||||
if self.pc == gen_pc {
|
||||
self.regs[0] = gen;
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
// 生成器函数调用返回迭代器对象,不执行函数体。
|
||||
if is_generator {
|
||||
let gen_pc = self.pc;
|
||||
let gen = self.create_generator_object(callee, this_value, &args)?;
|
||||
self.regs[0] = gen;
|
||||
// 参数初始化抛错已就地展开时不得覆盖 catch 参数(同上)。
|
||||
if self.pc == gen_pc {
|
||||
self.regs[0] = gen;
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
// 异步函数调用返回 capability promise,立即同步执行 body 到首个 await。
|
||||
|
||||
@@ -656,11 +656,14 @@ impl Vm {
|
||||
return Err("FOR_OF_DONE iterator is not an object".into());
|
||||
}
|
||||
|
||||
// 调用 next()/读 done 前清空异常值槽:跨调用残留不得污染本指令的取槽。
|
||||
self.last_uncaught_value = None;
|
||||
let iter_obj = unsafe { &*iterator.as_js_object_ptr() };
|
||||
let next_si = self.kernel_core.perm_interner().intern("next").0;
|
||||
let next_fn = match self.ordinary_get(iter_obj, next_si, iterator) {
|
||||
Ok(v) => v,
|
||||
// 错误路径只经 throw_for_of_error 传播,不写 rd(rd 保持循环决策位,
|
||||
// 异常展开后指令流离开循环,残留值无效)。
|
||||
Err(e) => return self.throw_for_of_error(e),
|
||||
};
|
||||
let result = match self.call_function_sync(next_fn, iterator, &[]) {
|
||||
@@ -694,6 +697,7 @@ impl Vm {
|
||||
let value_si = self.kernel_core.perm_interner().intern("value").0;
|
||||
self.regs[rd] = match self.ordinary_get(result_obj, value_si, result) {
|
||||
Ok(v) => v,
|
||||
// 错误路径只经 throw_for_of_error 传播,不写 rd(同上)。
|
||||
Err(e) => return self.throw_for_of_error(e),
|
||||
};
|
||||
Ok(())
|
||||
@@ -710,6 +714,8 @@ impl Vm {
|
||||
};
|
||||
self.exception_value = Some(exc);
|
||||
self.pending_error_kind = Some(self.thrown_error_kind(exc));
|
||||
// 无论取到原值还是重建错误对象,异常必须展开传播,不得静默返回 Ok——
|
||||
// 否则 for-of 循环继续推进形成不终止。
|
||||
self.unwind()
|
||||
}
|
||||
|
||||
@@ -764,9 +770,13 @@ impl Vm {
|
||||
if suppress_return_error {
|
||||
let saved_exc = self.exception_value;
|
||||
let saved_kind = self.pending_error_kind;
|
||||
// 忽略调用期间的原始异常值一并暂存:return() 自身抛错按契约被在途
|
||||
// 异常替代,其值不得外泄进槽供后续 take 误取。
|
||||
let saved_uncaught = self.last_uncaught_value.take();
|
||||
let _ = self.call_function_sync(return_fn, iterator, &[]);
|
||||
self.exception_value = saved_exc;
|
||||
self.pending_error_kind = saved_kind;
|
||||
self.last_uncaught_value = saved_uncaught;
|
||||
} else {
|
||||
let inner = match self.call_function_sync(return_fn, iterator, &[]) {
|
||||
Ok(v) => v,
|
||||
|
||||
@@ -522,6 +522,8 @@ impl Vm {
|
||||
handler.finally_active = true;
|
||||
self.try_stack.push(handler);
|
||||
self.pc = finally_pc;
|
||||
// 在途异常已进入 finally 处理流程:槽作废,防残留值被后续 take 误取。
|
||||
self.last_uncaught_value = None;
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(catch_pc) = handler.catch_pc {
|
||||
@@ -529,6 +531,8 @@ impl Vm {
|
||||
let exc = self.exception_value.take().unwrap_or(JsValue::undefined());
|
||||
self.regs[0] = exc;
|
||||
self.pc = catch_pc;
|
||||
// 在途异常已写入 catch 参数:槽作废,防残留值被后续 take 误取。
|
||||
self.last_uncaught_value = None;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
//! 异常值通道配对回归测试:`last_uncaught_value` 单槽必须与异常生命周期配对——
|
||||
//! 忽略路径(IteratorClose 的 return() 抛错被丢弃)不得把值残留进槽,unwind 进入
|
||||
//! finally/catch 接住异常后槽作废,否则后续 take 会误取残留值污染拒绝原因。
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_vm::promise::promise_settled_value;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
/// 执行源码并格式化顶层结果;Promise 结果取其 drain 后的 settled 值。
|
||||
fn eval(source: &str) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let program = match oxide_parser::parse(&allocator, source) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return format!("parse error: {}", e[0].message),
|
||||
};
|
||||
let module = match Compiler::new().compile(&program) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return format!("compile error: {e}"),
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
match vm.run(&module) {
|
||||
Ok(result) => format_value(&vm, result),
|
||||
Err(e) => format!("vm error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_value(vm: &Vm, val: oxide_vm::JsValue) -> String {
|
||||
if val.is_string() {
|
||||
format!("\"{}\"", vm.lookup_str(val).unwrap_or_default())
|
||||
} else if val.is_object() {
|
||||
let obj = unsafe { &*val.as_js_object_ptr() };
|
||||
if obj.is_promise_obj() {
|
||||
match promise_settled_value(obj) {
|
||||
Some((true, v)) => format_value(vm, v),
|
||||
Some((false, v)) => format!("<rejected {}>", format_value(vm, v)),
|
||||
None => "<pending>".to_string(),
|
||||
}
|
||||
} else {
|
||||
"[object]".to_string()
|
||||
}
|
||||
} else {
|
||||
format!("{val}")
|
||||
}
|
||||
}
|
||||
|
||||
/// 复现 return() 抛真值(IteratorClose suppress 路径写槽不恢复)+ finally 内 await
|
||||
/// 窗口(unwind 进 finally 不清槽)→ 拒绝原因必须是循环体抛出的 "body",
|
||||
/// 不得被 return() 抛出的 `true` 残留污染。
|
||||
#[test]
|
||||
fn return_throw_finally_await_reason_is_body_not_true() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var iter = { [Symbol.iterator]() { return {\
|
||||
next() { return { value: 1, done: false }; },\
|
||||
return() { throw true; } }; } };\
|
||||
async function f() {\
|
||||
try { for (const x of iter) { throw 'body'; } }\
|
||||
finally { await Promise.resolve(); } }\
|
||||
f()"
|
||||
),
|
||||
"<rejected \"body\">"
|
||||
);
|
||||
}
|
||||
|
||||
/// 复现 catch 接住异常后槽残留(return() 抛真值写槽、unwind 进 catch 不清槽),
|
||||
/// 后续新抛的裸值必须作为拒绝原因,不得被残留的 `true` 替换。
|
||||
#[test]
|
||||
fn catch_then_new_throw_reason_is_new_value() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var iter = { [Symbol.iterator]() { return {\
|
||||
next() { return { value: 1, done: false }; },\
|
||||
return() { throw true; } }; } };\
|
||||
async function f() {\
|
||||
try { for (const x of iter) { throw 'body'; } } catch (e) {}\
|
||||
throw 'real'; }\
|
||||
f()"
|
||||
),
|
||||
"<rejected \"real\">"
|
||||
);
|
||||
}
|
||||
|
||||
/// 回归:return() 不抛错时 for-of 提前退出仍调用 return() 且循环体异常优先。
|
||||
#[test]
|
||||
fn return_ok_iterator_close_still_called_body_error_wins() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var log = '';\
|
||||
var iter = { [Symbol.iterator]() { return {\
|
||||
next() { return { value: 1, done: false }; },\
|
||||
return() { log = 'closed'; return {}; } }; } };\
|
||||
try { for (const x of iter) { throw 'A'; } } catch (e) { log += ':' + e; }\
|
||||
log"
|
||||
),
|
||||
"\"closed:A\""
|
||||
);
|
||||
}
|
||||
|
||||
/// 回归:迭代器自然结束(done:true)时不调用 return(),通道无残留。
|
||||
#[test]
|
||||
fn natural_done_skips_return_no_slot_residue() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var log = '';\
|
||||
var iter = { [Symbol.iterator]() { return {\
|
||||
next() { return { value: 1, done: true }; },\
|
||||
return() { log = 'closed'; return {}; } }; } };\
|
||||
for (const x of iter) {}\
|
||||
Promise.resolve().then(function () { return log; })"
|
||||
),
|
||||
"\"\""
|
||||
);
|
||||
}
|
||||
|
||||
/// 复现 B067 RG1 裸值传递:promise 链中抛出的裸值 1 必须原样传递,
|
||||
/// 拒绝原因不得被前置路径残留值替换。
|
||||
#[test]
|
||||
fn promise_chain_bare_reason_preserved() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"Promise.resolve()\
|
||||
.then(function () { throw 1; })\
|
||||
.then(function () {}, function (e) { return e; })\
|
||||
.then(function (e) { return 'got:' + e; })"
|
||||
),
|
||||
"\"got:1\""
|
||||
);
|
||||
}
|
||||
|
||||
/// 复现 21 超时代表面(同步 rest 收集):迭代器 next() 返回 value 为抛错 getter
|
||||
/// 的结果对象时,`[...it]` 必须同步抛错,不得无限循环。
|
||||
#[test]
|
||||
fn sync_rest_poisoned_value_getter_throws() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var v = Object.defineProperty({}, 'value', { get: function () { throw 'x'; } });\
|
||||
var it = { [Symbol.iterator]() { return { next: function () { return v; } }; } };\
|
||||
try { [...it]; 'no-throw' } catch (e) { e === 'x' }"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
/// 复现 21 超时代表面(async-generator 参数解构):rest 收集遇 poisoned value 时
|
||||
/// `f(it)` 调用点必须同步抛原值,异常可被用户 catch 捕获。
|
||||
#[test]
|
||||
fn async_gen_param_destructure_poisoned_throws() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var v = Object.defineProperty({}, 'value', { get: function () { throw 'x'; } });\
|
||||
var it = { [Symbol.iterator]() { return { next: function () { return v; } }; } };\
|
||||
async function* f([...x]) {}\
|
||||
try { f(it); 'no-throw' } catch (e) { e === 'x' }"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
/// 回归:生成器参数 rest 收集正常迭代时不误伤(pc 保护只拦截异常展开路径)。
|
||||
#[test]
|
||||
fn gen_param_rest_normal_collect_ok() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"function* f([...x]) { return x.length; }\
|
||||
var g = f([1, 2, 3]);\
|
||||
var r = g.next();\
|
||||
r.value + ':' + r.done"
|
||||
),
|
||||
"\"3:true\""
|
||||
);
|
||||
}
|
||||
|
||||
/// 回归:async-generator 参数 rest 收集正常迭代时返回正确结果。
|
||||
#[test]
|
||||
fn async_gen_param_rest_normal_collect_ok() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"async function* f([...x]) { return x.join('-'); }\
|
||||
f([1, 2]).next().then(function (r) { return r.value + ':' + r.done; })"
|
||||
),
|
||||
"\"1-2:true\""
|
||||
);
|
||||
}
|
||||
|
||||
/// 复现 21 超时代表面(async for-await-of + rest 解构):poisoned value getter
|
||||
/// 抛裸值 'x' 时 promise 必须以 'x' 拒绝,不得无限循环。
|
||||
#[test]
|
||||
fn async_for_await_of_rest_poisoned_rejects() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var v = Object.defineProperty({}, 'value', { get: function () { throw 'x'; } });\
|
||||
var it = { [Symbol.iterator]() { return { next: function () { return v; } }; } };\
|
||||
async function fn() { for await (var [...x] of [it]) { return; } }\
|
||||
fn()"
|
||||
),
|
||||
"<rejected \"x\">"
|
||||
);
|
||||
}
|
||||
|
||||
/// 复现 21 超时代表面(同步生成器参数解构):生成器参数 rest 收集遇 poisoned
|
||||
/// value 时调用点必须同步抛错,异常可被用户 catch 捕获。
|
||||
#[test]
|
||||
fn gen_param_destructure_poisoned_throws() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var v = Object.defineProperty({}, 'value', { get: function () { throw 'x'; } });\
|
||||
var it = { [Symbol.iterator]() { return { next: function () { return v; } }; } };\
|
||||
function* f([...x]) {}\
|
||||
try { f(it).next(); 'no-throw' } catch (e) { e === 'x' }"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
/// 复现 21 超时代表面(嵌套 inline + 真实帧深度链):rest 收集在 map 回调
|
||||
/// (inline dispatch)调用的普通函数内执行,poisoned value 异常须穿透
|
||||
/// 深度链原样到达用户 catch。
|
||||
#[test]
|
||||
fn nested_inline_map_rest_poisoned_throws() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var v = Object.defineProperty({}, 'value', { get: function () { throw 'x'; } });\
|
||||
var it = { [Symbol.iterator]() { return { next: function () { return v; } }; } };\
|
||||
function collect(p) { return [...p]; }\
|
||||
try { [1].map(function (x) { return collect(it); }); 'no-throw' } catch (e) { e === 'x' }"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user