diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index 93a54358..07637c52 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -1156,6 +1156,12 @@ pub fn iterator_symbol_iterator(vm: &mut H, args: &[u8]) -> NativeRes /// 包装器带 `next` 与 `return`(用于 for-of 提前退出时的 IteratorClose 清理)。 pub fn iterator_from(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(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(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(vm: &mut H, value: JsValue, method /// - `Ok(None)`:不可迭代; /// - `Err`:`@@iterator` 或 `next` getter 抛错,透传原异常值。 fn get_iterator(vm: &mut H, value: JsValue) -> Result)>, 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 经原型链读到的仍是内建默认迭代器函数 // 时才走快速路径;用户覆盖(自身属性或原型链改写/删除)走通用协议 diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index ce488dcf..ed399759 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -301,6 +301,11 @@ pub struct BuiltinWorld { pub set_iterator_proto: P, /// `%StringIteratorPrototype%`:String.prototype[@@iterator] 返回的迭代器。 pub string_iterator_proto: P, + /// String.prototype[@@iterator] 默认迭代器函数对象指针(绑定层捕获,原始值指针、 + /// 所有权归 wrapper 释放表)。String 臂覆盖判定以此做指针比较:默认迭代器即 + /// @@iterator 槽本身(自别名),集合式锚点槽比较不可复用,须独立存指针。 + /// `Cell` 供绑定层经 `&Arc` 共享引用写入。 + pub string_default_iterator: std::cell::Cell<*const JsObject>, /// `%RegExpStringIteratorPrototype%`:matchAll 返回的迭代器。 pub regexp_string_iterator_proto: P, /// `%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, diff --git a/crates/oxide_vm/src/bindings/bind_string.rs b/crates/oxide_vm/src/bindings/bind_string.rs index 6c556d90..b3d5dd86 100644 --- a/crates/oxide_vm/src/bindings/bind_string.rs +++ b/crates/oxide_vm/src/bindings/bind_string.rs @@ -72,6 +72,18 @@ pub fn bind_string(core: &Arc, session: &KernelSession, global: &mut oxide_builtins::string::string_symbol_iterator:: 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 })。 diff --git a/crates/oxide_vm/tests/iterator_from_primitives_tests.rs b/crates/oxide_vm/tests/iterator_from_primitives_tests.rs new file mode 100644 index 00000000..7117caec --- /dev/null +++ b/crates/oxide_vm/tests/iterator_from_primitives_tests.rs @@ -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 { + 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; + })()", + ); +}