fix: 字符串面 GetIterator 尊重用户 @@iterator 覆盖

String 臂此前对原始串与装箱串一律走码元步进、忽略用户覆盖的
@@iterator。改为按 GetMethod 语义以值自身为 receiver 读 @@iterator
(原始串落 String.prototype、装箱串读自身→原型链),默认迭代器
自别名即 @@iterator 槽本身、以绑定层捕获的函数指针比较走快速
路径避免递归回本臂;覆盖则调用、receiver 为值。peek 对非字符串
原始值先装箱再读 @@iterator;Iterator.from 增 GetIteratorFlattenable
前置校验,非字符串原始值非对象 → TypeError(不装箱)。
This commit is contained in:
2026-09-15 02:18:12 +00:00
parent 4b0b52ade3
commit 0f5bd1577e
4 changed files with 252 additions and 6 deletions
+98 -6
View File
@@ -1156,6 +1156,12 @@ pub fn iterator_symbol_iterator<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRes
/// 包装器带 `next` 与 `return`(用于 for-of 提前退出时的 IteratorClose 清理)。 /// 包装器带 `next` 与 `return`(用于 for-of 提前退出时的 IteratorClose 清理)。
pub fn iterator_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn iterator_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let iterable = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; let iterable = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
// GetIteratorFlattenable 语义:非字符串原始值非对象 → TypeError(不装箱,
// 区别于 Array.from 的 GetIterator 装箱路径)。字符串保留原始值,由 String
// 臂以原始值 receiver 读 @@iterator(typeof this === 'string')。
if !iterable.is_object() && !iterable.is_string() {
return NativeResult::Err(crate::error::create_type_error(vm, "value is not an object"));
}
match make_iterator_for_value(vm, iterable) { match make_iterator_for_value(vm, iterable) {
Ok(iterator) => NativeResult::Ok(iterator), Ok(iterator) => NativeResult::Ok(iterator),
Err(err) => NativeResult::Err(err), Err(err) => NativeResult::Err(err),
@@ -1386,10 +1392,21 @@ pub(crate) fn peek_iterator_method<H: VmHost>(vm: &mut H, value: JsValue) -> Res
if value.is_string() { if value.is_string() {
return Ok(true); return Ok(true);
} }
if value.is_object() { // 原始值(number/bigint/boolean/symbol)先装箱再按对象读 @@iterator
let obj = unsafe { &*value.as_js_object_ptr() }; // (如 `Array.from(5)` 经 Number.prototype[Symbol.iterator]);
// null/undefined 装箱失败按不可迭代处理。
let recv = if value.is_object() {
value
} else {
match to_object(value, vm) {
Ok(o) => o,
Err(_) => return Ok(false),
}
};
if recv.is_object() {
let obj = unsafe { &*recv.as_js_object_ptr() };
let sym_iter_si = make_well_known_symbol_key(0); let sym_iter_si = make_well_known_symbol_key(0);
let method = match vm.ordinary_get(obj, sym_iter_si, value) { let method = match vm.ordinary_get(obj, sym_iter_si, recv) {
Ok(m) => m, Ok(m) => m,
Err(err) => { Err(err) => {
// GetMethod 取 @@iterator 时 getter 抛出:透传原值,不落入鸭子回退。 // GetMethod 取 @@iterator 时 getter 抛出:透传原值,不落入鸭子回退。
@@ -1409,7 +1426,7 @@ pub(crate) fn peek_iterator_method<H: VmHost>(vm: &mut H, value: JsValue) -> Res
} }
// 鸭子回退:@@iterator 解析为 null/undefined 且对象自身有可调用 next。 // 鸭子回退:@@iterator 解析为 null/undefined 且对象自身有可调用 next。
let next_si = vm.kernel_core().perm_interner().intern("next").0; let next_si = vm.kernel_core().perm_interner().intern("next").0;
if let Ok(next) = vm.ordinary_get(obj, next_si, value) { if let Ok(next) = vm.ordinary_get(obj, next_si, recv) {
if is_callable(next) { if is_callable(next) {
return Ok(true); return Ok(true);
} }
@@ -1461,8 +1478,83 @@ fn builtin_iterator_default_intact<H: VmHost>(vm: &mut H, value: JsValue, method
/// - `Ok(None)`:不可迭代; /// - `Ok(None)`:不可迭代;
/// - `Err`:`@@iterator` 或 `next` getter 抛错,透传原异常值。 /// - `Err`:`@@iterator` 或 `next` getter 抛错,透传原异常值。
fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<(JsValue, Option<JsValue>)>, JsValue> { fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<(JsValue, Option<JsValue>)>, JsValue> {
if value.is_string() { // 字符串面:原始串与装箱串(is_string_obj)同走本臂,GetMethod 语义。
return Ok(Some((value, None))); // receiver = value:原始串 typeof this 'string',装箱串 'object'。读 @@iterator
// 从值的自身对象起(原始串无自身对象,落 String.prototype;装箱串自身 → 原型链),
// 覆盖默认快速路径的码元步进,并避免调默认迭代器再递归回本臂二次触发 getter。
let value_is_string_box = if value.is_string() {
false
} else if value.is_object() {
unsafe { &*value.as_js_object_ptr() }.is_string_obj()
} else {
false
};
if value.is_string() || value_is_string_box {
// 读 @@iterator 的对象起点:装箱串读自身(可命中自身 @@iterator),
// 原始串读 String.prototype(原始串无自身属性)。
let read_ptr = if value_is_string_box {
value.as_js_object_ptr()
} else {
vm.session().builtin_world().string_proto.as_ptr() as *mut JsObject
};
let read_obj = unsafe { &*read_ptr };
let sym_iter_si = make_well_known_symbol_key(0);
let method = match vm.ordinary_get(read_obj, sym_iter_si, value) {
Ok(m) => m,
Err(err) => {
// GetMethod 取 @@iterator 时 getter 抛出:透传原值,不落入码元快速路径。
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &err));
return Err(exc);
}
};
// 码元快速路径的 inner:原始串取 value 本身;装箱串取其内容(槽 0 恒为
// 原始串)——wrapper 码元步进按原始串产出,装箱串不直接作 inner。
let inner = if value_is_string_box {
let raw = unsafe { (&*value.as_js_object_ptr()).get_prop_at(0) };
if raw.is_string() {
raw
} else {
value
}
} else {
value
};
// 默认迭代器(自别名:即 @@iterator 槽本身)走码元快速路径、不调方法——
// 调默认函数会递归回本 String 臂。指针未捕获(绑定前瞬时)亦回退快速路径。
let default_ptr = vm.session().builtin_world().string_default_iterator.get();
if !default_ptr.is_null()
&& is_callable(method)
&& std::ptr::eq(method.as_js_object_ptr() as *const JsObject, default_ptr)
{
return Ok(Some((inner, None)));
}
if is_callable(method) {
// 用户覆盖:调用,receiver = value(原始串/装箱串)。
let iterator = match vm.call_function_sync(method, value, &[]) {
Ok(it) => it,
Err(err) => {
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &err));
return Err(exc);
}
};
if !iterator.is_object() {
return Err(crate::error::create_type_error(
vm,
"Result of the Symbol.iterator method is not an object",
));
}
return Ok(Some((iterator, None)));
}
if !method.is_null() && !method.is_undefined() {
// GetMethod 步 4:非空不可调用方法 → TypeError。
return Err(crate::error::create_type_error(vm, "value is not iterable"));
}
// null/undefined:String 恒可迭代 → 码元快速路径。
return Ok(Some((inner, None)));
} }
// 内建集合标记:仅当 @@iterator 经原型链读到的仍是内建默认迭代器函数 // 内建集合标记:仅当 @@iterator 经原型链读到的仍是内建默认迭代器函数
// 时才走快速路径;用户覆盖(自身属性或原型链改写/删除)走通用协议 // 时才走快速路径;用户覆盖(自身属性或原型链改写/删除)走通用协议
+13
View File
@@ -301,6 +301,11 @@ pub struct BuiltinWorld {
pub set_iterator_proto: P<JsObject>, pub set_iterator_proto: P<JsObject>,
/// `%StringIteratorPrototype%`:String.prototype[@@iterator] 返回的迭代器。 /// `%StringIteratorPrototype%`:String.prototype[@@iterator] 返回的迭代器。
pub string_iterator_proto: P<JsObject>, pub string_iterator_proto: P<JsObject>,
/// String.prototype[@@iterator] 默认迭代器函数对象指针(绑定层捕获,原始值指针、
/// 所有权归 wrapper 释放表)。String 臂覆盖判定以此做指针比较:默认迭代器即
/// @@iterator 槽本身(自别名),集合式锚点槽比较不可复用,须独立存指针。
/// `Cell` 供绑定层经 `&Arc<BuiltinWorld>` 共享引用写入。
pub string_default_iterator: std::cell::Cell<*const JsObject>,
/// `%RegExpStringIteratorPrototype%`:matchAll 返回的迭代器。 /// `%RegExpStringIteratorPrototype%`:matchAll 返回的迭代器。
pub regexp_string_iterator_proto: P<JsObject>, pub regexp_string_iterator_proto: P<JsObject>,
/// `%IteratorHelperPrototype%`:Iterator helpers 结果对象的共享原型,链到 %IteratorPrototype%。 /// `%IteratorHelperPrototype%`:Iterator helpers 结果对象的共享原型,链到 %IteratorPrototype%。
@@ -1246,9 +1251,12 @@ impl BuiltinWorld {
let iterator_helper_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let iterator_helper_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let disposable_stack_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let disposable_stack_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let async_disposable_stack_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let async_disposable_stack_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
// 默认迭代器指针初值空(绑定层随后经 bind_string 捕获写入)。
let string_default_iterator = std::cell::Cell::new(std::ptr::null());
let world = Self { let world = Self {
object_proto, object_proto,
string_default_iterator,
array_proto, array_proto,
function_proto, function_proto,
string_proto, string_proto,
@@ -1687,6 +1695,11 @@ impl BuiltinWorld {
let world = BuiltinWorld { let world = BuiltinWorld {
object_proto, object_proto,
string_default_iterator: std::cell::Cell::new(if dirty.string {
std::ptr::null()
} else {
current.string_default_iterator.get()
}),
array_proto, array_proto,
function_proto, function_proto,
string_proto, string_proto,
@@ -72,6 +72,18 @@ pub fn bind_string(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
oxide_builtins::string::string_symbol_iterator::<crate::vm::Vm> as *const (), oxide_builtins::string::string_symbol_iterator::<crate::vm::Vm> as *const (),
0, 0,
); );
// 捕获默认迭代器函数对象指针写入 world:String 臂覆盖判定以此做指针比较。
// 此时槽为初始数据属性(用户覆盖尚未可能),读回即默认函数本体。
let iter_key = oxide_types::private_key::make_well_known_symbol_key(0);
if let Some(pos) = core.shape_forge().lookup_position(proto.shape_id(), iter_key) {
let v = proto.get_prop_at(pos);
if v.is_object() {
session
.builtin_world()
.string_default_iterator
.set(v.as_js_object_ptr() as *const JsObject);
}
}
// 全局 String 槽位既有槽原位更新(旧家族构造器指针不得滞留在属性 vec), // 全局 String 槽位既有槽原位更新(旧家族构造器指针不得滞留在属性 vec),
// 无槽时开新槽;描述符非枚举(规范 { writable:true, enumerable:false, configurable:true })。 // 无槽时开新槽;描述符非枚举(规范 { writable:true, enumerable:false, configurable:true })。
@@ -0,0 +1,129 @@
//! `Iterator.from` / `Array.from` 的原始值迭代器面钉:String 臂经 GetMethod
//! 以原始值 receiver 读 `@@iterator`(用户覆盖 getter 观测 `typeof this`),
//! peek 对非字符串原始值装箱读 `@@iterator`,`Iterator.from` 对非字符串原始值
//! 非对象按 GetIteratorFlattenable 抛 TypeError(区别于 `Array.from` 的装箱路径)。
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn run(source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
let mut vm = Vm::new();
vm.run(&Arc::new(module))
}
/// 断言 IIFE 求值为 `true`(把多断言收敛为布尔,规避 JsValue Display 只暴露
/// number/bool 的限制)。
fn assert_truthy(name: &str, source: &str) {
let result = run(source).unwrap_or_else(|e| panic!("{name}: {e}"));
assert!(result.as_bool(), "{name}: expected truthy, got {:?}", result);
}
#[test]
fn iterator_from_string_getter_sees_raw_string() {
// 原始字符串 receiver:getter 的 typeof this 为 'string'(不装箱);
// 包装字符串对象 receiver 为 'object'。
assert_truthy(
"string getter receiver",
"(function(){
var orig = String.prototype[Symbol.iterator];
var obs;
Object.defineProperty(String.prototype, Symbol.iterator, {
get(){ 'use strict'; obs = typeof this; return orig; }
});
Iterator.from('');
var raw = obs;
Iterator.from(new String(''));
var boxed = obs;
return raw === 'string' && boxed === 'object';
})()",
);
}
#[test]
fn array_from_number_boxes_to_read_number_iterator() {
// Array.from(5) 经 GetIterator 装箱后读 Number.prototype[Symbol.iterator]。
assert_truthy(
"Array.from(5)",
"(function(){
Number.prototype[Symbol.iterator] = function*(){
var i = 0, t = this >>> 0;
while (i < t) { yield i; ++i; }
};
var r = Array.from(5);
return r.length === 5 && r[0] === 0 && r[1] === 1 && r[4] === 4;
})()",
);
}
#[test]
fn iterator_from_number_throws_type_error() {
// Iterator.from(5):非字符串原始值非对象,GetIteratorFlattenable 抛 TypeError
// (不装箱),即便 Number.prototype 已覆盖。
assert_truthy(
"Iterator.from(5) throws",
"(function(){
Number.prototype[Symbol.iterator] = function*(){ yield 1; };
try { Iterator.from(5); return false; }
catch (e) { return e instanceof TypeError; }
})()",
);
}
#[test]
fn array_from_iterator_from_boxed_number() {
// new Number(5) 是对象:Iterator.from 经 GetIteratorFlattenable 走覆盖迭代器。
assert_truthy(
"Array.from(Iterator.from(new Number(5)))",
"(function(){
Number.prototype[Symbol.iterator] = function*(){
var i = 0, t = this >>> 0;
while (i < t) { yield i; ++i; }
};
var r = Array.from(Iterator.from(new Number(5)));
return r.length === 5 && r[0] === 0 && r[4] === 4;
})()",
);
}
#[test]
fn array_from_iterator_from_string_yields_chars() {
// Iterator.from 原始字符串走 String 臂码元快速路径,产出字符序列。
assert_truthy(
"Array.from(Iterator.from('string'))",
"(function(){
var r = Array.from(Iterator.from('string'));
return r.length === 6 && r[0] === 's' && r[5] === 'g';
})()",
);
}
#[test]
fn array_from_string_default_no_override() {
// 无覆盖回归:默认字符串迭代器仍走码元快速路径,逐个字符产出。
assert_truthy(
"Array.from('abc') default",
"(function(){
var r = Array.from('abc');
return r.length === 3 && r[0] === 'a' && r[1] === 'b' && r[2] === 'c';
})()",
);
}
#[test]
fn for_of_string_default_no_override() {
// 无覆盖回归:for-of 字符串仍逐字符迭代(默认快速路径未被破坏)。
assert_truthy(
"for-of 'hix' default",
"(function(){
var n = 0;
for (var c of 'hix') { n++; }
return n === 3;
})()",
);
}