From 82c088ac9fa4af7ebfaa3713785ed1b43eeb581f Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 16 Aug 2026 12:00:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20for-await-of=20=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E8=BF=AD=E4=BB=A3=E5=99=A8=20return()=20=E5=BF=BD=E7=95=A5?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E8=A1=A5=E5=BC=82=E5=B8=B8=E5=80=BC=E6=A7=BD?= =?UTF-8?q?=E9=85=8D=E5=AF=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/async_from_sync.rs | 7 ++- .../oxide_vm/tests/exception_channel_tests.rs | 51 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/crates/oxide_vm/src/async_from_sync.rs b/crates/oxide_vm/src/async_from_sync.rs index cee235e..ca03ed1 100644 --- a/crates/oxide_vm/src/async_from_sync.rs +++ b/crates/oxide_vm/src/async_from_sync.rs @@ -145,7 +145,9 @@ const ASFS_INNER_PROP: &str = "__oxide_asfs_inner__"; /// # 参数 /// - `inner`:内层同步迭代器(关闭目标)。 /// - `result`:内层同步方法调用结果(`Err` 为调用抛出的字符串展平文本)。 -fn continue_async_from_sync(vm: &mut Vm, inner: JsValue, result: Result, close_on_rejection: bool) -> NativeResult { +fn continue_async_from_sync( + vm: &mut Vm, inner: JsValue, result: Result, close_on_rejection: bool, +) -> NativeResult { let (promise, resolve, reject) = vm.new_promise_capability(); let reject_with = |vm: &mut Vm, exc: JsValue| { if close_on_rejection { @@ -224,7 +226,10 @@ fn close_sync_iterator(vm: &mut Vm, inner: JsValue) { Err(_) => return, }; if oxide_builtins::iterator::is_callable(return_fn) { + // return() 的抛错被忽略,其值不得外泄进槽覆盖在途异常。 + let saved_uncaught = vm.last_uncaught_value.take(); let _ = vm.call_function_sync(return_fn, inner, &[]); + vm.last_uncaught_value = saved_uncaught; } } diff --git a/crates/oxide_vm/tests/exception_channel_tests.rs b/crates/oxide_vm/tests/exception_channel_tests.rs index 6303397..9b42821 100644 --- a/crates/oxide_vm/tests/exception_channel_tests.rs +++ b/crates/oxide_vm/tests/exception_channel_tests.rs @@ -228,3 +228,54 @@ fn nested_inline_map_rest_poisoned_throws() { "true" ); } + +/// 复现 AsyncFromSyncIterator 的 valueWrapper reject 结算路径(close_sync_iterator +/// 调用户 return() 抛真值):return() 的抛错被忽略后其值不得残留进槽——异常逃逸 +/// 出异步帧(resume_async 经槽恢复原值时取到残留 `true`)拒绝原因被污染。 +/// return() 只在 reject-close 第一次调用时抛错,IteratorClose 转发不抛, +/// 保证残留之后没有新写槽覆盖。 +#[test] +fn sync_iterator_return_throw_no_slot_pollution() { + assert_eq!( + eval( + "var threw = false;\ + var inner = {\ + next() { throw 'x'; },\ + return() { if (!threw) { threw = true; throw true; } return { done: true }; } };\ + var iterable = { [Symbol.iterator]() { return inner; } };\ + async function f() { for await (var y of iterable) {} }\ + f()" + ), + "" + ); +} + +/// 回归:聚合迭代器关闭(close_agg_iterator)遇迭代抛错时调用户 return() 抛真值, +/// 拒绝原因保持迭代抛出的原异常,不得被 return() 的 `true` 替换。 +#[test] +fn agg_iterator_return_throw_no_slot_pollution() { + assert_eq!( + eval( + "var inner = { next() { throw 'x'; }, return() { throw true; } };\ + var iterable = { [Symbol.iterator]() { return inner; } };\ + Promise.all(iterable).then(function () {}, function (e) { return e === true ? 'polluted:true' : e; })" + ), + "\"x\"" + ); +} + +/// 回归:Array.from 迭代输入抛错时 close_iterator 仍调用 return()(记录日志), +/// 且迭代抛出的原异常优先于 return() 的返回值。 +#[test] +fn array_from_iterator_close_original_error_wins() { + assert_eq!( + eval( + "var log = '';\ + var inner = { next() { throw 'x'; }, return() { log += 'closed'; return {}; } };\ + var iterable = { [Symbol.iterator]() { return inner; } };\ + try { Array.from(iterable); } catch (e) { log += ':' + e; }\ + log" + ), + "\"closed:x\"" + ); +}