fix: 迭代器快速路径尊重用户覆盖的 @@iterator

This commit is contained in:
2026-09-14 17:35:40 +00:00
parent e5d0558388
commit d67e1469c1
2 changed files with 106 additions and 9 deletions
+47 -9
View File
@@ -1406,9 +1406,42 @@ pub(crate) fn peek_iterator_method<H: VmHost>(vm: &mut H, value: JsValue) -> Res
Ok(false)
}
/// 经迭代协议取内层迭代器:内建集合直接作为内层(无缓存 next),`@@iterator`
/// 调用结果作为内层(无缓存 next),鸭子回退路径同时回传读到的 `next` 闭包
/// 供调用方缓存进包装器 `__next__` 槽(消费期不重触发 getter)。
/// 判断已读到的 `@@iterator` 结果是否仍是内建集合的默认迭代器函数
/// (内建原型别名槽上的同一函数对象)。
///
/// # 边界
/// value 须为内建集合(Array/TypedArray/Map/Set),锚点槽按种类选取
/// (Array/TypedArray/Set 为 `values`、Map 为 `entries`)。
///
/// # 注意
/// 锚点槽读取失败(如用户把别名改写为抛出 getter)一律返回 false,
/// 落入通用协议按 `method` 继续处理,不在此处吞错。
fn builtin_iterator_default_intact<H: VmHost>(vm: &mut H, value: JsValue, method: JsValue) -> bool {
if !method.is_object() {
return false;
}
let world = vm.session().builtin_world();
let (anchor_ptr, anchor_name) = if is_array_value(value) {
(world.array_proto.as_ptr() as *mut JsObject, "values")
} else if is_typed_array_value(value) {
(world.typed_array_proto.as_ptr() as *mut JsObject, "values")
} else if is_map_value(value) {
(world.map_proto.as_ptr() as *mut JsObject, "entries")
} else {
(world.set_proto.as_ptr() as *mut JsObject, "values")
};
let anchor_obj = unsafe { &*anchor_ptr };
let anchor_si = vm.kernel_core().perm_interner().intern(anchor_name).0;
let Ok(anchor) = vm.ordinary_get(anchor_obj, anchor_si, JsValue::from_js_object(anchor_ptr)) else {
return false;
};
anchor.is_object() && std::ptr::eq(method.as_js_object_ptr(), anchor.as_js_object_ptr())
}
/// 经迭代协议取内层迭代器:内建集合(原型链 `@@iterator` 未被覆盖时)直接
/// 作为内层(无缓存 next),用户覆盖的 `@@iterator` 或 duck-next 回退路径
/// 的调用结果作为内层(无缓存 next),鸭子回退路径同时回传读到的 `next`
/// 闭包供调用方缓存进包装器 `__next__` 槽(消费期不重触发 getter)。
///
/// # 返回值
/// - `Ok(Some((inner, next)))`:`inner` 为内层迭代器;鸭子回退时 `next` 为
@@ -1416,14 +1449,16 @@ pub(crate) fn peek_iterator_method<H: VmHost>(vm: &mut H, value: JsValue) -> Res
/// - `Ok(None)`:不可迭代;
/// - `Err`:`@@iterator` 或 `next` getter 抛错,透传原异常值。
fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<(JsValue, Option<JsValue>)>, JsValue> {
if value.is_string()
|| is_array_value(value)
|| is_typed_array_value(value)
|| is_map_value(value)
|| is_set_value(value)
{
if value.is_string() {
return Ok(Some((value, None)));
}
// 内建集合标记:仅当 @@iterator 经原型链读到的仍是内建默认迭代器函数
// 时才走快速路径;用户覆盖(自身属性或原型链改写/删除)走通用协议
// 调用用户函数。
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])。
@@ -1449,6 +1484,9 @@ fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<(JsValue
return Err(exc);
}
};
if builtin_value && builtin_iterator_default_intact(vm, value, method) {
return Ok(Some((value, None)));
}
if is_callable(method) {
let iterator = match vm.call_function_sync(method, obj_value, &[]) {
Ok(it) => it,
@@ -2112,3 +2112,62 @@ fn iterator_helper_rebound_after_full_reset() {
assert_eq!(result.as_bool(), expected, "for {}", src);
}
}
#[test]
fn for_of_respects_overridden_array_prototype_iterator() {
let mut vm = Vm::new();
// 红形钉:内置原型上改写的 @@iterator 必须被迭代入口调用(展开与 for-of
// 同面),红形为短路直取内建迭代器、无视覆盖。
let r = eval(
&mut vm,
"(() => {
const old = Array.prototype[Symbol.iterator];
Array.prototype[Symbol.iterator] = function* () { yield 42; };
try {
const spread = [...[1, 2, 3]].join(',');
let looped = '';
for (const v of [7]) looped += v;
return spread + '|' + looped;
} finally { Array.prototype[Symbol.iterator] = old; }
})()",
)
.unwrap();
assert_eq!(to_str(&vm, r), "42|42");
}
#[test]
fn for_of_respects_own_iterator_override_internal_paths_unaffected() {
let mut vm = Vm::new();
// 红形钉:实例自身属性的 @@iterator 覆盖须生效;slice 走内部元素直读面,
// 不受覆盖影响(内部路径不观测用户迭代器)。
let r = eval(
&mut vm,
"(() => {
const a = [7, 8];
a[Symbol.iterator] = function* () { yield 99; };
return [...a].join(',') + '|' + a.slice(0, 2).join(',');
})()",
)
.unwrap();
assert_eq!(to_str(&vm, r), "99|7,8");
}
#[test]
fn for_of_throws_when_array_prototype_iterator_deleted() {
let mut vm = Vm::new();
// 红形钉:原型 @@iterator 被删除后 GetMethod 取不到方法,迭代入口须抛
// TypeError(红形为短路直取元素区、静默迭代成功)。
let r = eval(
&mut vm,
"(() => {
const old = Array.prototype[Symbol.iterator];
delete Array.prototype[Symbol.iterator];
try {
try { for (const _v of [1, 2]) {} return 'no'; }
catch (e) { return e.constructor.name; }
} finally { Array.prototype[Symbol.iterator] = old; }
})()",
)
.unwrap();
assert_eq!(to_str(&vm, r), "TypeError");
}