diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index 9091b66..7722148 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -839,8 +839,10 @@ mod tests { vm.regs[1] = JsValue::from_js_object(child_session); vm.exception_value = Some(JsValue::from_js_object(root_session)); vm.pending_exception = Some(JsValue::from_js_object(child_session)); - vm.iters.for_of_iters.push(JsValue::from_js_object(child_session)); - vm.iters.last_for_of_result = JsValue::from_js_object(root_session); + 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), + }); let mut roots = Vec::new(); vm.for_each_root(|v| roots.push(v)); diff --git a/crates/oxide_vm/src/suspended.rs b/crates/oxide_vm/src/suspended.rs index 10c3016..cbbc02f 100644 --- a/crates/oxide_vm/src/suspended.rs +++ b/crates/oxide_vm/src/suspended.rs @@ -13,6 +13,7 @@ use oxide_types::object::Cell; use oxide_types::value::JsValue; use crate::vm::{CallFrame, Completion, ForInIter, TryHandler, Vm}; +use crate::vm_state::ForOfEntry; /// 挂起执行上下文快照(三份状态结构体共享的执行核心)。 pub(crate) struct SuspendedFrame { @@ -30,8 +31,7 @@ pub(crate) struct SuspendedFrame { pub cell_stack: Vec>, pub try_stack: Vec, pub for_in_iters: Vec<*mut ForInIter<'static>>, - pub for_of_iters: Vec, - pub last_for_of_result: JsValue, + pub for_of_iters: Vec, /// `yield*` 委托中的内层迭代器;异步函数恒为 None。 pub delegated_iterator: Option, pub saved_bytecode_stack: Vec>, @@ -58,7 +58,6 @@ impl SuspendedFrame { try_stack: Vec::new(), for_in_iters: Vec::new(), for_of_iters: Vec::new(), - last_for_of_result: JsValue::undefined(), delegated_iterator: None, saved_bytecode_stack: Vec::new(), saved_immutables_stack: Vec::new(), @@ -96,7 +95,6 @@ impl SuspendedFrame { self.try_stack = std::mem::take(&mut vm.try_stack); self.for_in_iters = std::mem::take(&mut vm.iters.for_in_iters); self.for_of_iters = std::mem::take(&mut vm.iters.for_of_iters); - self.last_for_of_result = vm.iters.last_for_of_result; self.delegated_iterator = std::mem::take(&mut vm.delegated_iterator); self.saved_bytecode_stack = std::mem::take(&mut vm.saved_bytecode_stack); self.saved_immutables_stack = std::mem::take(&mut vm.saved_immutables_stack); @@ -134,7 +132,6 @@ impl SuspendedFrame { vm.try_stack = std::mem::take(&mut self.try_stack); vm.iters.for_in_iters = std::mem::take(&mut self.for_in_iters); vm.iters.for_of_iters = std::mem::take(&mut self.for_of_iters); - vm.iters.last_for_of_result = self.last_for_of_result; vm.delegated_iterator = std::mem::take(&mut self.delegated_iterator); vm.saved_bytecode_stack = std::mem::take(&mut self.saved_bytecode_stack); vm.saved_immutables_stack = std::mem::take(&mut self.saved_immutables_stack); @@ -174,10 +171,10 @@ impl SuspendedFrame { f(unsafe { &*p }.value); } } - for v in &self.for_of_iters { - f(*v); + for entry in &self.for_of_iters { + f(entry.iterator); + f(entry.last_result); } - f(self.last_for_of_result); if let Some(it) = self.delegated_iterator { f(it); } @@ -228,10 +225,10 @@ impl SuspendedFrame { cell.value = rewrite(cell.value); } } - for v in &mut self.for_of_iters { - *v = rewrite(*v); + for entry in &mut self.for_of_iters { + entry.iterator = rewrite(entry.iterator); + entry.last_result = rewrite(entry.last_result); } - self.last_for_of_result = rewrite(self.last_for_of_result); self.delegated_iterator = self.delegated_iterator.map(&mut rewrite); self.exception_value = self.exception_value.map(&mut rewrite); self.pending_exception = self.pending_exception.map(&mut rewrite); @@ -291,8 +288,15 @@ impl SuspendedFrame { cell_stack: self.cell_stack.clone(), try_stack: self.try_stack.clone(), for_in_iters: self.for_in_iters.clone(), - for_of_iters: self.for_of_iters.iter().copied().map(&mut rewrite).collect(), - last_for_of_result: rewrite(self.last_for_of_result), + for_of_iters: self + .for_of_iters + .iter() + .copied() + .map(|entry| ForOfEntry { + iterator: rewrite(entry.iterator), + last_result: rewrite(entry.last_result), + }) + .collect(), delegated_iterator: self.delegated_iterator.map(&mut rewrite), saved_bytecode_stack: self.saved_bytecode_stack.clone(), saved_immutables_stack: self.saved_immutables_stack.clone(), @@ -314,7 +318,7 @@ impl SuspendedFrame { self.bytecode.len() as u64 * std::mem::size_of::() as u64 + self.spill_stack.capacity() as u64 * std::mem::size_of::() as u64 + self.save_stack.capacity() as u64 * std::mem::size_of::() as u64 - + self.for_of_iters.capacity() as u64 * std::mem::size_of::() as u64 + + self.for_of_iters.capacity() as u64 * std::mem::size_of::() as u64 } } @@ -368,8 +372,10 @@ mod tests { frame_depth: 0, for_of_depth: 0, }); - frame.for_of_iters.push(JsValue::float(6.0)); - frame.last_for_of_result = JsValue::float(7.0); + frame.for_of_iters.push(ForOfEntry { + iterator: JsValue::float(6.0), + last_result: JsValue::float(7.0), + }); 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)); diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 31373d0..087b9c2 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -12,7 +12,7 @@ use smallvec::SmallVec; pub use crate::bindings::init_kernel_builtins; use crate::native::NativeFn; use crate::session_gc::SessionGc; -use crate::vm_state::{GcState, IterState, ProfilingState, SymbolState}; +use crate::vm_state::{ForOfEntry, GcState, IterState, ProfilingState, SymbolState}; use crate::{vm_debug, vm_error, vm_trace, vm_warn}; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_runtime_api as coercion; @@ -309,8 +309,7 @@ pub(crate) struct InlineSyncState { pub(crate) pending_error_kind: Option<&'static str>, pub(crate) pending_completion: Option, pub(crate) for_in_iters: Vec<*mut ForInIter<'static>>, - pub(crate) for_of_iters: Vec, - pub(crate) last_for_of_result: JsValue, + pub(crate) for_of_iters: Vec, pub(crate) saved_bytecode_stack: Vec>, pub(crate) saved_immutables_stack: Vec<*const [JsValue]>, pub(crate) save_stack: Vec, @@ -727,10 +726,10 @@ impl Vm { f(self.async_context.unwrap_or(JsValue::undefined())); f(self.async_gen_context.unwrap_or(JsValue::undefined())); f(self.inline_callee.unwrap_or(JsValue::undefined())); - for &v in &self.iters.for_of_iters { - f(v); + for &entry in &self.iters.for_of_iters { + f(entry.iterator); + f(entry.last_result); } - f(self.iters.last_for_of_result); // 微任务队列中的处理器/能力/值都是 GC 根。 for job in &self.job_queue { crate::promise::for_each_job_value(job, &mut f); @@ -791,10 +790,10 @@ impl Vm { self.async_context = self.async_context.map(&mut rewrite); self.async_gen_context = self.async_gen_context.map(&mut rewrite); self.inline_callee = self.inline_callee.map(&mut rewrite); - for v in &mut self.iters.for_of_iters { - *v = rewrite(*v); + for entry in &mut self.iters.for_of_iters { + entry.iterator = rewrite(entry.iterator); + entry.last_result = rewrite(entry.last_result); } - self.iters.last_for_of_result = rewrite(self.iters.last_for_of_result); // 微任务队列中的值随 sweep 重写。 for job in &mut self.job_queue { crate::promise::rewrite_job_values(job, &mut rewrite); @@ -2255,7 +2254,7 @@ fn rehome_subtree(module: &CompiledModule, base: u32, out: &mut Vec>()); - vm.iters.for_of_iters.push(JsValue::undefined()); + vm.iters.for_of_iters.push(ForOfEntry { + iterator: JsValue::undefined(), + last_result: JsValue::undefined(), + }); vm.saved_bytecode_stack .push(Arc::from(vec![opcode::encode(opcode::OpCode::HALT, 0, 0, 0)])); vm.saved_immutables_stack @@ -2437,7 +2439,10 @@ mod tests { ..CompiledModule::new() }; let mut vm = Vm::new(); - vm.iters.for_of_iters.push(JsValue::undefined()); + vm.iters.for_of_iters.push(ForOfEntry { + iterator: JsValue::undefined(), + last_result: JsValue::undefined(), + }); vm.run(&module).expect("FOR_OF_CLOSE should tolerate non-object sentinel"); diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 2357f1f..76d8075 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -538,7 +538,6 @@ impl Vm { match oxide_builtins::iterator::make_iterator_for_value(self, iterable) { Ok(iterator) => { self.iters.push_for_of(iterator); - self.iters.clear_last_for_of_result(); Ok(()) } Err(err) => { @@ -557,7 +556,6 @@ impl Vm { match crate::async_from_sync::make_async_iterator(self, iterable) { Ok(iterator) => { self.iters.push_for_of(iterator); - self.iters.clear_last_for_of_result(); Ok(()) } Err(err) => { @@ -594,14 +592,14 @@ impl Vm { } /// for-await-of 步进完成检查:读取 `AWAIT` 恢复值(a 槽,即迭代器结果对象)的 - /// `done`,写入 last_for_of_result 并把 `!done` 写 rd(供 JMP_IF_FALSE 分支)。 + /// `done`,写入本迭代器条目结果并把 `!done` 写 rd(供 JMP_IF_FALSE 分支)。 pub(crate) fn dispatch_for_await_of_done(&mut self, rd: usize, a: usize) -> Result<(), String> { vm_trace!("FOR_AWAIT_OF_DONE rd={} r{}={:?}", rd, a, self.regs[a]); let result = self.regs[a]; if !result.is_object() { return self.raise_type_error("iterator result is not an object"); } - self.iters.set_last_for_of_result(result); + self.iters.set_last_result(result); let result_obj = unsafe { &*result.as_js_object_ptr() }; let done_si = self.kernel_core.perm_interner().intern("done").0; let done_val = match self.ordinary_get(result_obj, done_si, result) { @@ -616,10 +614,11 @@ impl Vm { /// 调用 `return()`,其返回的 promise 经 await 挂起,恢复后继续循环后的指令。 pub(crate) fn dispatch_for_await_of_close(&mut self) -> Result<(), String> { vm_trace!("FOR_AWAIT_OF_CLOSE"); - let Some(iterator) = self.iters.pop_for_of() else { + let Some(entry) = self.iters.pop_for_of() else { return Ok(()); }; - let result = self.iters.last_for_of_result(); + let iterator = entry.iterator; + let result = entry.last_result; if result.is_object() { let result_obj = unsafe { &*result.as_js_object_ptr() }; let done_si = self.kernel_core.perm_interner().intern("done").0; @@ -678,7 +677,7 @@ impl Vm { Err(e) => return self.throw_for_of_error(e), }; let done = to_boolean(done_val); - self.iters.set_last_for_of_result(result); + self.iters.set_last_result(result); self.regs[rd] = JsValue::bool(!done); Ok(()) } @@ -686,7 +685,7 @@ impl Vm { pub(crate) fn dispatch_for_of_next(&mut self, rd: usize) -> Result<(), String> { vm_trace!("FOR_OF_NEXT rd={}", rd); self.last_uncaught_value = None; - let result = self.iters.last_for_of_result(); + let result = self.iters.last_result(); if !result.is_object() { self.regs[rd] = JsValue::undefined(); return Ok(()); @@ -716,12 +715,13 @@ impl Vm { pub(crate) fn dispatch_for_of_close(&mut self) -> Result<(), String> { vm_trace!("FOR_OF_CLOSE"); - let Some(iterator) = self.iters.pop_for_of() else { + let Some(entry) = self.iters.pop_for_of() else { return Ok(()); }; + let iterator = entry.iterator; + let result = entry.last_result; // 迭代已自然结束(最后一次 next 返回 done:true)时不调 return(); // 仅当元素耗尽但迭代器未 done(提前退出)才执行 IteratorClose。 - let result = self.iters.last_for_of_result(); if result.is_object() { let result_obj = unsafe { &*result.as_js_object_ptr() }; let done_si = self.kernel_core.perm_interner().intern("done").0; @@ -789,10 +789,10 @@ impl Vm { /// 路径(它已弹出自己的迭代器)。 pub(crate) fn close_for_of_above(&mut self, depth: usize) { while self.iters.for_of_iters.len() > depth { - let Some(iterator) = self.iters.pop_for_of() else { + let Some(entry) = self.iters.pop_for_of() else { break; }; - let _ = self.close_for_of_iterator(iterator, true); + let _ = self.close_for_of_iterator(entry.iterator, true); } } diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index dbd2efe..6ba4f7e 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -50,7 +50,6 @@ macro_rules! inline_save_field { ($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, last_for_of_result, iter_copy) => { $recv.iters.last_for_of_result }; ($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) }; @@ -83,7 +82,6 @@ macro_rules! inline_restore_field { ($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, last_for_of_result, iter_copy) => { $recv.iters.last_for_of_result = $saved.last_for_of_result }; ($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 }; @@ -117,7 +115,6 @@ macro_rules! inline_core_fields { (pending_completion, opt_copy), // V M (for_in_iters, for_in_keys), // V M (for_of_iters, iter_take), // V M - (last_for_of_result, iter_copy), // V M (saved_bytecode_stack, move_field), // M (saved_immutables_stack, move_field), // M (save_stack, move_field), // V M @@ -195,7 +192,6 @@ impl Vm { self.try_stack.clear(); self.iters.for_in_iters.clear(); self.iters.for_of_iters.clear(); - self.iters.last_for_of_result = JsValue::undefined(); self.spill_stack.clear(); self.save_stack.clear(); self.saved_bytecode_stack.clear(); @@ -480,7 +476,10 @@ mod tests { value: JsValue::float(8.0), remaining_finally: 1, }); - vm.iters.last_for_of_result = JsValue::float(9.0); + vm.iters.for_of_iters.push(crate::vm_state::ForOfEntry { + iterator: JsValue::float(8.5), + last_result: JsValue::float(9.0), + }); vm.spill_stack.push(JsValue::float(10.0)); vm.save_stack.push(JsValue::float(11.0)); vm.inline_callee = Some(JsValue::float(12.0)); @@ -506,7 +505,9 @@ mod tests { vm.pending_completion, Some(Completion::Return { value, remaining_finally: 1 }) if value == JsValue::float(8.0) )); - assert_eq!(vm.iters.last_for_of_result, JsValue::float(9.0)); + assert_eq!(vm.iters.for_of_iters.len(), 1); + assert_eq!(vm.iters.for_of_iters[0].iterator, JsValue::float(8.5)); + assert_eq!(vm.iters.for_of_iters[0].last_result, JsValue::float(9.0)); assert_eq!(vm.spill_stack, vec![JsValue::float(10.0)]); assert_eq!(vm.save_stack, vec![JsValue::float(11.0)]); assert_eq!(vm.inline_callee, Some(JsValue::float(12.0))); diff --git a/crates/oxide_vm/src/vm_state.rs b/crates/oxide_vm/src/vm_state.rs index e0cad9b..c13db18 100644 --- a/crates/oxide_vm/src/vm_state.rs +++ b/crates/oxide_vm/src/vm_state.rs @@ -116,18 +116,29 @@ impl SymbolState { } } +/// for-of 迭代器栈条目:迭代器对象与其最近一次 `next()` 结果对象配对存放。 +/// +/// `last_result` 只属于本迭代器(嵌套 for-of / 数组解构 / spread 各自持有自己的 +/// 结果),`FOR_OF_CLOSE`/`FOR_AWAIT_OF_CLOSE` 据此判定本迭代器是否自然 done, +/// 避免共享单槽被其它迭代器覆盖导致漏调/多调 `return()`。 +#[derive(Debug, Clone, Copy)] +pub(crate) struct ForOfEntry { + /// 迭代器对象(同步迭代器 / 异步迭代器 / AsyncFromSyncIterator 包装)。 + pub(crate) iterator: JsValue, + /// 本迭代器最近一次 DONE 返回的结果对象(CLOSE 判 done 用)。 + pub(crate) last_result: JsValue, +} + /// for-in / for-of 的活跃迭代器状态。 pub(crate) struct IterState { pub(crate) for_in_iters: Vec<*mut ForInIter<'static>>, - pub(crate) for_of_iters: Vec, - pub(crate) last_for_of_result: JsValue, + pub(crate) for_of_iters: Vec, } impl IterState { pub(crate) fn reset(&mut self) { self.for_in_iters.clear(); self.for_of_iters.clear(); - self.last_for_of_result = JsValue::undefined(); } pub(crate) fn push_for_in(&mut self, iter: *mut ForInIter<'static>) { @@ -142,28 +153,32 @@ impl IterState { self.for_in_iters.last().copied().unwrap_or(std::ptr::null_mut()) } - pub(crate) fn push_for_of(&mut self, val: JsValue) { - self.for_of_iters.push(val); + /// 压入新迭代器条目:`last_result` 初始为 undefined(尚未执行任何 next())。 + pub(crate) fn push_for_of(&mut self, iterator: JsValue) { + self.for_of_iters.push(ForOfEntry { + iterator, + last_result: JsValue::undefined(), + }); } pub(crate) fn last_for_of(&self) -> Option { - self.for_of_iters.last().copied() + self.for_of_iters.last().map(|e| e.iterator) } - pub(crate) fn pop_for_of(&mut self) -> Option { + pub(crate) fn pop_for_of(&mut self) -> Option { self.for_of_iters.pop() } - pub(crate) fn last_for_of_result(&self) -> JsValue { - self.last_for_of_result + /// 栈顶条目最近一次 DONE 结果;栈空时返回 undefined(防御)。 + pub(crate) fn last_result(&self) -> JsValue { + self.for_of_iters.last().map(|e| e.last_result).unwrap_or(JsValue::undefined()) } - pub(crate) fn set_last_for_of_result(&mut self, val: JsValue) { - self.last_for_of_result = val; - } - - pub(crate) fn clear_last_for_of_result(&mut self) { - self.last_for_of_result = JsValue::undefined(); + /// 写入栈顶条目的 DONE 结果(本迭代器自身的结果,不跨迭代器覆盖)。 + pub(crate) fn set_last_result(&mut self, val: JsValue) { + if let Some(entry) = self.for_of_iters.last_mut() { + entry.last_result = val; + } } } diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index d7656ab..1023304 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -108,7 +108,6 @@ impl Vm { iters: IterState { for_in_iters: Vec::new(), for_of_iters: Vec::new(), - last_for_of_result: JsValue::undefined(), }, profiling: ProfilingState { ic_hits: std::cell::Cell::new(0), @@ -213,7 +212,6 @@ impl Vm { iters: IterState { for_in_iters: Vec::new(), for_of_iters: Vec::new(), - last_for_of_result: JsValue::undefined(), }, profiling: ProfilingState { ic_hits: std::cell::Cell::new(0), diff --git a/crates/oxide_vm/tests/async_tests.rs b/crates/oxide_vm/tests/async_tests.rs index a1165ee..e3ffc49 100644 --- a/crates/oxide_vm/tests/async_tests.rs +++ b/crates/oxide_vm/tests/async_tests.rs @@ -159,3 +159,48 @@ fn async_gen_yield_then_next_continues() { "\"1,2,true\"" ); } + +#[test] +fn for_await_of_break_closes_iterator() { + // 数组解构的 DONE 结果不得覆盖外层 for-await-of 迭代器的结果:break 提前 + // 退出时 CLOSE 按自身条目判 done,须调用 return() 关闭迭代器。 + assert_eq!( + eval( + "let closed=false;\ + const iter={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){closed=true;return Promise.resolve({done:true})}}}};\ + (async()=>{ for await (const [a,b] of iter) { break; } })();\ + Promise.resolve().then(()=>closed)" + ), + "true" + ); +} + +#[test] +fn for_await_of_natural_done_skips_return() { + // for-await-of 迭代自然结束(next 返回 done:true)时不调用 return()。 + assert_eq!( + eval( + "let closed=false;\ + const iter={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:true})},return(){closed=true;return Promise.resolve({done:true})}}}};\ + (async()=>{ for await (const x of iter) { } })();\ + Promise.resolve().then(()=>closed)" + ), + "false" + ); +} + +#[test] +fn for_await_of_nested_sync_for_of_break_close_respective() { + // for-await-of 内嵌同步 for-of:内层 break 只关内层同步迭代器,外层异步 + // 迭代器保持打开(条目配对,互不覆盖结果)。 + assert_eq!( + eval( + "let log=[];\ + const sync={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){log.push('s');return{}}}}};\ + const asyncIter={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){log.push('a');return Promise.resolve({done:true})}}}};\ + (async()=>{ for await (const x of asyncIter) { for (const y of sync) { break; } break; } })();\ + Promise.resolve().then(()=>log.join(','))" + ), + "\"s,a\"" + ); +} diff --git a/crates/oxide_vm/tests/for_of_abrupt_completion_tests.rs b/crates/oxide_vm/tests/for_of_abrupt_completion_tests.rs index bd25bf9..bee4429 100644 --- a/crates/oxide_vm/tests/for_of_abrupt_completion_tests.rs +++ b/crates/oxide_vm/tests/for_of_abrupt_completion_tests.rs @@ -124,3 +124,32 @@ fn for_of_array_regression_still_iterates() { fn for_of_string_regression_still_iterates() { assert_eq!(eval("var r=0;for(var c of 'abc'){r=r+1;}r===3"), "true"); } + +#[test] +fn for_of_destructuring_break_calls_return_on_outer_iterator() { + // 数组解构在循环体内复用 FOR_OF_* 指令:解构自己的 DONE 结果不得覆盖外层 + // 迭代器的结果(按迭代器配对),否则 CLOSE 误判外层已自然结束而漏调 return()。 + assert_eq!( + eval( + "var closed=0;\ + var it={[Symbol.iterator](){return{next(){return{value:[1],done:false}},return(){closed++;return{}}}}};\ + for (const [a,b] of it) { break; }closed===1" + ), + "true" + ); +} + +#[test] +fn for_of_nested_iterators_break_close_inner_only() { + // 嵌套 for-of 提前退出:内层 break 只关内层迭代器(各自条目结果配对), + // 外层迭代器保持打开继续下一轮。 + assert_eq!( + eval( + "var log=[];\ + var inner={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){log.push('i');return{}}}}};\ + var outer={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){log.push('o');return{}}}}};\ + for (var a of outer) { for (var b of inner) { break; } break; }log.join(',')==='i,o'" + ), + "true" + ); +}