diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index c837f878..f131fc8b 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -1391,8 +1391,8 @@ pub fn iterator_wrapper_next(vm: &mut H, args: &[u8]) -> NativeResult /// # 边界 /// 字符串原始值按自身 wrapper 的原型链读 `@@iterator`(读起点 String.prototype、 /// receiver = 原始值),不套用 duck-next 回退(原始串无自身 `next`);非对象原始值 -/// (number/boolean 等)装箱后判定。`@@iterator` getter 抛错时透传 `Err` -/// (不落入鸭子回退)。 +/// (number/boolean 等)经 ToObject 得查找起点,receiver 保持原始值(GetV 语义)。 +/// `@@iterator` getter 抛错时透传 `Err`(不落入鸭子回退)。 /// /// # 注意事项 /// 三态是 GetMethod-可选入口的规范语义:非空不可调用(含 [[IsHTMLDDA]] @@ -1426,9 +1426,9 @@ pub(crate) fn peek_iterator_method(vm: &mut H, value: JsValue) -> Res // null/undefined:原始串的临时 wrapper 无自身可调用 next,落 array-like。 return Ok(false); } - // 原始值(number/bigint/boolean/symbol)先装箱再按对象读 @@iterator - // (如 `Array.from(5)` 经 Number.prototype[Symbol.iterator]); - // null/undefined 装箱失败按不可迭代处理。 + // 原始值(number/bigint/boolean/symbol)经 ToObject 得 @@iterator 查找起点, + // receiver 保持原始值本身(getter 观测原始类型,如 `Array.from(5)` 经 + // Number.prototype[Symbol.iterator]);null/undefined 装箱失败按不可迭代处理。 let recv = if value.is_object() { value } else { @@ -1440,7 +1440,7 @@ pub(crate) fn peek_iterator_method(vm: &mut H, value: JsValue) -> Res if recv.is_object() { let obj = unsafe { &*recv.as_js_object_ptr() }; let sym_iter_si = make_well_known_symbol_key(0); - let method = match vm.ordinary_get(obj, sym_iter_si, recv) { + let method = match vm.ordinary_get(obj, sym_iter_si, value) { Ok(m) => m, Err(err) => { // GetMethod 取 @@iterator 时 getter 抛出:透传原值,不落入鸭子回退。 @@ -1463,7 +1463,7 @@ pub(crate) fn peek_iterator_method(vm: &mut H, value: JsValue) -> Res // @@iterator(Array.from/%TypedArray%.from 应落 array-like)。 if !obj.is_string_obj() { let next_si = vm.kernel_core().perm_interner().intern("next").0; - if let Ok(next) = vm.ordinary_get(obj, next_si, recv) { + if let Ok(next) = vm.ordinary_get(obj, next_si, value) { if is_callable(next) { return Ok(true); } @@ -1602,8 +1602,9 @@ fn get_iterator(vm: &mut H, value: JsValue) -> Result(vm: &mut H, value: JsValue) -> Result m, Err(err) => { // GetMethod 取 @@iterator 时 getter 抛出:透传原值,不落入鸭子回退。 @@ -1630,7 +1632,7 @@ fn get_iterator(vm: &mut H, value: JsValue) -> Result it, Err(err) => { let exc = vm @@ -1650,7 +1652,7 @@ fn get_iterator(vm: &mut H, value: JsValue) -> Result n, Err(err) => { // GetMethod 取 next 时 getter 抛出:透传原值,不降级为 "not iterable"。 diff --git a/crates/oxide_vm/tests/iterator_from_primitives_tests.rs b/crates/oxide_vm/tests/iterator_from_primitives_tests.rs index 7117caec..fbe2e2d8 100644 --- a/crates/oxide_vm/tests/iterator_from_primitives_tests.rs +++ b/crates/oxide_vm/tests/iterator_from_primitives_tests.rs @@ -1,7 +1,11 @@ //! `Iterator.from` / `Array.from` 的原始值迭代器面钉:String 臂经 GetMethod //! 以原始值 receiver 读 `@@iterator`(用户覆盖 getter 观测 `typeof this`), -//! peek 对非字符串原始值装箱读 `@@iterator`,`Iterator.from` 对非字符串原始值 +//! peek 与 get_iterator 对非字符串原始值经 ToObject 得查找起点、receiver 保持 +//! 原始值(GetV / GetIteratorFromMethod 语义),`Iterator.from` 对非字符串原始值 //! 非对象按 GetIteratorFlattenable 抛 TypeError(区别于 `Array.from` 的装箱路径)。 +//! +//! 观察者一律用 `'use strict'`:引擎对 sloppy 函数的原始 this 不做 ToObject +//! (既有债),sloppy 观察者不反映本组 GetV receiver 语义。 use std::sync::Arc; @@ -47,7 +51,8 @@ fn iterator_from_string_getter_sees_raw_string() { #[test] fn array_from_number_boxes_to_read_number_iterator() { - // Array.from(5) 经 GetIterator 装箱后读 Number.prototype[Symbol.iterator]。 + // Array.from(5) 经 GetIterator 以原始值 receiver 调用 + // Number.prototype[Symbol.iterator](装箱对象只作查找起点)。 assert_truthy( "Array.from(5)", "(function(){ @@ -127,3 +132,145 @@ fn for_of_string_default_no_override() { })()", ); } + +#[test] +fn array_from_number_strict_observer_sees_raw_receiver() { + // Array.from(5) 同经 peek 与 get_iterator:strict getter 与 strict 方法的 + // this 均观测原始 number(装箱对象只作查找起点,receiver 保持原始值)。 + assert_truthy( + "Array.from(5) raw receiver", + "(function(){ + var getThis, callThis; + function iter(){ 'use strict'; callThis = typeof this; return [1][Symbol.iterator](); } + Object.defineProperty(Number.prototype, Symbol.iterator, { + configurable: true, + get(){ 'use strict'; getThis = typeof this; return iter; } + }); + Array.from(5); + return getThis === 'number' && callThis === 'number'; + })()", + ); +} + +#[test] +fn for_of_number_strict_observer_sees_raw_receiver() { + // for-of 只经 get_iterator:strict getter 与被调方法的 this 均为原始 number。 + assert_truthy( + "for-of 5 raw receiver", + "(function(){ + var getThis, callThis; + function iter(){ 'use strict'; callThis = typeof this; return [1][Symbol.iterator](); } + Object.defineProperty(Number.prototype, Symbol.iterator, { + configurable: true, + get(){ 'use strict'; getThis = typeof this; return iter; } + }); + var n = 0; + for (var x of 5) { n++; } + return getThis === 'number' && callThis === 'number' && n === 1; + })()", + ); +} + +#[test] +fn spread_number_strict_observer_sees_raw_receiver() { + // 数组展开 `[...5]` 只经 get_iterator:strict getter 与方法的 this 均为原始 number。 + assert_truthy( + "[...5] raw receiver", + "(function(){ + var getThis, callThis; + function iter(){ 'use strict'; callThis = typeof this; return [1][Symbol.iterator](); } + Object.defineProperty(Number.prototype, Symbol.iterator, { + configurable: true, + get(){ 'use strict'; getThis = typeof this; return iter; } + }); + var r = [...5]; + return getThis === 'number' && callThis === 'number' && r.length === 1 && r[0] === 1; + })()", + ); +} + +#[test] +fn array_from_primitive_types_strict_observer_sees_raw_receiver() { + // boolean/bigint/symbol 原始值:getter 与被调方法的 this 观测各自原始类型。 + assert_truthy( + "Array.from(true/5n/Symbol()) raw receivers", + "(function(){ + function observe(proto, value){ + var getThis, callThis; + function iter(){ 'use strict'; callThis = typeof this; return [1][Symbol.iterator](); } + Object.defineProperty(proto, Symbol.iterator, { + configurable: true, + get(){ 'use strict'; getThis = typeof this; return iter; } + }); + Array.from(value); + return getThis + '|' + callThis; + } + return observe(Boolean.prototype, true) === 'boolean|boolean' + && observe(BigInt.prototype, 5n) === 'bigint|bigint' + && observe(Symbol.prototype, Symbol()) === 'symbol|symbol'; + })()", + ); +} + +#[test] +fn typed_array_from_number_strict_observer_sees_raw_receiver() { + // %TypedArray%.from 亦经 peek + get_iterator:strict this 观测原始 number。 + assert_truthy( + "Int8Array.from(5) raw receiver", + "(function(){ + var getThis, callThis; + function iter(){ 'use strict'; callThis = typeof this; return [1][Symbol.iterator](); } + Object.defineProperty(Number.prototype, Symbol.iterator, { + configurable: true, + get(){ 'use strict'; getThis = typeof this; return iter; } + }); + var r = Int8Array.from(5); + return getThis === 'number' && callThis === 'number' && r.length === 1 && r[0] === 1; + })()", + ); +} + +#[test] +fn array_from_boxed_objects_strict_observer_sees_object_receiver() { + // 装箱对象(new Number / new String)的 receiver 仍为对象:装箱面不回归。 + assert_truthy( + "Array.from(new Number(5))/new String('ab') object receivers", + "(function(){ + function observe(proto, value){ + var getThis, callThis; + function iter(){ 'use strict'; callThis = typeof this; return [1][Symbol.iterator](); } + Object.defineProperty(proto, Symbol.iterator, { + configurable: true, + get(){ 'use strict'; getThis = typeof this; return iter; } + }); + Array.from(value); + return getThis + '|' + callThis; + } + return observe(Number.prototype, new Number(5)) === 'object|object' + && observe(String.prototype, new String('ab')) === 'object|object'; + })()", + ); +} + +#[test] +fn primitive_iterator_default_paths_regression() { + // 无覆盖时非字符串原始值不可迭代:for-of 抛 TypeError、Array.from 走 + // array-like 空结果;数组与字符串的默认路径保持不变。 + assert_truthy( + "primitive default paths", + "(function(){ + var threw = false; + try { for (var x of 5) { } } catch (e) { threw = e instanceof TypeError; } + if (!threw) return false; + if (Array.from(5).length !== 0) return false; + var a = Array.from([1,2]); + if (a.length !== 2 || a[0] !== 1 || a[1] !== 2) return false; + var n = 0; + for (var c of 'ab') { n++; } + if (n !== 2) return false; + var s = Array.from('ab'); + if (s.length !== 2 || s[0] !== 'a' || s[1] !== 'b') return false; + return true; + })()", + ); +}