fix: 迭代器获取保留原始值接收者

This commit is contained in:
2026-09-18 00:58:08 +00:00
parent 3de0f8a6cb
commit 75eb0294c4
2 changed files with 164 additions and 15 deletions
+15 -13
View File
@@ -1391,8 +1391,8 @@ pub fn iterator_wrapper_next<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<(JsValue
let builtin_value =
is_array_value(value) || is_typed_array_value(value) || is_map_value(value) || is_set_value(value);
// 非字符串 primitive(boolean/number/symbol/bigint):GetIterator 先 ToObject,
// 再走迭代协议(如 `yield* true` 委托 Boolean.prototype[Symbol.iterator])。
// 非字符串 primitive(boolean/number/symbol/bigint):GetIterator 以 ToObject
// 得 @@iterator 查找起点,receiver 与被调方法的 this 保持原始值(GetV /
// GetIteratorFromMethod 语义,如 `yield* true` 委托 Boolean.prototype[Symbol.iterator])。
// null/undefined 的 ToObject 失败按不可迭代处理。
let obj_value = if value.is_object() {
value
@@ -1614,9 +1615,10 @@ fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<(JsValue
}
};
let obj = unsafe { &*obj_value.as_js_object_ptr() };
// 迭代协议:GetIterator 先取 value[Symbol.iterator] 并调用。
// 迭代协议:GetIterator 先取 value[Symbol.iterator] 并调用;装箱对象仅作查找
// 起点与鸭子回退 inner,receiver/this 用原始 value(GetV / GetIteratorFromMethod)。
let sym_iter_si = make_well_known_symbol_key(0);
let method = match vm.ordinary_get(obj, sym_iter_si, obj_value) {
let method = match vm.ordinary_get(obj, sym_iter_si, value) {
Ok(m) => m,
Err(err) => {
// GetMethod 取 @@iterator 时 getter 抛出:透传原值,不落入鸭子回退。
@@ -1630,7 +1632,7 @@ fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<(JsValue
return Ok(Some((value, None)));
}
if is_callable(method) {
let iterator = match vm.call_function_sync(method, obj_value, &[]) {
let iterator = match vm.call_function_sync(method, value, &[]) {
Ok(it) => it,
Err(err) => {
let exc = vm
@@ -1650,7 +1652,7 @@ fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<(JsValue
// 鸭子回退:对象自身有可调用 next(Map/Set 迭代器包装等既有用法)。
// getter 抛错透传原值(不落入"不可迭代"),读到的闭包随 inner 回传供缓存。
let next_si = vm.kernel_core().perm_interner().intern("next").0;
let next = match vm.ordinary_get(obj, next_si, obj_value) {
let next = match vm.ordinary_get(obj, next_si, value) {
Ok(n) => n,
Err(err) => {
// GetMethod 取 next 时 getter 抛出:透传原值,不降级为 "not iterable"。
@@ -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;
})()",
);
}