fix: for-await-of 收尾 return 抛错恢复原始异常值

This commit is contained in:
2026-09-27 18:28:48 +00:00
parent ac13b1a285
commit 93eab980bf
2 changed files with 69 additions and 2 deletions
+27 -2
View File
@@ -739,14 +739,22 @@ impl Vm {
}
let iter_obj = unsafe { &*iterator.as_js_object_ptr() };
let return_si = self.kernel_core.perm_interner().intern("return").0;
let return_fn = self.ordinary_get(iter_obj, return_si, iterator)?;
let return_fn = match self.ordinary_get(iter_obj, return_si, iterator) {
Ok(v) => v,
// return getter 抛出:恢复原始异常值并就地展开,使外围 try/catch 可捕获。
Err(e) => return self.throw_for_of_close_error(&e),
};
if !return_fn.is_object() {
return Ok(());
}
if !unsafe { &*return_fn.as_js_object_ptr() }.is_function() {
return Ok(());
}
let inner = self.call_function_sync(return_fn, iterator, &[])?;
let inner = match self.call_function_sync(return_fn, iterator, &[]) {
Ok(v) => v,
// return() 抛出:恢复原始异常值并就地展开,使外围 try/catch 可捕获。
Err(e) => return self.throw_for_of_close_error(&e),
};
if !inner.is_object() {
return self.raise_type_error("iterator return() result is not an object");
}
@@ -862,6 +870,23 @@ impl Vm {
self.unwind()
}
/// for-await-of 收尾抛错(return getter 或 return() 调用):恢复原始异常值并就地
/// 展开,使外围 try/catch 可捕获。本函数入口已弹出本迭代器条目,故展开路径不得
/// 再弹 for-of 栈(与 throw_for_of_error_value 的唯一区别)。
///
/// 直接 unwind 而非经 raise_call_error:后者以 native_call_depth 门控展开,
/// 异步函数恢复期深度恒大于零会误判为不展开,使异常绕过循环内 catch 直接
/// 拒绝 capability promise。
fn throw_for_of_close_error(&mut self, msg: &str) -> Result<(), String> {
let exc = self
.last_uncaught_value
.take()
.unwrap_or_else(|| oxide_builtins::error::create_from_text(self, msg));
self.exception_value = Some(exc);
self.pending_error_kind = Some(self.thrown_error_kind(exc));
self.unwind()
}
pub(crate) fn dispatch_for_of_close(&mut self) -> Result<(), String> {
vm_trace!("FOR_OF_CLOSE");
let Some(entry) = self.iters.pop_for_of() else {
+42
View File
@@ -239,6 +239,48 @@ fn for_await_of_return_escape_defers_async_close() {
);
}
#[test]
fn for_await_of_close_return_getter_throw_propagates_original() {
// 收尾时 return 的 getter 抛出:外围 catch 须收到原始抛出值,
// 不得是重建的 Error 对象。
assert_eq!(
eval(
"let caught;\
const it={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},get return(){throw 'ret-getter';}}}};\
(async()=>{ try { for await (const x of it) { break; } } catch (e) { caught = e; } return 'caught:' + caught; })()"
),
"\"caught:ret-getter\""
);
}
#[test]
fn for_await_of_close_return_call_throw_propagates_original() {
// 收尾时 return() 调用抛出:外围 catch 须收到原始抛出值。
assert_eq!(
eval(
"let caught;\
const it={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){throw 'ret-call';}}}};\
(async()=>{ try { for await (const x of it) { break; } } catch (e) { caught = e; } return 'caught:' + caught; })()"
),
"\"caught:ret-call\""
);
}
#[test]
fn for_await_of_close_throw_object_identity_preserved() {
// 收尾时 return() 抛出对象:外围 catch 收到的须是同一对象(身份保留),
// 证明原始异常值经异常通道传播而非按文本重建。
assert_eq!(
eval(
"let caught;\
const marker={tag:'x'};\
const it={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){throw marker;}}}};\
(async()=>{ try { for await (const x of it) { break; } } catch (e) { caught = e; } return caught === marker ? 'same' : 'diff:' + caught; })()"
),
"\"same\""
);
}
// ── class/object 生成器方法(异步) ──
// class async 生成器方法:yield 顺序与 done 收敛。顶层 then 链驱动