mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 字符串面 GetIterator 尊重用户 @@iterator 覆盖
String 臂此前对原始串与装箱串一律走码元步进、忽略用户覆盖的 @@iterator。改为按 GetMethod 语义以值自身为 receiver 读 @@iterator (原始串落 String.prototype、装箱串读自身→原型链),默认迭代器 自别名即 @@iterator 槽本身、以绑定层捕获的函数指针比较走快速 路径避免递归回本臂;覆盖则调用、receiver 为值。peek 对非字符串 原始值先装箱再读 @@iterator;Iterator.from 增 GetIteratorFlattenable 前置校验,非字符串原始值非对象 → TypeError(不装箱)。
This commit is contained in:
@@ -1156,6 +1156,12 @@ pub fn iterator_symbol_iterator<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRes
|
||||
/// 包装器带 `next` 与 `return`(用于 for-of 提前退出时的 IteratorClose 清理)。
|
||||
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() };
|
||||
// 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) {
|
||||
Ok(iterator) => NativeResult::Ok(iterator),
|
||||
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() {
|
||||
return Ok(true);
|
||||
}
|
||||
if value.is_object() {
|
||||
let obj = unsafe { &*value.as_js_object_ptr() };
|
||||
// 原始值(number/bigint/boolean/symbol)先装箱再按对象读 @@iterator
|
||||
// (如 `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 method = match vm.ordinary_get(obj, sym_iter_si, value) {
|
||||
let method = match vm.ordinary_get(obj, sym_iter_si, recv) {
|
||||
Ok(m) => m,
|
||||
Err(err) => {
|
||||
// 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。
|
||||
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) {
|
||||
return Ok(true);
|
||||
}
|
||||
@@ -1461,8 +1478,83 @@ fn builtin_iterator_default_intact<H: VmHost>(vm: &mut H, value: JsValue, method
|
||||
/// - `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() {
|
||||
return Ok(Some((value, None)));
|
||||
// 字符串面:原始串与装箱串(is_string_obj)同走本臂,GetMethod 语义。
|
||||
// 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 经原型链读到的仍是内建默认迭代器函数
|
||||
// 时才走快速路径;用户覆盖(自身属性或原型链改写/删除)走通用协议
|
||||
|
||||
@@ -301,6 +301,11 @@ pub struct BuiltinWorld {
|
||||
pub set_iterator_proto: P<JsObject>,
|
||||
/// `%StringIteratorPrototype%`:String.prototype[@@iterator] 返回的迭代器。
|
||||
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 返回的迭代器。
|
||||
pub regexp_string_iterator_proto: P<JsObject>,
|
||||
/// `%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 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 {
|
||||
object_proto,
|
||||
string_default_iterator,
|
||||
array_proto,
|
||||
function_proto,
|
||||
string_proto,
|
||||
@@ -1687,6 +1695,11 @@ impl BuiltinWorld {
|
||||
|
||||
let world = BuiltinWorld {
|
||||
object_proto,
|
||||
string_default_iterator: std::cell::Cell::new(if dirty.string {
|
||||
std::ptr::null()
|
||||
} else {
|
||||
current.string_default_iterator.get()
|
||||
}),
|
||||
array_proto,
|
||||
function_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 (),
|
||||
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),
|
||||
// 无槽时开新槽;描述符非枚举(规范 { 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;
|
||||
})()",
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user