From 131e1832766ce8821f96b67a49d4599cb3c2052c Mon Sep 17 00:00:00 2001 From: Berserk Date: Wed, 11 Nov 2026 01:40:21 +0530 Subject: [PATCH] fix(engine): check iterator completion before requesting rest values (#5648) --- core/engine/src/bytecompiler/iterator.rs | 4 + core/engine/src/tests/iterators.rs | 145 +++++++++++++++++++++++ 2 files changed, 149 insertions(+) diff --git a/core/engine/src/bytecompiler/iterator.rs b/core/engine/src/bytecompiler/iterator.rs index d52607d223b..9a5f8347584 100644 --- a/core/engine/src/bytecompiler/iterator.rs +++ b/core/engine/src/bytecompiler/iterator.rs @@ -57,6 +57,9 @@ impl ByteCompiler<'_> { let loop_ = self.next_opcode_location(); self.push_loop_control_info(None, loop_, false); + self.bytecode.emit_iterator_done(temp.variable()); + let done = self.jump_if_true(&temp); + self.bytecode.emit_increment_loop_iteration(); self.iterator_next(false); @@ -71,6 +74,7 @@ impl ByteCompiler<'_> { self.bytecode.emit_jump(loop_); self.pop_loop_control_info(); + self.patch_jump(done); self.patch_jump(end); self.register_allocator.dealloc(temp); } diff --git a/core/engine/src/tests/iterators.rs b/core/engine/src/tests/iterators.rs index cab9fe7a54e..454d0419e38 100644 --- a/core/engine/src/tests/iterators.rs +++ b/core/engine/src/tests/iterators.rs @@ -217,3 +217,148 @@ fn mixed_iterators_close_in_continue() { "#}), ]); } + +#[test] +fn array_rest_iterator_first_result_done() { + run_test_actions([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + let calls = 0; + const it = { + [Symbol.iterator]() { + return this; + }, + next() { + calls++; + return { done: true }; + } + }; + const [x, ...y] = it; + + let assignCalls = 0; + const it2 = { + [Symbol.iterator]() { + return this; + }, + next() { + assignCalls++; + return { done: true }; + } + }; + let a, b; + [a, ...b] = it2; + "#}), + TestAction::assert_eq("calls", 1), + TestAction::assert("x === undefined"), + TestAction::assert("arrayEquals(y, [])"), + TestAction::assert_eq("assignCalls", 1), + TestAction::assert("a === undefined"), + TestAction::assert("arrayEquals(b, [])"), + ]); +} + +#[test] +fn array_rest_iterator_multiple_values() { + run_test_actions([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + let calls = 0; + const values = [1, 2, 3]; + const it = { + [Symbol.iterator]() { + return this; + }, + next() { + calls++; + if (values.length > 0) { + return { value: values.shift(), done: false }; + } + return { done: true }; + } + }; + const [first, ...rest] = it; + "#}), + TestAction::assert_eq("first", 1), + TestAction::assert("arrayEquals(rest, [2, 3])"), + TestAction::assert_eq("calls", 4), + ]); +} + +#[test] +fn array_rest_iterator_next_throws() { + run_test_actions([ + TestAction::run(indoc! {r#" + let calls = 0; + const it = { + [Symbol.iterator]() { + return this; + }, + next() { + calls++; + if (calls === 2) { + throw new Error("iterator next error"); + } + return { value: calls, done: false }; + } + }; + let caught = false; + let errorMsg = ""; + try { + const [first, ...rest] = it; + } catch (err) { + caught = true; + errorMsg = err.message; + } + "#}), + TestAction::assert("caught"), + TestAction::assert_eq("calls", 2), + TestAction::assert("errorMsg === 'iterator next error'"), + ]); +} + +#[test] +fn array_rest_normal_destructuring() { + run_test_actions([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + const [a, b, ...rest] = [1, 2, 3, 4, 5]; + const [...all] = [10, 20]; + const [x, ...empty] = [42]; + const [...emptyAll] = []; + "#}), + TestAction::assert_eq("a", 1), + TestAction::assert_eq("b", 2), + TestAction::assert("arrayEquals(rest, [3, 4, 5])"), + TestAction::assert("arrayEquals(all, [10, 20])"), + TestAction::assert_eq("x", 42), + TestAction::assert("arrayEquals(empty, [])"), + TestAction::assert("arrayEquals(emptyAll, [])"), + ]); +} + +#[test] +fn array_rest_iterator_already_done_before_rest() { + run_test_actions([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + let calls = 0; + const it = { + [Symbol.iterator]() { + return this; + }, + next() { + calls++; + if (calls === 1) { + return { value: "first", done: false }; + } + return { done: true }; + } + }; + const [a, b, ...rest] = it; + "#}), + TestAction::assert_eq("calls", 2), + TestAction::assert("a === 'first'"), + TestAction::assert("b === undefined"), + TestAction::assert("arrayEquals(rest, [])"), + ]); +}