diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index 9de24ebe..c837f878 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -1190,18 +1190,6 @@ pub fn make_iterator_for_value_without_return(vm: &mut H, value: JsVa } } -/// 同 [`make_iterator_for_value`],但包装器原型指向调用方指定的原型 -/// (`String.prototype[@@iterator]` 用 %StringIteratorPrototype%)。 -pub(crate) fn make_iterator_for_value_with_proto( - vm: &mut H, value: JsValue, wrapper_proto: *mut JsObject, -) -> Result { - match try_make_iterator_inner_proto(vm, value, true, Some(wrapper_proto)) { - Ok(Some(iterator)) => Ok(iterator), - Ok(None) => Err(crate::error::create_type_error(vm, "value is not iterable")), - Err(err) => Err(err), - } -} - /// 尝试创建迭代器包装对象,把"不可迭代"与"真异常"区分返回。 /// /// # 步骤 @@ -1232,8 +1220,27 @@ pub(crate) fn try_make_iterator_inner_proto( Ok(None) => return Ok(None), Err(err) => return Err(err), }; - // 通用包装器默认挂 %IteratorPrototype%(经原型链获得 @@iterator 返回自身); - // 调用方指定原型时优先(如 String 迭代器的 %StringIteratorPrototype%)。 + Ok(Some(build_iterator_wrapper(vm, inner, cached_next, bind_return, wrapper_proto))) +} + +/// 以给定内层迭代器构造统一迭代器包装对象。 +/// +/// # 步骤 +/// 1. 选定包装器原型:调用方指定时优先(如 String 迭代器的 +/// %StringIteratorPrototype%),否则默认 %IteratorPrototype%(经原型链获得 +/// `@@iterator` 返回自身)。 +/// 2. 写入 `__inner__`/`__index__` 槽;鸭子回退路径把创建时读到的 `next` 闭包 +/// 缓存进 `__next__` 槽,消费期直读槽不重触发 getter。 +/// 3. 挂 `next` 方法;`bind_return` 且内层有可调用 `return` 时条件暴露 `return`。 +/// +/// # 边界 +/// - `bind_return` 控制是否暴露 `return` 方法(for-of/解构的 IteratorClose 需要, +/// `yield*` 委托不需要且须避免创建时访问内层 return getter)。 +/// - 本函数不解析 `@@iterator`:`get_iterator` 派生内层,或默认迭代器函数直接以 +/// 已 ToString 的串为内层构造(其语义只由 `this` 决定,与属性表状态解耦)。 +pub(crate) fn build_iterator_wrapper( + vm: &mut H, inner: JsValue, cached_next: Option, bind_return: bool, wrapper_proto: Option<*mut JsObject>, +) -> JsValue { let iterator_proto = match wrapper_proto { Some(proto) => proto, None => vm.session().builtin_world().iterator_proto.as_ptr() as *mut JsObject, @@ -1271,7 +1278,7 @@ pub(crate) fn try_make_iterator_inner_proto( } } - Ok(Some(JsValue::from_js_object(wrapper))) + JsValue::from_js_object(wrapper) } fn iterator_wrapper_return(vm: &mut H, args: &[u8]) -> NativeResult { diff --git a/crates/oxide_builtins/src/string/regex.rs b/crates/oxide_builtins/src/string/regex.rs index d6b36954..72daa295 100644 --- a/crates/oxide_builtins/src/string/regex.rs +++ b/crates/oxide_builtins/src/string/regex.rs @@ -849,13 +849,11 @@ pub fn string_symbol_iterator(vm: &mut H, args: &[u8]) -> NativeResul return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); } }; - // String 迭代器挂 %StringIteratorPrototype%(链到 %IteratorPrototype%): - // 包装机制复用通用路径,仅替换原型为中继层。 + // 直构 String 迭代器:内层即 ToString 结果,不再回读 @@iterator。本函数语义 + // 只由 this 决定,属性表被删除/置 null/被覆盖均不影响已保存引用的直调。 + // 迭代器挂 %StringIteratorPrototype%(链到 %IteratorPrototype%);串内层不暴露 return。 let string_iter_proto = vm.session().builtin_world().string_iterator_proto.as_ptr() as *mut JsObject; - match crate::iterator::make_iterator_for_value_with_proto(vm, s_val, string_iter_proto) { - Ok(iterator) => NativeResult::Ok(iterator), - Err(err) => NativeResult::Err(err), - } + NativeResult::Ok(crate::iterator::build_iterator_wrapper(vm, s_val, None, true, Some(string_iter_proto))) } /// `matchAll` 迭代器的 `next`:返回 `{value: 匹配数组, done}`,耗尽后 done 为 true。 diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index 6f028ee3..0f29ac1d 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -457,6 +457,16 @@ fn collect_array_like(vm: &mut H, value: JsValue, consult_iterator: b return Ok(values); } + // 字符串源(装箱串)的 length 与索引未物化,直接按 UTF-16 单元取值: + // 与 %TypedArray%.from 的 array-like 臂同读法,避免落通用 length 读空。 + if let Some(units) = crate::array::string_arraylike_units(vm, value) { + let mut values = Vec::with_capacity(units.len()); + for &unit in &units { + values.push(crate::array::unit_string_value(vm, unit)); + } + return Ok(values); + } + let length_si = vm.kernel_core().perm_interner().intern("length").0; let len_val = vm .ordinary_get(obj, length_si, value) diff --git a/crates/oxide_vm/tests/string_iterator_removed_tests.rs b/crates/oxide_vm/tests/string_iterator_removed_tests.rs index 4d2774c2..9838d6ae 100644 --- a/crates/oxide_vm/tests/string_iterator_removed_tests.rs +++ b/crates/oxide_vm/tests/string_iterator_removed_tests.rs @@ -296,3 +296,95 @@ fn default_string_iterator_regression() { })()"#, ); } + +#[test] +fn saved_default_iterator_still_works_after_delete_or_null() { + // 已保存的默认迭代器引用直调:语义只由 this 决定,属性表删除/置 null/置 + // undefined 不影响,仍按码元产出(不重新回读 @@iterator)。 + assert_truthy( + "saved iterator delete", + r#"(function(){ + var f = String.prototype[Symbol.iterator]; + delete String.prototype[Symbol.iterator]; + var it = f.call("ab"); + return it.next().value === "a" && it.next().value === "b"; + })()"#, + ); + assert_truthy( + "saved iterator null", + r#"(function(){ + var f = String.prototype[Symbol.iterator]; + String.prototype[Symbol.iterator] = null; + var it = f.call("ab"); + return it.next().value === "a" && it.next().value === "b"; + })()"#, + ); + assert_truthy( + "saved iterator undefined", + r#"(function(){ + var f = String.prototype[Symbol.iterator]; + String.prototype[Symbol.iterator] = undefined; + var it = f.call("ab"); + return it.next().value === "a" && it.next().value === "b"; + })()"#, + ); +} + +#[test] +fn saved_default_iterator_ignores_user_override() { + // 用户把 @@iterator 覆盖成函数后,已保存的默认迭代器引用直调仍构造 String + // 迭代器(规范忽略覆盖),不转发到覆盖函数。 + assert_truthy( + "saved iterator override", + r#"(function(){ + var f = String.prototype[Symbol.iterator]; + var called = false; + String.prototype[Symbol.iterator] = function(){ called = true; return null; }; + var it = f.call("ab"); + return !called && it.next().value === "a" && it.next().value === "b"; + })()"#, + ); +} + +#[test] +fn saved_default_iterator_handles_surrogate_pair_codepoint() { + // 直构仍走码点口径:合法代理对整体产出单个元素。 + assert_truthy( + "saved iterator gclef", + r#"(function(){ + var f = String.prototype[Symbol.iterator]; + delete String.prototype[Symbol.iterator]; + var it = f.call("\uD834\uDD1E"); + var first = it.next().value; + return first === "\uD834\uDD1E" && it.next().done === true; + })()"#, + ); +} + +#[test] +fn typed_array_constructor_boxed_string_uses_units_after_delete() { + // 构造器 array-like 臂与 %TypedArray%.from 对称:装箱串按 UTF-16 单元取值, + // 不读自身可调用 next,也不因 length 未物化读空。 + assert_truthy( + "new Uint8Array boxed duck-next delete", + r#"(function(){ + var s = new String("ab"); + var duckCalled = false; + s.next = function(){ duckCalled = true; return { value: 99, done: false }; }; + delete String.prototype[Symbol.iterator]; + var u = new Uint8Array(s); + return u.length === 2 && u[0] === 0 && u[1] === 0 && !duckCalled; + })()"#, + ); + assert_truthy( + "Uint8Array.from boxed duck-next delete symmetric", + r#"(function(){ + var s = new String("ab"); + var duckCalled = false; + s.next = function(){ duckCalled = true; return { value: 99, done: false }; }; + delete String.prototype[Symbol.iterator]; + var u = Uint8Array.from(s); + return u.length === 2 && u[0] === 0 && u[1] === 0 && !duckCalled; + })()"#, + ); +}