fix: for-of/for-in 提前退出时按规范顺序完成 IteratorClose
return/break/continue 逃出 for-of 时此前完全不执行 IteratorClose: BREAK/CONTINUE/RETURN 指令现在经 ext 字携带词法逃出的迭代器层数 (for-of + for-in 各 16 位),运行时在全部 finally 穿越之后、真正 跳转/返回之前按 LIFO 关闭——与规范 §13.7.5.4(循环体完成值已含 内部 try-finally 处理,IteratorClose 在后)顺序一致。 - emit 词法:LabelCtx 增加 for-of/for-in 深度计数,loop/switch/label 打开时快照,break/continue/return 按当前深度减目标深度计算逃出层数 (switch 内 break 以 switch 打开点为基准,目标循环自身不计入) - 指令编码:brk/cont/ret 三参构造,lower 跳转路径补发 ext 字 - 运行时:dispatch_break/continue/return 读取逃出计数,无 finally 时 直接关闭后跳转/返回,有 finally 时随 Completion 悬挂由 TRY_FINALLY_END 在穿越完成后消费;生成器 .return()/.throw() 注入经 complete_generator_return 全关快照栈迭代器;return() 抛错经 unwind 替代完成值并关闭剩余迭代器 - 顺带:for-in 逃出残留(无 return() 语义,仅弹栈);for-await-of 的 异步迭代器不做同步关闭(return() promise 须 await,走异步机制) - 新增 15 项回归测试:return/labeled break/continue/switch/while/finally 顺序/抛错优先/生成器注入/for-in/for-await-of 嵌套
This commit is contained in:
@@ -647,21 +647,33 @@ impl Vm {
|
||||
/// - `Ok(Some(value))`:直接完成,无需再 dispatch(`value` 为返回值);
|
||||
/// - `Ok(None)`:进入 finally 穿越,调用方须继续 `dispatch()`。
|
||||
pub(crate) fn complete_generator_return(&mut self, value: JsValue) -> Result<Option<JsValue>, String> {
|
||||
self.pop_frame_catch_handlers();
|
||||
// 纯 catch handler 的清理延后到完成消费处:record_completion 在 finally
|
||||
// 穿越路径弹出逃出的 catch-only handler;无 finally 时 do_return 弹帧前
|
||||
// 兜底清理。若提前弹出,return() 抛错经 unwind 展开时将找不到外围 catch。
|
||||
let crossed = self
|
||||
.try_stack
|
||||
.iter()
|
||||
.filter(|h| h.frame_depth == self.frames.len() && h.finally_pc.is_some())
|
||||
.count();
|
||||
// .return()/.throw() 注入时生成器挂起快照中打开的全部迭代器一并逃出
|
||||
// (栈上迭代器属于本生成器),完成恢复处统一关闭。
|
||||
let for_of_count = self.iters.for_of_iters.len();
|
||||
let for_in_count = self.iters.for_in_iters.len();
|
||||
if let Some(finally_pc) = self.record_completion(crate::vm::Completion::Return {
|
||||
value,
|
||||
remaining_finally: crossed,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
}) {
|
||||
self.pc = finally_pc;
|
||||
return Ok(None);
|
||||
}
|
||||
// 无 finally:直接交付返回(弹出生成器帧后 frames 为空 → Some(value))。
|
||||
self.do_return(value)
|
||||
// 无 finally:关闭逃出迭代器后直接交付返回(弹出生成器帧后 frames 为空 → Some(value))。
|
||||
match self.close_escaped_iters(for_of_count, for_in_count) {
|
||||
Ok(true) => self.do_return(value),
|
||||
Ok(false) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -842,6 +842,7 @@ mod tests {
|
||||
vm.iters.for_of_iters.push(crate::vm_state::ForOfEntry {
|
||||
iterator: JsValue::from_js_object(child_session),
|
||||
last_result: JsValue::from_js_object(root_session),
|
||||
is_async: false,
|
||||
});
|
||||
|
||||
let mut roots = Vec::new();
|
||||
@@ -876,6 +877,8 @@ mod tests {
|
||||
vm.pending_completion = Some(crate::vm::Completion::Return {
|
||||
value: JsValue::from_js_object(session),
|
||||
remaining_finally: 0,
|
||||
for_of_count: 0,
|
||||
for_in_count: 0,
|
||||
});
|
||||
|
||||
let mut forwarding = std::collections::HashMap::new();
|
||||
|
||||
@@ -44,7 +44,8 @@ pub(crate) struct SuspendedFrame {
|
||||
|
||||
impl SuspendedFrame {
|
||||
/// 全空帧(New 阶段构造状态盒时用)。
|
||||
pub fn new_empty() -> Self { SuspendedFrame {
|
||||
pub fn new_empty() -> Self {
|
||||
SuspendedFrame {
|
||||
regs: Box::new([JsValue::undefined(); 256]),
|
||||
pc: 0,
|
||||
bytecode: Arc::default(),
|
||||
@@ -233,9 +234,16 @@ impl SuspendedFrame {
|
||||
self.exception_value = self.exception_value.map(&mut rewrite);
|
||||
self.pending_exception = self.pending_exception.map(&mut rewrite);
|
||||
self.pending_completion = self.pending_completion.map(|completion| match completion {
|
||||
Completion::Return { value, remaining_finally } => Completion::Return {
|
||||
Completion::Return {
|
||||
value,
|
||||
remaining_finally,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
} => Completion::Return {
|
||||
value: rewrite(value),
|
||||
remaining_finally,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
},
|
||||
other => other,
|
||||
});
|
||||
@@ -295,6 +303,7 @@ impl SuspendedFrame {
|
||||
.map(|entry| ForOfEntry {
|
||||
iterator: rewrite(entry.iterator),
|
||||
last_result: rewrite(entry.last_result),
|
||||
is_async: entry.is_async,
|
||||
})
|
||||
.collect(),
|
||||
delegated_iterator: self.delegated_iterator.map(&mut rewrite),
|
||||
@@ -304,9 +313,16 @@ impl SuspendedFrame {
|
||||
pending_exception: self.pending_exception.map(&mut rewrite),
|
||||
pending_error_kind: self.pending_error_kind,
|
||||
pending_completion: self.pending_completion.map(|completion| match completion {
|
||||
Completion::Return { value, remaining_finally } => Completion::Return {
|
||||
Completion::Return {
|
||||
value,
|
||||
remaining_finally,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
} => Completion::Return {
|
||||
value: rewrite(value),
|
||||
remaining_finally,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
},
|
||||
other => other,
|
||||
}),
|
||||
@@ -375,16 +391,21 @@ mod tests {
|
||||
frame.for_of_iters.push(ForOfEntry {
|
||||
iterator: JsValue::float(6.0),
|
||||
last_result: JsValue::float(7.0),
|
||||
is_async: false,
|
||||
});
|
||||
frame.delegated_iterator = Some(JsValue::float(8.0));
|
||||
frame.saved_bytecode_stack.push(Arc::from(vec![0u32]));
|
||||
frame.saved_immutables_stack.push(std::ptr::slice_from_raw_parts(std::ptr::null(), 0));
|
||||
frame
|
||||
.saved_immutables_stack
|
||||
.push(std::ptr::slice_from_raw_parts(std::ptr::null(), 0));
|
||||
frame.exception_value = Some(JsValue::float(9.0));
|
||||
frame.pending_exception = Some(JsValue::float(10.0));
|
||||
frame.pending_error_kind = Some("Error");
|
||||
frame.pending_completion = Some(Completion::Return {
|
||||
value: JsValue::float(11.0),
|
||||
remaining_finally: 0,
|
||||
for_of_count: 0,
|
||||
for_in_count: 0,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -426,6 +447,8 @@ mod tests {
|
||||
machine.pending_completion = Some(Completion::Return {
|
||||
value: JsValue::float(46.0),
|
||||
remaining_finally: 1,
|
||||
for_of_count: 0,
|
||||
for_in_count: 0,
|
||||
});
|
||||
|
||||
let mut frame = SuspendedFrame::new_empty();
|
||||
@@ -435,7 +458,7 @@ mod tests {
|
||||
assert_eq!(frame.delegated_iterator, Some(JsValue::float(45.0)));
|
||||
assert!(matches!(
|
||||
frame.pending_completion,
|
||||
Some(Completion::Return { value, remaining_finally: 1 }) if value == JsValue::float(46.0)
|
||||
Some(Completion::Return { value, remaining_finally: 1, .. }) if value == JsValue::float(46.0)
|
||||
));
|
||||
|
||||
// sub_modules 为空表:恢复应报"跨 run"错(保持现有三处行为)。
|
||||
|
||||
+75
-10
@@ -251,11 +251,28 @@ pub struct TryHandler {
|
||||
///
|
||||
/// 仿 `pending_exception` 的侧通道:finally 执行期间悬挂在此,由 TRY_FINALLY_END
|
||||
/// 逐个恢复(`remaining_finally` 为仍需穿越的 finally 体数,进入一个递减一个)。
|
||||
/// `for_of_count`/`for_in_count` 为逃出时需关闭的迭代器层数:在全部 finally 穿越
|
||||
/// 之后、跳转/返回之前执行(规范 §13.7.5.4 的 IteratorClose 在完成值之后)。
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Completion {
|
||||
Break { target_pc: usize, remaining_finally: usize },
|
||||
Continue { target_pc: usize, remaining_finally: usize },
|
||||
Return { value: JsValue, remaining_finally: usize },
|
||||
Break {
|
||||
target_pc: usize,
|
||||
remaining_finally: usize,
|
||||
for_of_count: usize,
|
||||
for_in_count: usize,
|
||||
},
|
||||
Continue {
|
||||
target_pc: usize,
|
||||
remaining_finally: usize,
|
||||
for_of_count: usize,
|
||||
for_in_count: usize,
|
||||
},
|
||||
Return {
|
||||
value: JsValue,
|
||||
remaining_finally: usize,
|
||||
for_of_count: usize,
|
||||
for_in_count: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl Completion {
|
||||
@@ -268,20 +285,59 @@ impl Completion {
|
||||
}
|
||||
}
|
||||
|
||||
/// 逃出时需关闭的 for-of 迭代器层数。
|
||||
pub fn for_of_count(&self) -> usize {
|
||||
match *self {
|
||||
Completion::Break { for_of_count, .. }
|
||||
| Completion::Continue { for_of_count, .. }
|
||||
| Completion::Return { for_of_count, .. } => for_of_count,
|
||||
}
|
||||
}
|
||||
|
||||
/// 逃出时需弹出的 for-in 迭代器层数。
|
||||
pub fn for_in_count(&self) -> usize {
|
||||
match *self {
|
||||
Completion::Break { for_in_count, .. }
|
||||
| Completion::Continue { for_in_count, .. }
|
||||
| Completion::Return { for_in_count, .. } => for_in_count,
|
||||
}
|
||||
}
|
||||
|
||||
/// 复制并改写剩余 finally 计数(进入一个 finally 后递减)。
|
||||
pub fn with_remaining(&self, remaining: usize) -> Completion {
|
||||
match *self {
|
||||
Completion::Break { target_pc, .. } => Completion::Break {
|
||||
Completion::Break {
|
||||
target_pc,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
..
|
||||
} => Completion::Break {
|
||||
target_pc,
|
||||
remaining_finally: remaining,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
},
|
||||
Completion::Continue { target_pc, .. } => Completion::Continue {
|
||||
Completion::Continue {
|
||||
target_pc,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
..
|
||||
} => Completion::Continue {
|
||||
target_pc,
|
||||
remaining_finally: remaining,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
},
|
||||
Completion::Return { value, .. } => Completion::Return {
|
||||
Completion::Return {
|
||||
value,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
..
|
||||
} => Completion::Return {
|
||||
value,
|
||||
remaining_finally: remaining,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -779,9 +835,16 @@ impl Vm {
|
||||
self.pending_exception = self.pending_exception.map(&mut rewrite);
|
||||
self.last_uncaught_value = self.last_uncaught_value.map(&mut rewrite);
|
||||
self.pending_completion = self.pending_completion.map(|completion| match completion {
|
||||
Completion::Return { value, remaining_finally } => Completion::Return {
|
||||
Completion::Return {
|
||||
value,
|
||||
remaining_finally,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
} => Completion::Return {
|
||||
value: rewrite(value),
|
||||
remaining_finally,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
},
|
||||
other => other,
|
||||
});
|
||||
@@ -1569,11 +1632,11 @@ impl Vm {
|
||||
}
|
||||
|
||||
OpCode::BREAK => {
|
||||
self.dispatch_break(instr);
|
||||
self.dispatch_break(instr)?;
|
||||
}
|
||||
|
||||
OpCode::CONTINUE => {
|
||||
self.dispatch_continue(instr);
|
||||
self.dispatch_continue(instr)?;
|
||||
}
|
||||
|
||||
OpCode::JMP_IF_FALSE => {
|
||||
@@ -1696,7 +1759,7 @@ impl Vm {
|
||||
self.dispatch_define_accessor_attrs(rd, a, b, key_word, attrs)?;
|
||||
}
|
||||
|
||||
OpCode::RETURN => match self.dispatch_return(rd) {
|
||||
OpCode::RETURN => match self.dispatch_return(instr) {
|
||||
Ok(Some(result)) => return Ok(result),
|
||||
Ok(None) => {}
|
||||
Err(e) => return Err(e),
|
||||
@@ -2383,6 +2446,7 @@ mod tests {
|
||||
vm.iters.for_of_iters.push(ForOfEntry {
|
||||
iterator: JsValue::undefined(),
|
||||
last_result: JsValue::undefined(),
|
||||
is_async: false,
|
||||
});
|
||||
vm.saved_bytecode_stack
|
||||
.push(Arc::from(vec![opcode::encode(opcode::OpCode::HALT, 0, 0, 0)]));
|
||||
@@ -2442,6 +2506,7 @@ mod tests {
|
||||
vm.iters.for_of_iters.push(ForOfEntry {
|
||||
iterator: JsValue::undefined(),
|
||||
last_result: JsValue::undefined(),
|
||||
is_async: false,
|
||||
});
|
||||
|
||||
vm.run(&module).expect("FOR_OF_CLOSE should tolerate non-object sentinel");
|
||||
|
||||
@@ -286,33 +286,63 @@ impl Vm {
|
||||
|
||||
/// break 完成:`crossed`(rd 槽)为 emit 词法算出的逃出 finally 域数。
|
||||
/// 逐个穿越 finally 后跳转到目标;crossed 为 0 时直接跳转。
|
||||
pub(crate) fn dispatch_break(&mut self, instr: u32) {
|
||||
/// ext 字携带逃出的迭代器层数:无 finally 穿越时立即关闭后跳转,有 finally
|
||||
/// 时计数随 Completion 悬挂,由 TRY_FINALLY_END 在穿越完成后消费。
|
||||
pub(crate) fn dispatch_break(&mut self, instr: u32) -> Result<(), String> {
|
||||
let (for_of_count, for_in_count) = self.read_escape_counts();
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
let target_pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
let crossed = opcode::rd(instr) as usize;
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Break {
|
||||
target_pc,
|
||||
remaining_finally: crossed,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
}) {
|
||||
self.pc = finally_pc;
|
||||
} else {
|
||||
self.pc = target_pc;
|
||||
// 无 finally 穿越:关闭逃出迭代器后跳转;return() 抛错被接管时
|
||||
// unwind 已设 pc,跳过跳转。
|
||||
match self.close_escaped_iters(for_of_count, for_in_count) {
|
||||
Ok(true) => self.pc = target_pc,
|
||||
Ok(false) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// continue 完成:同 break,目标为循环继续位置。
|
||||
pub(crate) fn dispatch_continue(&mut self, instr: u32) {
|
||||
pub(crate) fn dispatch_continue(&mut self, instr: u32) -> Result<(), String> {
|
||||
let (for_of_count, for_in_count) = self.read_escape_counts();
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
let target_pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
let crossed = opcode::rd(instr) as usize;
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Continue {
|
||||
target_pc,
|
||||
remaining_finally: crossed,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
}) {
|
||||
self.pc = finally_pc;
|
||||
} else {
|
||||
self.pc = target_pc;
|
||||
// 无 finally 穿越:关闭逃出迭代器后跳转;return() 抛错被接管时
|
||||
// unwind 已设 pc,跳过跳转。
|
||||
match self.close_escaped_iters(for_of_count, for_in_count) {
|
||||
Ok(true) => self.pc = target_pc,
|
||||
Ok(false) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 读取 BREAK/CONTINUE/RETURN 的 ext 字逃出计数(低 16 位 for-of,高 16 位
|
||||
/// for-in)。调用时机:主循环已把 pc 推进到 ext 字位置;两条跳转/返回路径
|
||||
/// 都会覆盖 pc,此处无需再推进。
|
||||
fn read_escape_counts(&self) -> (usize, usize) {
|
||||
let packed = self.bytecode[self.pc];
|
||||
((packed & 0xFFFF) as usize, (packed >> 16) as usize)
|
||||
}
|
||||
|
||||
/// 记录一次控制流完成:穿越 `remaining_finally` 个 finally 体后执行完成本身。
|
||||
@@ -365,19 +395,20 @@ impl Vm {
|
||||
|
||||
/// return 完成:`crossed` = 当前帧深度内全部 finally handler 数(return 逃出整个
|
||||
/// 函数,途中被覆盖的 finally 体也在内),穿越后实际返回。
|
||||
pub(crate) fn dispatch_return(&mut self, rd: usize) -> Result<Option<JsValue>, String> {
|
||||
/// ext 字携带逃出的迭代器层数,由 TRY_FINALLY_END 在穿越完成后关闭。
|
||||
pub(crate) fn dispatch_return(&mut self, instr: u32) -> Result<Option<JsValue>, String> {
|
||||
let rd = opcode::rd(instr) as usize;
|
||||
let result = self.regs[rd];
|
||||
let (for_of_count, for_in_count) = self.read_escape_counts();
|
||||
crate::vm_debug!(
|
||||
"RETURN depth={} saved_pc={}",
|
||||
self.frames.len(),
|
||||
self.frames.last().map(|f| f.return_addr).unwrap_or(0)
|
||||
);
|
||||
// return 逃出整个函数:当前帧残留的纯 catch handler 一律弹出(return 不被
|
||||
// catch 捕获),finally handler 保留给下方完成穿越逐个执行。即使 emit 侧已
|
||||
// 从栈顶弹出连续 catch,这里仍扫描兜底——finally 之下的 catch 无法由
|
||||
// TRY_END 直接弹出。防 handler 泄漏到已返回函数,导致后续异常 unwind
|
||||
// 跳回死函数的 catch 形成死循环。
|
||||
self.pop_frame_catch_handlers();
|
||||
// 纯 catch handler 的清理延后到完成消费处:record_completion 在 finally
|
||||
// 穿越路径弹出逃出的 catch-only handler;无 finally 时 do_return 弹帧前
|
||||
// 兜底清理。若在此提前弹出,return() 抛错经 unwind 展开时将找不到
|
||||
// 外围 catch(规范要求新错误替代完成值继续被捕获)。
|
||||
let crossed = self
|
||||
.try_stack
|
||||
.iter()
|
||||
@@ -386,11 +417,19 @@ impl Vm {
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Return {
|
||||
value: result,
|
||||
remaining_finally: crossed,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
}) {
|
||||
self.pc = finally_pc;
|
||||
return Ok(None);
|
||||
}
|
||||
self.do_return(result)
|
||||
// 无 finally 穿越:关闭逃出迭代器后返回;return() 抛错被接管时原完成值
|
||||
// 被新错误替代,跳过实际返回。
|
||||
match self.close_escaped_iters(for_of_count, for_in_count) {
|
||||
Ok(true) => self.do_return(result),
|
||||
Ok(false) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// 弹出当前帧内所有残留的纯 catch handler(无 finally 域),供 return 逃出
|
||||
@@ -562,14 +601,79 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
match c {
|
||||
Completion::Break { target_pc, .. } | Completion::Continue { target_pc, .. } => {
|
||||
self.pc = target_pc;
|
||||
Completion::Break {
|
||||
target_pc,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
..
|
||||
}
|
||||
| Completion::Continue {
|
||||
target_pc,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
..
|
||||
} => {
|
||||
// 全部 finally 穿越完成、真正跳转前关闭逃出迭代器——与规范顺序
|
||||
// (循环体完成值已含内部 try-finally 处理,IteratorClose 在后)一致。
|
||||
// return() 抛错被接管时 unwind 已设 pc,跳过跳转。
|
||||
match self.close_escaped_iters(for_of_count, for_in_count) {
|
||||
Ok(true) => self.pc = target_pc,
|
||||
Ok(false) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
Completion::Return { value, .. } => self.do_return(value),
|
||||
Completion::Return {
|
||||
value,
|
||||
for_of_count,
|
||||
for_in_count,
|
||||
..
|
||||
} => match self.close_escaped_iters(for_of_count, for_in_count) {
|
||||
Ok(true) => self.do_return(value),
|
||||
Ok(false) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// 逃出关闭:按 LIFO 弹出逃出的 for-in 迭代器(无 return() 语义),再关闭逃出的
|
||||
/// for-of 迭代器。异步迭代器条目(for-await-of)跳过——其 return() 返回 promise
|
||||
/// 须 await 后结算,走独立异步关闭机制。逃出路径非 suppress:return() 抛错经
|
||||
/// raise_call_error 展开,剩余迭代器由 unwind 的 close_for_of_above 以 suppress
|
||||
/// 继续关闭——新错误替代原完成值向外传播。
|
||||
///
|
||||
/// # 返回值
|
||||
/// `Ok(true)` = 全部关闭完成,调用方继续跳转/返回动作;`Ok(false)` = return()
|
||||
/// 抛错且异常已被外围 catch/finally 接管(unwind 已改写 pc),调用方须丢弃
|
||||
/// 原完成动作;`Err` = 无处理器,异常逃逸。
|
||||
pub(crate) fn close_escaped_iters(&mut self, for_of_count: usize, for_in_count: usize) -> Result<bool, String> {
|
||||
for _ in 0..for_in_count {
|
||||
self.iters.pop_for_in();
|
||||
}
|
||||
for _ in 0..for_of_count {
|
||||
let Some(entry) = self.iters.pop_for_of() else {
|
||||
break;
|
||||
};
|
||||
// 异步迭代器不在此关闭:for-await-of 的逃出(labeled/return)由
|
||||
// 异步关闭机制处理,此处同步关闭会跳过 return() promise 的等待。
|
||||
if entry.is_async {
|
||||
continue;
|
||||
}
|
||||
let pc_before = self.pc;
|
||||
match self.close_for_of_iterator(entry.iterator, false) {
|
||||
Ok(()) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
// return() 抛错被接管:unwind 把 pc 改写为 catch/finally 入口,或挂起
|
||||
// 在途异常等 finally 穿越。正常关闭时 pc 不变(call_function_sync 保存
|
||||
// 并恢复调用方 pc)。
|
||||
if self.pc != pc_before || self.pending_exception.is_some() {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// 找下一个未被覆盖的包裹 finally 并进入(resume 路径用):弹出途中被逃出的
|
||||
/// catch-only 与防御性的 active handler。无更多返回 None。
|
||||
fn find_next_finally(&mut self) -> Option<usize> {
|
||||
|
||||
@@ -537,7 +537,7 @@ impl Vm {
|
||||
let iterable = self.regs[a];
|
||||
match oxide_builtins::iterator::make_iterator_for_value(self, iterable) {
|
||||
Ok(iterator) => {
|
||||
self.iters.push_for_of(iterator);
|
||||
self.iters.push_for_of(iterator, false);
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -555,7 +555,7 @@ impl Vm {
|
||||
let iterable = self.regs[a];
|
||||
match crate::async_from_sync::make_async_iterator(self, iterable) {
|
||||
Ok(iterator) => {
|
||||
self.iters.push_for_of(iterator);
|
||||
self.iters.push_for_of(iterator, true);
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
|
||||
@@ -34,31 +34,81 @@ fn place_flat(module: &Arc<CompiledModule>, out: &mut Vec<Option<Arc<CompiledMod
|
||||
/// `regs` 取窗口副本:值来自 `save_inline_state` 预填充的 `window_regs` 局部缓冲
|
||||
/// (经宏参数注入,绕开 macro hygiene),从池中取出后即 move 进 `InlineSyncState`。
|
||||
macro_rules! inline_save_field {
|
||||
($recv:ident, $window_regs:ident, regs, boxed_window) => { std::mem::take(&mut $window_regs).into_boxed_slice() };
|
||||
($recv:ident, $window_regs:ident, saved_this, copy) => { $recv.regs[254] };
|
||||
($recv:ident, $window_regs:ident, saved_new_target, copy) => { $recv.regs[255] };
|
||||
($recv:ident, $window_regs:ident, pc, copy) => { $recv.pc };
|
||||
($recv:ident, $window_regs:ident, bytecode, move_field) => { std::mem::take(&mut $recv.bytecode) };
|
||||
($recv:ident, $window_regs:ident, active_immutables, copy) => { $recv.active_immutables };
|
||||
($recv:ident, $window_regs:ident, active_reg_limit, copy) => { $recv.active_reg_limit };
|
||||
($recv:ident, $window_regs:ident, root_reg_limit, copy) => { $recv.root_reg_limit };
|
||||
($recv:ident, $window_regs:ident, try_stack, move_field) => { std::mem::take(&mut $recv.try_stack) };
|
||||
($recv:ident, $window_regs:ident, frames, frames_values) => { std::mem::take(&mut $recv.frames) };
|
||||
($recv:ident, $window_regs:ident, exception_value, opt_take) => { $recv.exception_value.take() };
|
||||
($recv:ident, $window_regs:ident, pending_exception, opt_take) => { $recv.pending_exception.take() };
|
||||
($recv:ident, $window_regs:ident, pending_error_kind, opt_take) => { $recv.pending_error_kind.take() };
|
||||
($recv:ident, $window_regs:ident, pending_completion, opt_copy) => { $recv.pending_completion };
|
||||
($recv:ident, $window_regs:ident, for_in_iters, for_in_keys) => { std::mem::take(&mut $recv.iters.for_in_iters) };
|
||||
($recv:ident, $window_regs:ident, for_of_iters, iter_take) => { std::mem::take(&mut $recv.iters.for_of_iters) };
|
||||
($recv:ident, $window_regs:ident, saved_bytecode_stack, move_field) => { std::mem::take(&mut $recv.saved_bytecode_stack) };
|
||||
($recv:ident, $window_regs:ident, saved_immutables_stack, move_field) => { std::mem::take(&mut $recv.saved_immutables_stack) };
|
||||
($recv:ident, $window_regs:ident, save_stack, move_field) => { std::mem::take(&mut $recv.save_stack) };
|
||||
($recv:ident, $window_regs:ident, spill_stack, move_field) => { std::mem::take(&mut $recv.spill_stack) };
|
||||
($recv:ident, $window_regs:ident, cell_stack, move_field) => { std::mem::take(&mut $recv.cell_stack) };
|
||||
($recv:ident, $window_regs:ident, inline_callee, opt_copy) => { $recv.inline_callee };
|
||||
($recv:ident, $window_regs:ident, inline_args_base, copy) => { $recv.inline_args_base };
|
||||
($recv:ident, $window_regs:ident, inline_args_count, copy) => { $recv.inline_args_count };
|
||||
($recv:ident, $window_regs:ident, accessor_frame_target_reg, copy) => { $recv.accessor_frame_target_reg };
|
||||
($recv:ident, $window_regs:ident, regs, boxed_window) => {
|
||||
std::mem::take(&mut $window_regs).into_boxed_slice()
|
||||
};
|
||||
($recv:ident, $window_regs:ident, saved_this, copy) => {
|
||||
$recv.regs[254]
|
||||
};
|
||||
($recv:ident, $window_regs:ident, saved_new_target, copy) => {
|
||||
$recv.regs[255]
|
||||
};
|
||||
($recv:ident, $window_regs:ident, pc, copy) => {
|
||||
$recv.pc
|
||||
};
|
||||
($recv:ident, $window_regs:ident, bytecode, move_field) => {
|
||||
std::mem::take(&mut $recv.bytecode)
|
||||
};
|
||||
($recv:ident, $window_regs:ident, active_immutables, copy) => {
|
||||
$recv.active_immutables
|
||||
};
|
||||
($recv:ident, $window_regs:ident, active_reg_limit, copy) => {
|
||||
$recv.active_reg_limit
|
||||
};
|
||||
($recv:ident, $window_regs:ident, root_reg_limit, copy) => {
|
||||
$recv.root_reg_limit
|
||||
};
|
||||
($recv:ident, $window_regs:ident, try_stack, move_field) => {
|
||||
std::mem::take(&mut $recv.try_stack)
|
||||
};
|
||||
($recv:ident, $window_regs:ident, frames, frames_values) => {
|
||||
std::mem::take(&mut $recv.frames)
|
||||
};
|
||||
($recv:ident, $window_regs:ident, exception_value, opt_take) => {
|
||||
$recv.exception_value.take()
|
||||
};
|
||||
($recv:ident, $window_regs:ident, pending_exception, opt_take) => {
|
||||
$recv.pending_exception.take()
|
||||
};
|
||||
($recv:ident, $window_regs:ident, pending_error_kind, opt_take) => {
|
||||
$recv.pending_error_kind.take()
|
||||
};
|
||||
($recv:ident, $window_regs:ident, pending_completion, opt_copy) => {
|
||||
$recv.pending_completion
|
||||
};
|
||||
($recv:ident, $window_regs:ident, for_in_iters, for_in_keys) => {
|
||||
std::mem::take(&mut $recv.iters.for_in_iters)
|
||||
};
|
||||
($recv:ident, $window_regs:ident, for_of_iters, iter_take) => {
|
||||
std::mem::take(&mut $recv.iters.for_of_iters)
|
||||
};
|
||||
($recv:ident, $window_regs:ident, saved_bytecode_stack, move_field) => {
|
||||
std::mem::take(&mut $recv.saved_bytecode_stack)
|
||||
};
|
||||
($recv:ident, $window_regs:ident, saved_immutables_stack, move_field) => {
|
||||
std::mem::take(&mut $recv.saved_immutables_stack)
|
||||
};
|
||||
($recv:ident, $window_regs:ident, save_stack, move_field) => {
|
||||
std::mem::take(&mut $recv.save_stack)
|
||||
};
|
||||
($recv:ident, $window_regs:ident, spill_stack, move_field) => {
|
||||
std::mem::take(&mut $recv.spill_stack)
|
||||
};
|
||||
($recv:ident, $window_regs:ident, cell_stack, move_field) => {
|
||||
std::mem::take(&mut $recv.cell_stack)
|
||||
};
|
||||
($recv:ident, $window_regs:ident, inline_callee, opt_copy) => {
|
||||
$recv.inline_callee
|
||||
};
|
||||
($recv:ident, $window_regs:ident, inline_args_base, copy) => {
|
||||
$recv.inline_args_base
|
||||
};
|
||||
($recv:ident, $window_regs:ident, inline_args_count, copy) => {
|
||||
$recv.inline_args_count
|
||||
};
|
||||
($recv:ident, $window_regs:ident, accessor_frame_target_reg, copy) => {
|
||||
$recv.accessor_frame_target_reg
|
||||
};
|
||||
}
|
||||
|
||||
/// restore 方向的字段写回语句。`regs` 只回拷窗口并把缓冲归还池。
|
||||
@@ -67,30 +117,78 @@ macro_rules! inline_restore_field {
|
||||
$recv.regs[..$saved.regs.len()].copy_from_slice(&$saved.regs);
|
||||
$recv.inline_reg_pool = Some($saved.regs.into_vec());
|
||||
};
|
||||
($recv:ident, $saved:ident, saved_this, copy) => { $recv.regs[254] = $saved.saved_this };
|
||||
($recv:ident, $saved:ident, saved_new_target, copy) => { $recv.regs[255] = $saved.saved_new_target };
|
||||
($recv:ident, $saved:ident, pc, copy) => { $recv.pc = $saved.pc };
|
||||
($recv:ident, $saved:ident, bytecode, move_field) => { $recv.bytecode = $saved.bytecode };
|
||||
($recv:ident, $saved:ident, active_immutables, copy) => { $recv.active_immutables = $saved.active_immutables };
|
||||
($recv:ident, $saved:ident, active_reg_limit, copy) => { $recv.active_reg_limit = $saved.active_reg_limit };
|
||||
($recv:ident, $saved:ident, root_reg_limit, copy) => { $recv.root_reg_limit = $saved.root_reg_limit };
|
||||
($recv:ident, $saved:ident, try_stack, move_field) => { $recv.try_stack = $saved.try_stack };
|
||||
($recv:ident, $saved:ident, frames, frames_values) => { $recv.frames = $saved.frames };
|
||||
($recv:ident, $saved:ident, exception_value, opt_take) => { $recv.exception_value = $saved.exception_value };
|
||||
($recv:ident, $saved:ident, pending_exception, opt_take) => { $recv.pending_exception = $saved.pending_exception };
|
||||
($recv:ident, $saved:ident, pending_error_kind, opt_take) => { $recv.pending_error_kind = $saved.pending_error_kind };
|
||||
($recv:ident, $saved:ident, pending_completion, opt_copy) => { $recv.pending_completion = $saved.pending_completion };
|
||||
($recv:ident, $saved:ident, for_in_iters, for_in_keys) => { $recv.iters.for_in_iters = $saved.for_in_iters };
|
||||
($recv:ident, $saved:ident, for_of_iters, iter_take) => { $recv.iters.for_of_iters = $saved.for_of_iters };
|
||||
($recv:ident, $saved:ident, saved_bytecode_stack, move_field) => { $recv.saved_bytecode_stack = $saved.saved_bytecode_stack };
|
||||
($recv:ident, $saved:ident, saved_immutables_stack, move_field) => { $recv.saved_immutables_stack = $saved.saved_immutables_stack };
|
||||
($recv:ident, $saved:ident, save_stack, move_field) => { $recv.save_stack = $saved.save_stack };
|
||||
($recv:ident, $saved:ident, spill_stack, move_field) => { $recv.spill_stack = $saved.spill_stack };
|
||||
($recv:ident, $saved:ident, cell_stack, move_field) => { $recv.cell_stack = $saved.cell_stack };
|
||||
($recv:ident, $saved:ident, inline_callee, opt_copy) => { $recv.inline_callee = $saved.inline_callee };
|
||||
($recv:ident, $saved:ident, inline_args_base, copy) => { $recv.inline_args_base = $saved.inline_args_base };
|
||||
($recv:ident, $saved:ident, inline_args_count, copy) => { $recv.inline_args_count = $saved.inline_args_count };
|
||||
($recv:ident, $saved:ident, accessor_frame_target_reg, copy) => { $recv.accessor_frame_target_reg = $saved.accessor_frame_target_reg };
|
||||
($recv:ident, $saved:ident, saved_this, copy) => {
|
||||
$recv.regs[254] = $saved.saved_this
|
||||
};
|
||||
($recv:ident, $saved:ident, saved_new_target, copy) => {
|
||||
$recv.regs[255] = $saved.saved_new_target
|
||||
};
|
||||
($recv:ident, $saved:ident, pc, copy) => {
|
||||
$recv.pc = $saved.pc
|
||||
};
|
||||
($recv:ident, $saved:ident, bytecode, move_field) => {
|
||||
$recv.bytecode = $saved.bytecode
|
||||
};
|
||||
($recv:ident, $saved:ident, active_immutables, copy) => {
|
||||
$recv.active_immutables = $saved.active_immutables
|
||||
};
|
||||
($recv:ident, $saved:ident, active_reg_limit, copy) => {
|
||||
$recv.active_reg_limit = $saved.active_reg_limit
|
||||
};
|
||||
($recv:ident, $saved:ident, root_reg_limit, copy) => {
|
||||
$recv.root_reg_limit = $saved.root_reg_limit
|
||||
};
|
||||
($recv:ident, $saved:ident, try_stack, move_field) => {
|
||||
$recv.try_stack = $saved.try_stack
|
||||
};
|
||||
($recv:ident, $saved:ident, frames, frames_values) => {
|
||||
$recv.frames = $saved.frames
|
||||
};
|
||||
($recv:ident, $saved:ident, exception_value, opt_take) => {
|
||||
$recv.exception_value = $saved.exception_value
|
||||
};
|
||||
($recv:ident, $saved:ident, pending_exception, opt_take) => {
|
||||
$recv.pending_exception = $saved.pending_exception
|
||||
};
|
||||
($recv:ident, $saved:ident, pending_error_kind, opt_take) => {
|
||||
$recv.pending_error_kind = $saved.pending_error_kind
|
||||
};
|
||||
($recv:ident, $saved:ident, pending_completion, opt_copy) => {
|
||||
$recv.pending_completion = $saved.pending_completion
|
||||
};
|
||||
($recv:ident, $saved:ident, for_in_iters, for_in_keys) => {
|
||||
$recv.iters.for_in_iters = $saved.for_in_iters
|
||||
};
|
||||
($recv:ident, $saved:ident, for_of_iters, iter_take) => {
|
||||
$recv.iters.for_of_iters = $saved.for_of_iters
|
||||
};
|
||||
($recv:ident, $saved:ident, saved_bytecode_stack, move_field) => {
|
||||
$recv.saved_bytecode_stack = $saved.saved_bytecode_stack
|
||||
};
|
||||
($recv:ident, $saved:ident, saved_immutables_stack, move_field) => {
|
||||
$recv.saved_immutables_stack = $saved.saved_immutables_stack
|
||||
};
|
||||
($recv:ident, $saved:ident, save_stack, move_field) => {
|
||||
$recv.save_stack = $saved.save_stack
|
||||
};
|
||||
($recv:ident, $saved:ident, spill_stack, move_field) => {
|
||||
$recv.spill_stack = $saved.spill_stack
|
||||
};
|
||||
($recv:ident, $saved:ident, cell_stack, move_field) => {
|
||||
$recv.cell_stack = $saved.cell_stack
|
||||
};
|
||||
($recv:ident, $saved:ident, inline_callee, opt_copy) => {
|
||||
$recv.inline_callee = $saved.inline_callee
|
||||
};
|
||||
($recv:ident, $saved:ident, inline_args_base, copy) => {
|
||||
$recv.inline_args_base = $saved.inline_args_base
|
||||
};
|
||||
($recv:ident, $saved:ident, inline_args_count, copy) => {
|
||||
$recv.inline_args_count = $saved.inline_args_count
|
||||
};
|
||||
($recv:ident, $saved:ident, accessor_frame_target_reg, copy) => {
|
||||
$recv.accessor_frame_target_reg = $saved.accessor_frame_target_reg
|
||||
};
|
||||
}
|
||||
|
||||
/// 内联同步调用可搬移执行核心字段的单一登记表。save/restore 双向由本宏展开;
|
||||
@@ -98,32 +196,35 @@ macro_rules! inline_restore_field {
|
||||
/// `$window_regs` 只在 save 方向被 `regs` 字段消费。
|
||||
macro_rules! inline_core_fields {
|
||||
($recv:ident, $saved:ident, $ops:ident, $window_regs:ident) => {
|
||||
$ops!($recv, $saved, $window_regs,
|
||||
(regs, boxed_window), // V M
|
||||
(saved_this, copy), // V
|
||||
(saved_new_target, copy), // V
|
||||
(pc, copy), // M
|
||||
(bytecode, move_field), // V M
|
||||
(active_immutables, copy), // M
|
||||
(active_reg_limit, copy), // M
|
||||
(root_reg_limit, copy), // M
|
||||
(try_stack, move_field), // M
|
||||
(frames, frames_values), // V M
|
||||
(exception_value, opt_take), // V M
|
||||
(pending_exception, opt_take), // V M
|
||||
(pending_error_kind, opt_take), // M
|
||||
(pending_completion, opt_copy), // V M
|
||||
(for_in_iters, for_in_keys), // V M
|
||||
(for_of_iters, iter_take), // V M
|
||||
(saved_bytecode_stack, move_field), // M
|
||||
$ops!(
|
||||
$recv,
|
||||
$saved,
|
||||
$window_regs,
|
||||
(regs, boxed_window), // V M
|
||||
(saved_this, copy), // V
|
||||
(saved_new_target, copy), // V
|
||||
(pc, copy), // M
|
||||
(bytecode, move_field), // V M
|
||||
(active_immutables, copy), // M
|
||||
(active_reg_limit, copy), // M
|
||||
(root_reg_limit, copy), // M
|
||||
(try_stack, move_field), // M
|
||||
(frames, frames_values), // V M
|
||||
(exception_value, opt_take), // V M
|
||||
(pending_exception, opt_take), // V M
|
||||
(pending_error_kind, opt_take), // M
|
||||
(pending_completion, opt_copy), // V M
|
||||
(for_in_iters, for_in_keys), // V M
|
||||
(for_of_iters, iter_take), // V M
|
||||
(saved_bytecode_stack, move_field), // M
|
||||
(saved_immutables_stack, move_field), // M
|
||||
(save_stack, move_field), // V M
|
||||
(spill_stack, move_field), // V M
|
||||
(cell_stack, move_field), // V M
|
||||
(inline_callee, opt_copy), // V M
|
||||
(inline_args_base, copy), // M
|
||||
(inline_args_count, copy), // M
|
||||
(accessor_frame_target_reg, copy), // M
|
||||
(save_stack, move_field), // V M
|
||||
(spill_stack, move_field), // V M
|
||||
(cell_stack, move_field), // V M
|
||||
(inline_callee, opt_copy), // V M
|
||||
(inline_args_base, copy), // M
|
||||
(inline_args_count, copy), // M
|
||||
(accessor_frame_target_reg, copy), // M
|
||||
)
|
||||
};
|
||||
}
|
||||
@@ -475,10 +576,13 @@ mod tests {
|
||||
vm.pending_completion = Some(Completion::Return {
|
||||
value: JsValue::float(8.0),
|
||||
remaining_finally: 1,
|
||||
for_of_count: 0,
|
||||
for_in_count: 0,
|
||||
});
|
||||
vm.iters.for_of_iters.push(crate::vm_state::ForOfEntry {
|
||||
iterator: JsValue::float(8.5),
|
||||
last_result: JsValue::float(9.0),
|
||||
is_async: false,
|
||||
});
|
||||
vm.spill_stack.push(JsValue::float(10.0));
|
||||
vm.save_stack.push(JsValue::float(11.0));
|
||||
@@ -503,7 +607,7 @@ mod tests {
|
||||
assert_eq!(vm.pending_error_kind, Some("TypeError"));
|
||||
assert!(matches!(
|
||||
vm.pending_completion,
|
||||
Some(Completion::Return { value, remaining_finally: 1 }) if value == JsValue::float(8.0)
|
||||
Some(Completion::Return { value, remaining_finally: 1, .. }) if value == JsValue::float(8.0)
|
||||
));
|
||||
assert_eq!(vm.iters.for_of_iters.len(), 1);
|
||||
assert_eq!(vm.iters.for_of_iters[0].iterator, JsValue::float(8.5));
|
||||
|
||||
@@ -127,6 +127,10 @@ pub(crate) struct ForOfEntry {
|
||||
pub(crate) iterator: JsValue,
|
||||
/// 本迭代器最近一次 DONE 返回的结果对象(CLOSE 判 done 用)。
|
||||
pub(crate) last_result: JsValue,
|
||||
/// 是否为 for-await-of 的异步迭代器:异步逃出关闭须 await return() 的
|
||||
/// promise(独立异步机制),同步逃出路径(break/continue/return 计数关闭)
|
||||
/// 不得同步调用其 return()。
|
||||
pub(crate) is_async: bool,
|
||||
}
|
||||
|
||||
/// for-in / for-of 的活跃迭代器状态。
|
||||
@@ -154,10 +158,12 @@ impl IterState {
|
||||
}
|
||||
|
||||
/// 压入新迭代器条目:`last_result` 初始为 undefined(尚未执行任何 next())。
|
||||
pub(crate) fn push_for_of(&mut self, iterator: JsValue) {
|
||||
/// `is_async` 标记 for-await-of 的异步迭代器(异步逃出关闭走独立机制)。
|
||||
pub(crate) fn push_for_of(&mut self, iterator: JsValue, is_async: bool) {
|
||||
self.for_of_iters.push(ForOfEntry {
|
||||
iterator,
|
||||
last_result: JsValue::undefined(),
|
||||
is_async,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,10 @@ fn async_throw_rejects_promise() {
|
||||
|
||||
#[test]
|
||||
fn async_is_not_constructor() {
|
||||
assert!(eval("new (async function(){})").starts_with("vm error") || eval("new (async function(){})").contains("not a constructor"));
|
||||
assert!(
|
||||
eval("new (async function(){})").starts_with("vm error")
|
||||
|| eval("new (async function(){})").contains("not a constructor")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -201,6 +204,35 @@ fn for_await_of_nested_sync_for_of_break_close_respective() {
|
||||
(async()=>{ for await (const x of asyncIter) { for (const y of sync) { break; } break; } })();\
|
||||
Promise.resolve().then(()=>log.join(','))"
|
||||
),
|
||||
"\"s,a\""
|
||||
"\"s,a\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_await_of_body_throw_closes_and_original_error_wins() {
|
||||
// for-await-of 循环体抛错:先调 return() 关闭迭代器,原错误优先传播给 catch。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"let log=[];\
|
||||
const it={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){log.push('r');return Promise.resolve({done:true})}}}};\
|
||||
(async()=>{ try { for await (const x of it) { throw 'orig'; } } catch(e) { log.push('c:'+e); } return log.join(','); })()"
|
||||
),
|
||||
"\"r,c:orig\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_await_of_return_escape_defers_async_close() {
|
||||
// return 逃出 for-await-of:异步迭代器 return() 的 promise 须 await 后结算,
|
||||
// 走异步关闭机制(后续阶段);阶段 1 同步逃出路径不得同步调用异步 return()。
|
||||
// 断言循环体已执行且没有同步副作用泄露。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"let log=[];\
|
||||
const it={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){log.push('close');return Promise.resolve({done:true})}}}};\
|
||||
(async()=>{ for await (const x of it) { log.push('body'); return 1; } })();\
|
||||
Promise.resolve().then(()=>log.join(','))"
|
||||
),
|
||||
"\"body\""
|
||||
);
|
||||
}
|
||||
|
||||
@@ -153,3 +153,158 @@ fn for_of_nested_iterators_break_close_inner_only() {
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_of_return_escape_closes_iterator() {
|
||||
// return 逃出 for-of:跳转前执行 IteratorClose(当前实现此前完全漏调)。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var closed=0;\
|
||||
var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){closed++;return{}}}}};\
|
||||
function f(){ for (const x of it) { return 1; } } f();closed===1"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_of_labeled_break_escapes_all_in_lifo_order() {
|
||||
// labeled break 逃出两层:按嵌套逆序(内层先)逐层关闭。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var log=[];\
|
||||
var mk=(n)=>({[Symbol.iterator](){return{next(){return{value:n,done:false}},return(){log.push(n);return{}}}}});\
|
||||
var it1=mk(1), it2=mk(2);\
|
||||
outer: for (const a of it1) { for (const b of it2) { break outer; } }\
|
||||
log.join(',')==='2,1'"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_of_labeled_continue_closes_inner_only() {
|
||||
// labeled continue 逃出:内层每轮关闭,外层循环继续迭代不关闭。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var log=[];\
|
||||
var mk=(n)=>({[Symbol.iterator](){return{next(){return{value:n,done:false}},return(){log.push(n);return{}}}}});\
|
||||
var it2=mk(2);\
|
||||
outer: for (const a of [1,2]) { for (const b of it2) { continue outer; } }\
|
||||
log.join(',')==='2,2'"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_of_escape_finally_runs_before_close() {
|
||||
// 规范顺序:循环体完成值(含内部 try-finally 处理)之后才执行 IteratorClose。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var log=[];\
|
||||
var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){log.push('close');return{}}}}};\
|
||||
function f(){ try { for (const x of it) { log.push('ret'); return 1; } } finally { log.push('finally'); } }\
|
||||
f();log.join(',')==='ret,finally,close'"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_of_escape_return_error_supersedes_and_closes_rest() {
|
||||
// 逃出时 return() 抛错:新错误替代完成值被外围 catch 捕获,剩余迭代器
|
||||
// (外层)仍被关闭(unwind 兜底,错误优先)。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var log=[];\
|
||||
var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){log.push('c1');return{}}}}};\
|
||||
var it2={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){log.push('c2');throw new Error('B');return{}}}}};\
|
||||
function f(){ try { for (var a of it) { for (var b of it2) { return 1; } } } catch(e) { log.push(e.message); } }\
|
||||
f();log.join(',')==='c2,c1,B'"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_of_generator_return_escape_closes_iterators() {
|
||||
// 生成器 .return() 注入时关闭挂起点打开的全部迭代器。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var closed=0;\
|
||||
var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){closed++;return{}}}}};\
|
||||
function* g(){ for (const x of it) { yield 1; return 2; } }\
|
||||
var gen=g();gen.next();gen.return(9);closed===1"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_labeled_break_pops_iterator_stack() {
|
||||
// labeled break 逃出 for-in:弹出内层迭代器,后续 for-in 从干净栈开始迭代
|
||||
// (此前逃出会使 for_in_iters 残留,后续 FOR_IN_DONE 读错 keys)。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var seen=[];\
|
||||
outer: for (var k in {a:1,b:2}) { for (var j in {c:3}) { break outer; } }\
|
||||
for (var m in {d:4}) { seen.push(m); }seen.join(',')==='d'"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_of_while_break_keeps_outer_iterator() {
|
||||
// while 内 break 不逃出外层 for-of:外层迭代器不被关闭(计数按词法深度对齐)。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var closed=0;\
|
||||
var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){closed++;return{}}}}};\
|
||||
for (var a of it) { while (true) { break; } break; }closed===1"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_of_switch_break_keeps_outer_iterator() {
|
||||
// switch 内 break 不逃出外层 for-of:只关当前循环自身。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var closed=0;\
|
||||
var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){closed++;return{}}}}};\
|
||||
for (var a of it) { switch (a) { case 1: break; } break; }closed===1"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_of_switch_break_continues_body_then_closes_once() {
|
||||
// switch 内 break 只跳出 switch:循环体后续语句继续执行,迭代器保持打开,
|
||||
// 直到循环收尾 CLOSE 才关闭一次(此前误在 switch 出口处提前关闭)。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var log=[];\
|
||||
var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){log.push('close');return{}}}}};\
|
||||
for (var a of it) { switch (a) { case 1: break; } log.push('body'); break; }\
|
||||
log.join(',')==='body,close'"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_of_labeled_block_break_closes_all() {
|
||||
// label 包裹非循环语句:break 逃出整个 label 域,全部 for-of 关闭。
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var closed=0;\
|
||||
var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){closed++;return{}}}}};\
|
||||
outer: { for (var a of it) { break outer; } }closed===1"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user