From 9f13d107ec7fc7c5cb68e1b592b29458296a045d Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 16 Aug 2026 14:44:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Iterator=20helpers=20=E5=9F=BA=E5=BB=BA?= =?UTF-8?q?=E2=80=94=E2=80=94Symbol.dispose=20=E6=B3=A8=E5=86=8C=E4=B8=8E?= =?UTF-8?q?=20%IteratorHelperPrototype%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/iterator.rs | 105 ++++++- crates/oxide_builtins/src/object.rs | 2 + crates/oxide_kernel/src/builtin.rs | 27 +- crates/oxide_kernel/src/kernel.rs | 10 +- crates/oxide_runtime_api/src/lib.rs | 6 + crates/oxide_types/src/private_key.rs | 5 +- crates/oxide_vm/src/bindings/bind_iterator.rs | 4 +- crates/oxide_vm/src/bindings/bind_symbol.rs | 8 + crates/oxide_vm/src/bindings/mod.rs | 145 ++++++++++ .../oxide_vm/tests/builtins_iterator_tests.rs | 261 ++++++++++++++++++ 10 files changed, 567 insertions(+), 6 deletions(-) diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index 460450d4..ec7d8df7 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -1,5 +1,5 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; -use oxide_types::object::JsObject; +use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::private_key::{make_int_key, make_well_known_symbol_key}; use oxide_types::value::JsValue; @@ -14,6 +14,109 @@ pub fn iterator_constructor(vm: &mut H, _args: &[u8]) -> NativeResult NativeResult::Err(crate::error::create_type_error(vm, "Iterator is not a constructor")) } +/// `%IteratorPrototype%` 上 `constructor` 访问器的 getter:返回当前 global 上的 +/// `Iterator` 构造器。 +/// +/// 动态查 global(shape 槽查找)而非缓存指针:dirty reset 重建 global 时会换新的 +/// `Iterator` 函数对象,缓存旧指针会指向已释放对象。lookup 失败(极端:global 无 +/// `Iterator`)返回 undefined,不 panic。 +pub fn iterator_constructor_getter(vm: &mut H, _args: &[u8]) -> NativeResult { + let global = vm.session().global_object(); + let si = vm.kernel_core().perm_interner().intern("Iterator").0; + let Some(pos) = vm.kernel_core().shape_forge().lookup_position(global.shape_id(), si) else { + return NativeResult::Ok(JsValue::undefined()); + }; + NativeResult::Ok(global.get_prop_at(pos)) +} + +/// `%IteratorPrototype%` 上 `Symbol.toStringTag` 访问器的 getter:返回 `"Iterator"`。 +pub fn iterator_to_string_tag_getter(vm: &mut H, _args: &[u8]) -> NativeResult { + let sf = vm.kernel_core().perm_interner().as_ref(); + NativeResult::Ok(JsValue::perm_string(sf.string_ptr(sf.intern("Iterator").0))) +} + +/// SetterThatIgnoresPrototypeProperties 的共享实现:`%IteratorPrototype%` 的 +/// `constructor` 与 `Symbol.toStringTag` 访问器共用同一语义,仅属性键不同。 +/// +/// # 步骤 +/// 1. `this` 非对象 → TypeError(原始值直接抛,不建 own 属性)。 +/// 2. `this` 为 `%IteratorPrototype%`(home 对象)→ TypeError(模拟对 home 不可写 +/// 数据属性的严格模式赋值)。 +/// 3. `this` 无 own 指定键属性 → CreateDataPropertyOrThrow(写全可写可枚举)。 +/// 4. 有 own 属性 → 普通 Set(继承访问器时在此被调用的场景)。 +fn iterator_setter_ignore_proto_props(vm: &mut H, args: &[u8], key: u32) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + if !this_val.is_object() { + return NativeResult::Err(crate::error::create_type_error(vm, "Iterator property setter called on non-object")); + } + let home = vm.session().builtin_world().iterator_proto.as_ptr() as *mut JsObject; + if std::ptr::eq(this_val.as_js_object_ptr(), home) { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Cannot assign to read only property of Iterator prototype", + )); + } + let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let this_obj = unsafe { &mut *this_val.as_js_object_ptr() }; + if vm + .kernel_core() + .shape_forge() + .lookup_position(this_obj.shape_id(), key) + .is_none() + { + match vm.define_data_property(this_obj, key, val, PropAttributes::new(true, true, true)) { + Ok(()) => NativeResult::Ok(JsValue::undefined()), + Err(err) => NativeResult::Err(crate::error::create_type_error(vm, &err)), + } + } else { + match vm.ordinary_set(this_obj, key, val, this_val) { + Ok(()) => NativeResult::Ok(JsValue::undefined()), + Err(err) => NativeResult::Err(crate::error::create_type_error(vm, &err)), + } + } +} + +/// `constructor` 访问器的 setter(键 = "constructor")。 +pub fn iterator_constructor_setter(vm: &mut H, args: &[u8]) -> NativeResult { + let key = vm.kernel_core().perm_interner().intern("constructor").0; + iterator_setter_ignore_proto_props(vm, args, key) +} + +/// `Symbol.toStringTag` 访问器的 setter(键 = well-known symbol 9)。 +pub fn iterator_to_string_tag_setter(vm: &mut H, args: &[u8]) -> NativeResult { + iterator_setter_ignore_proto_props(vm, args, make_well_known_symbol_key(9)) +} + +/// `%IteratorPrototype%[@@dispose]`:GetMethod(this, "return"),有则调用并返回 +/// undefined。 +/// +/// # 步骤 +/// 1. `this` 非对象 → TypeError(GetMethod 的 GetV 语义要求对象)。 +/// 2. 读 `return` 方法:不可调用(undefined/null)则跳过;getter 抛错透传原值。 +/// 3. 调用 `return()` 成功/抛错均最终返回 undefined 或透传原异常。 +pub fn iterator_dispose(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + if !this_val.is_object() { + return NativeResult::Err(crate::error::create_type_error( + vm, + "%IteratorPrototype%[@@dispose] called on non-object", + )); + } + let obj = unsafe { &*this_val.as_js_object_ptr() }; + let return_si = vm.kernel_core().perm_interner().intern("return").0; + let return_fn = match vm.ordinary_get(obj, return_si, this_val) { + Ok(f) if is_callable(f) => f, + Ok(_) => JsValue::undefined(), + Err(err) => return NativeResult::Err(engine_error(vm, &err)), + }; + if !return_fn.is_undefined() { + if let Err(err) = vm.call_function_sync(return_fn, this_val, &[]) { + return NativeResult::Err(engine_error(vm, &err)); + } + } + NativeResult::Ok(JsValue::undefined()) +} + /// `%IteratorPrototype%[@@iterator]`:返回 this(迭代器对象自迭代)。 /// /// 挂在 `%IteratorPrototype%` 上,所有集合迭代器经原型链继承,保证 diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index b2e866cc..4cdffa1e 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -142,6 +142,8 @@ fn decode_symbol_key(vm: &H, key: u32) -> JsValue { 7 => world.sym_match_all.as_ptr(), 8 => world.sym_async_iterator.as_ptr(), 10 => world.sym_species.as_ptr(), + 11 => world.sym_async_dispose.as_ptr(), + 12 => world.sym_dispose.as_ptr(), _ => world.sym_to_string_tag.as_ptr(), }; return JsValue::from_js_object(ptr as *mut JsObject); diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index 124e101e..8fc56a6c 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -254,6 +254,8 @@ pub struct BuiltinWorld { pub sym_async_iterator: P, pub sym_to_string_tag: P, pub sym_species: P, + pub sym_async_dispose: P, + pub sym_dispose: P, pub temporal_object: P, pub temporal_now_object: P, pub instant_constructor: P, @@ -282,6 +284,8 @@ pub struct BuiltinWorld { pub string_iterator_proto: P, /// `%RegExpStringIteratorPrototype%`:matchAll 返回的迭代器。 pub regexp_string_iterator_proto: P, + /// `%IteratorHelperPrototype%`:Iterator helpers 结果对象的共享原型,链到 %IteratorPrototype%。 + pub iterator_helper_proto: P, pub stub_objects: Vec>, } @@ -603,12 +607,13 @@ fn wire_builtin_world_links(world: &BuiltinWorld) { // → %IteratorPrototype%(next/@@iterator 方法由绑定层安装到对应原型)。 set_proto_if_changed(&world.iterator_proto, obj_proto_val); let iterator_proto_val = JsValue::from_js_object(world.iterator_proto.as_ptr() as *mut JsObject); - let iterator_protos: [&P; 5] = [ + let iterator_protos: [&P; 6] = [ &world.array_iterator_proto, &world.map_iterator_proto, &world.set_iterator_proto, &world.string_iterator_proto, &world.regexp_string_iterator_proto, + &world.iterator_helper_proto, ]; for proto in &iterator_protos { set_proto_if_changed(proto, iterator_proto_val); @@ -733,6 +738,8 @@ impl BuiltinWorld { BuiltinId::SymAsyncIterator => &self.sym_async_iterator, BuiltinId::SymToStringTag => &self.sym_to_string_tag, BuiltinId::SymSpecies => &self.sym_species, + BuiltinId::SymAsyncDispose => &self.sym_async_dispose, + BuiltinId::SymDispose => &self.sym_dispose, BuiltinId::TemporalObject => &self.temporal_object, BuiltinId::TemporalNowObject => &self.temporal_now_object, BuiltinId::InstantConstructor => &self.instant_constructor, @@ -792,6 +799,8 @@ impl BuiltinWorld { let sym_async_iterator = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let sym_to_string_tag = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let sym_species = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + let sym_async_dispose = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + let sym_dispose = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let temporal_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let temporal_now_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let (instant_proto, instant_constructor) = make_named_pair(string_forge, shape_forge, labels, "Instant"); @@ -815,6 +824,7 @@ impl BuiltinWorld { let set_iterator_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let string_iterator_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let regexp_string_iterator_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 world = Self { object_proto, @@ -888,6 +898,8 @@ impl BuiltinWorld { sym_async_iterator, sym_to_string_tag, sym_species, + sym_async_dispose, + sym_dispose, temporal_object, temporal_now_object, instant_constructor, @@ -910,6 +922,7 @@ impl BuiltinWorld { set_iterator_proto, string_iterator_proto, regexp_string_iterator_proto, + iterator_helper_proto, stub_objects, }; wire_builtin_world_links(&world); @@ -997,6 +1010,8 @@ impl BuiltinWorld { sym_async_iterator, sym_to_string_tag, sym_species, + sym_async_dispose, + sym_dispose, ) = if dirty.symbol_family { let (symbol_proto, symbol_constructor) = make_named_pair(string_forge, shape_forge, labels, "Symbol"); ( @@ -1013,6 +1028,8 @@ impl BuiltinWorld { P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), + P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), + P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), ) } else { ( @@ -1029,6 +1046,8 @@ impl BuiltinWorld { current.sym_async_iterator.clone(), current.sym_to_string_tag.clone(), current.sym_species.clone(), + current.sym_async_dispose.clone(), + current.sym_dispose.clone(), ) }; @@ -1178,6 +1197,7 @@ impl BuiltinWorld { set_iterator_proto, string_iterator_proto, regexp_string_iterator_proto, + iterator_helper_proto, ) = if dirty.object { ( P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), @@ -1186,6 +1206,7 @@ impl BuiltinWorld { P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), + P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), ) } else { ( @@ -1195,6 +1216,7 @@ impl BuiltinWorld { current.set_iterator_proto.clone(), current.string_iterator_proto.clone(), current.regexp_string_iterator_proto.clone(), + current.iterator_helper_proto.clone(), ) }; @@ -1270,6 +1292,8 @@ impl BuiltinWorld { sym_async_iterator, sym_to_string_tag, sym_species, + sym_async_dispose, + sym_dispose, temporal_object, temporal_now_object, instant_constructor, @@ -1292,6 +1316,7 @@ impl BuiltinWorld { set_iterator_proto, string_iterator_proto, regexp_string_iterator_proto, + iterator_helper_proto, stub_objects, }; wire_builtin_world_links(&world); diff --git a/crates/oxide_kernel/src/kernel.rs b/crates/oxide_kernel/src/kernel.rs index 88e77abc..462855ca 100644 --- a/crates/oxide_kernel/src/kernel.rs +++ b/crates/oxide_kernel/src/kernel.rs @@ -225,7 +225,7 @@ pub struct KernelSession { /// 维护注意:每个新增的 `BuiltinWorld` 对象字段都必须加到这里以及 /// `KernelSession::dirty_since_snapshot()`,以便选择性重置重建正确的 /// builtin 家族。 -pub const NUM_BUILTINS: usize = 86; +pub const NUM_BUILTINS: usize = 88; /// 内置对象枚举 id,与 `BuiltinWorld` 中的存储槽一一对应。 /// @@ -320,6 +320,8 @@ pub enum BuiltinId { ZonedDateTimeProto = 82, PlainDateTimeConstructor = 83, PlainDateTimeProto = 84, + SymAsyncDispose = 86, + SymDispose = 87, } impl BuiltinId { @@ -413,6 +415,8 @@ impl BuiltinId { BuiltinId::PlainDateTimeConstructor, BuiltinId::PlainDateTimeProto, BuiltinId::SymSpecies, + BuiltinId::SymAsyncDispose, + BuiltinId::SymDispose, ]; } @@ -616,7 +620,9 @@ impl KernelSession { || gen(BuiltinId::SymMatchAll) != snap(BuiltinId::SymMatchAll) || gen(BuiltinId::SymAsyncIterator) != snap(BuiltinId::SymAsyncIterator) || gen(BuiltinId::SymToStringTag) != snap(BuiltinId::SymToStringTag) - || gen(BuiltinId::SymSpecies) != snap(BuiltinId::SymSpecies), + || gen(BuiltinId::SymSpecies) != snap(BuiltinId::SymSpecies) + || gen(BuiltinId::SymAsyncDispose) != snap(BuiltinId::SymAsyncDispose) + || gen(BuiltinId::SymDispose) != snap(BuiltinId::SymDispose), math: gen(BuiltinId::MathObject) != snap(BuiltinId::MathObject), json: gen(BuiltinId::JsonObject) != snap(BuiltinId::JsonObject), date: gen(BuiltinId::DateConstructor) != snap(BuiltinId::DateConstructor) diff --git a/crates/oxide_runtime_api/src/lib.rs b/crates/oxide_runtime_api/src/lib.rs index 7bca0665..e3ba3b0c 100644 --- a/crates/oxide_runtime_api/src/lib.rs +++ b/crates/oxide_runtime_api/src/lib.rs @@ -1044,6 +1044,10 @@ pub fn well_known_symbol_id(host: &H, ptr: *mut JsObject) -> Some(9) } else if std::ptr::eq(ptr, world.sym_species.as_ptr()) { Some(10) + } else if std::ptr::eq(ptr, world.sym_async_dispose.as_ptr()) { + Some(11) + } else if std::ptr::eq(ptr, world.sym_dispose.as_ptr()) { + Some(12) } else { None } @@ -1063,6 +1067,8 @@ pub fn well_known_symbol_name(id: u32) -> Option<&'static str> { 8 => "Symbol.asyncIterator", 9 => "Symbol.toStringTag", 10 => "Symbol.species", + 11 => "Symbol.asyncDispose", + 12 => "Symbol.dispose", _ => return None, }) } diff --git a/crates/oxide_types/src/private_key.rs b/crates/oxide_types/src/private_key.rs index 719ec312..648e86f7 100644 --- a/crates/oxide_types/src/private_key.rs +++ b/crates/oxide_types/src/private_key.rs @@ -51,7 +51,10 @@ pub const fn int_key_value(key: u32) -> u32 { pub const SYMBOL_KEY_BASE: u32 = 0xA000_0000; /// 保留给 well-known symbol 的键槽位数(位于 Symbol 键区间最低端)。 -pub const WELL_KNOWN_SYMBOL_COUNT: u32 = 11; +/// +/// 键序与 TC39 well-known symbol 表一致:0-10 为既有项,11/12 分别为 +/// `Symbol.asyncDispose` 与 `Symbol.dispose`(显式资源管理提案)。 +pub const WELL_KNOWN_SYMBOL_COUNT: u32 = 13; /// 用户 symbol 下标的保留位掩码(低 28 位)。 const SYMBOL_INDEX_MASK: u32 = 0x0FFF_FFFF; diff --git a/crates/oxide_vm/src/bindings/bind_iterator.rs b/crates/oxide_vm/src/bindings/bind_iterator.rs index 133dcba8..4f5eef93 100644 --- a/crates/oxide_vm/src/bindings/bind_iterator.rs +++ b/crates/oxide_vm/src/bindings/bind_iterator.rs @@ -6,7 +6,8 @@ use oxide_types::object::JsObject; use oxide_types::value::JsValue; use crate::bindings::{ - apply_binding_table, bind_global_value, bind_iterator_function_prototype, configure_native_constructor, + apply_binding_table, bind_global_value, bind_iterator_ctor_identity, bind_iterator_function_prototype, + configure_native_constructor, }; /// 绑定 `Iterator` 全局构造器(`prototype` = %IteratorPrototype%、`from` 方法)。 @@ -23,6 +24,7 @@ pub fn bind_iterator(core: &Arc, session: &KernelSession, global: &m oxide_builtins::iterator::iterator_constructor:: as *const (), 0, ); + bind_iterator_ctor_identity(core, &mut iterator); bind_iterator_function_prototype(core, session, &mut iterator); apply_binding_table( diff --git a/crates/oxide_vm/src/bindings/bind_symbol.rs b/crates/oxide_vm/src/bindings/bind_symbol.rs index 455c32d3..f76340f5 100644 --- a/crates/oxide_vm/src/bindings/bind_symbol.rs +++ b/crates/oxide_vm/src/bindings/bind_symbol.rs @@ -90,6 +90,14 @@ pub fn bind_symbol(core: &Arc, session: &KernelSession, global: &mut "species", JsValue::from_js_object(session.builtin_world().sym_species.as_ptr() as *mut JsObject), ), + ( + "asyncDispose", + JsValue::from_js_object(session.builtin_world().sym_async_dispose.as_ptr() as *mut JsObject), + ), + ( + "dispose", + JsValue::from_js_object(session.builtin_world().sym_dispose.as_ptr() as *mut JsObject), + ), ] { bind_well_known_symbol(core, ctor, name, val); } diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 07255f9d..b72a10c0 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -165,6 +165,98 @@ pub(crate) fn bind_accessor_getter( proto.bump_generation(); } +/// 在原型上绑定一个原生访问器属性(getter + setter 成对),键可为字符串 intern 键 +/// 或 well-known symbol 键(shape 键直接传入,不要求字符串 intern)。 +/// +/// # 步骤 +/// 1. 构造 getter 函数对象(name = `getter_name`,length 0)与 setter 函数对象 +/// (name = `setter_name`,length 1)。 +/// 2. 为键开 shape 槽位并写入访问器 meta(enumerable=false, configurable=true)。 +/// +/// # 注意事项 +/// getter/setter 函数对象经 `Box::into_raw` 持有,与 `bind_accessor_getter` 的 +/// 方法 wrapper 同一生命周期约定(内置对象在 session 生命周期内不被回收)。 +#[expect(clippy::too_many_arguments)] +pub(crate) fn bind_accessor_getset( + core: &Arc, session: &KernelSession, proto: &mut JsObject, key: u32, getter_name: &str, + setter_name: &str, getter_fn: *const (), setter_fn: *const (), +) { + let shape_forge = core.shape_forge().as_ref(); + let string_forge = core.perm_interner().as_ref(); + let fn_proto_val = JsValue::from_js_object(session.builtin_world().function_proto.as_ptr() as *mut JsObject); + + let mut getter = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); + getter.set_function(true); + // SAFETY: getter_fn 是转成 *const () 的 NativeFn 函数项指针。 + getter.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(getter_fn) })); + getter.set_native_arg_count(0); + // 给函数对象开 name/length 槽位(描述符均 { writable:false, enumerable:false, configurable:true })。 + let si_name = string_forge.intern("name").0; + let name_shape = shape_forge.make_shape(getter.shape_id(), si_name); + getter.set_shape_id(name_shape); + getter + .ensure_hash_props() + .push(JsValue::perm_string(string_forge.string_ptr(string_forge.intern(getter_name).0))); + let name_pos = getter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + getter.set_data_meta(name_pos, PropAttributes::new(false, false, true)); + let si_length = string_forge.intern("length").0; + let length_shape = shape_forge.make_shape(getter.shape_id(), si_length); + getter.set_shape_id(length_shape); + getter.ensure_hash_props().push(JsValue::int(0)); + let length_pos = getter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + getter.set_data_meta(length_pos, PropAttributes::new(false, false, true)); + let getter_val = JsValue::from_js_object(Box::into_raw(getter)); + + let mut setter = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); + setter.set_function(true); + // SAFETY: setter_fn 是转成 *const () 的 NativeFn 函数项指针。 + setter.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(setter_fn) })); + setter.set_native_arg_count(1); + let name_shape = shape_forge.make_shape(setter.shape_id(), si_name); + setter.set_shape_id(name_shape); + setter + .ensure_hash_props() + .push(JsValue::perm_string(string_forge.string_ptr(string_forge.intern(setter_name).0))); + let name_pos = setter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + setter.set_data_meta(name_pos, PropAttributes::new(false, false, true)); + let length_shape = shape_forge.make_shape(setter.shape_id(), si_length); + setter.set_shape_id(length_shape); + setter.ensure_hash_props().push(JsValue::int(1)); + let length_pos = setter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + setter.set_data_meta(length_pos, PropAttributes::new(false, false, true)); + let setter_val = JsValue::from_js_object(Box::into_raw(setter)); + + // 访问器属性槽:enumerable=false、configurable=true(get/set 函数对象已持有)。 + let new_shape = shape_forge.make_shape(proto.shape_id(), key); + proto.set_shape_id(new_shape); + let pos = proto.push_prop(JsValue::undefined()); + proto.set_accessor_meta(pos, getter_val, setter_val, PropAttributes::new(true, false, true)); + proto.bump_generation(); +} + +/// 为 `Iterator` 构造器绑定 `length`=0 与 `name`="Iterator" 属性(规范描述符均 +/// { writable:false, enumerable:false, configurable:true })。 +/// +/// `bind_iterator`(初始化路径)与 `bind_iterator_global`(dirty reset 路径)共用, +/// 防两处漂移导致 dirty reset 后 `Iterator.length`/`Iterator.name` 消失。 +pub(crate) fn bind_iterator_ctor_identity(core: &Arc, ctor: &mut JsObject) { + let shape_forge = core.shape_forge().as_ref(); + let string_forge = core.perm_interner().as_ref(); + let si_length = string_forge.intern("length").0; + let length_shape = shape_forge.make_shape(ctor.shape_id(), si_length); + ctor.set_shape_id(length_shape); + ctor.ensure_hash_props().push(JsValue::int(0)); + let length_pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true)); + let si_name = string_forge.intern("name").0; + let name_shape = shape_forge.make_shape(ctor.shape_id(), si_name); + ctor.set_shape_id(name_shape); + ctor.ensure_hash_props() + .push(JsValue::perm_string(string_forge.string_ptr(string_forge.intern("Iterator").0))); + let name_pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + ctor.set_data_meta(name_pos, PropAttributes::new(false, false, true)); +} + /// 把原型上 `source` 属性已绑定的函数值复制到 `alias` 名下(共享同一函数对象)。 /// /// 用于规范要求的方法别名(如 Set 的 `keys`/`@@iterator` 与 `values` 同一函数对象)。 @@ -294,6 +386,58 @@ fn bind_iterator_protos(core: &Arc, session: &KernelSession) { ); } + // %IteratorPrototype% 的 constructor / @@toStringTag 访问器:getter 动态读 + // global(dirty reset 后 global 重建,不能缓存指针),setter 实现 + // SetterThatIgnoresPrototypeProperties(home 赋值抛 TypeError)。 + let si_constructor = core.perm_interner().intern("constructor").0; + if core + .shape_forge() + .lookup_position(iter_proto.shape_id(), si_constructor) + .is_none() + { + bind_accessor_getset( + core, + session, + iter_proto, + si_constructor, + "get constructor", + "set constructor", + oxide_builtins::iterator::iterator_constructor_getter:: as *const (), + oxide_builtins::iterator::iterator_constructor_setter:: as *const (), + ); + } + let sym_to_string_tag = oxide_types::private_key::make_well_known_symbol_key(9); + if core + .shape_forge() + .lookup_position(iter_proto.shape_id(), sym_to_string_tag) + .is_none() + { + bind_accessor_getset( + core, + session, + iter_proto, + sym_to_string_tag, + "get [Symbol.toStringTag]", + "set [Symbol.toStringTag]", + oxide_builtins::iterator::iterator_to_string_tag_getter:: as *const (), + oxide_builtins::iterator::iterator_to_string_tag_setter:: as *const (), + ); + } + + // %IteratorPrototype% 的 @@dispose:显式资源管理下用 return 关闭迭代器。 + let sym_dispose = oxide_types::private_key::make_well_known_symbol_key(12); + if core.shape_forge().lookup_position(iter_proto.shape_id(), sym_dispose).is_none() { + bind_well_known_method( + world, + core, + iter_proto, + 12, + "[Symbol.dispose]", + oxide_builtins::iterator::iterator_dispose:: as *const (), + 0, + ); + } + // %ArrayIteratorPrototype% 服务 Array/TA 两族;Map/Set 共用按 `__mode__` // 分发的实现;%RegExpStringIteratorPrototype% 供 matchAll。 bind_iterator_proto_next( @@ -488,6 +632,7 @@ fn bind_iterator_global(core: &Arc, session: &KernelSession, global: oxide_builtins::iterator::iterator_constructor:: as *const (), 0, ); + bind_iterator_ctor_identity(core, &mut iterator); bind_iterator_function_prototype(core, session, &mut iterator); apply_binding_table( session.builtin_world(), diff --git a/crates/oxide_vm/tests/builtins_iterator_tests.rs b/crates/oxide_vm/tests/builtins_iterator_tests.rs index 4d17a4a8..9c38a02e 100644 --- a/crates/oxide_vm/tests/builtins_iterator_tests.rs +++ b/crates/oxide_vm/tests/builtins_iterator_tests.rs @@ -1,4 +1,5 @@ use oxide_compiler::compiler::Compiler; +use oxide_types::object::JsObject; use oxide_types::value::JsValue; use oxide_vm::vm::Vm; @@ -289,3 +290,263 @@ fn iterator_proto_next_consumption_unchanged() { let done = eval(&mut vm, "var it = new Uint8Array([1]).values(); it.next(); it.next().done").unwrap(); assert!(done.as_bool()); } + +#[test] +fn iterator_function_name_length() { + let mut vm = Vm::new(); + let bool_cases = [ + ("Iterator.name === 'Iterator'", true), + ("Iterator.length === 0", true), + ("Object.getOwnPropertyDescriptor(Iterator, 'name').writable === false", true), + ("Object.getOwnPropertyDescriptor(Iterator, 'name').enumerable === false", true), + ("Object.getOwnPropertyDescriptor(Iterator, 'name').configurable === true", true), + ("Object.getOwnPropertyDescriptor(Iterator, 'length').writable === false", true), + ("Object.getOwnPropertyDescriptor(Iterator, 'length').enumerable === false", true), + ("Object.getOwnPropertyDescriptor(Iterator, 'length').configurable === true", true), + ]; + for (src, expected) in bool_cases { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(result.as_bool(), expected, "for {}", src); + } +} + +#[test] +fn iterator_prototype_constructor_accessor() { + let mut vm = Vm::new(); + let bool_cases = [ + // getter 动态读 global 上的 Iterator 构造器。 + ("Iterator.prototype.constructor === Iterator", true), + ( + "typeof Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor').get === 'function'", + true, + ), + ( + "typeof Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor').set === 'function'", + true, + ), + ( + "Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor').enumerable === false", + true, + ), + ( + "Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor').configurable === true", + true, + ), + // getter 无参调用(this=undefined)仍返回 Iterator。 + ( + "Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor').get.call() === Iterator", + true, + ), + ]; + for (src, expected) in bool_cases { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(result.as_bool(), expected, "for {}", src); + } +} + +#[test] +fn iterator_prototype_constructor_setter_semantics() { + let mut vm = Vm::new(); + // home 对象赋值与原始值 this 抛 TypeError(SetterThatIgnoresPrototypeProperties)。 + let throws = [ + ( + "try { Iterator.prototype.constructor = 'x'; false } catch (e) { e instanceof TypeError }", + true, + ), + ( + "try { var d = Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor'); \ + d.set.call(undefined, 'x'); false } catch (e) { e instanceof TypeError }", + true, + ), + ( + "try { var d = Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor'); \ + d.set.call(null, 'x'); false } catch (e) { e instanceof TypeError }", + true, + ), + ( + "try { var d = Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor'); \ + d.set.call(true, 'x'); false } catch (e) { e instanceof TypeError }", + true, + ), + ]; + for (src, expected) in throws { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(result.as_bool(), expected, "for {}", src); + } + // 非 home 对象:无 own 属性建 own,有 own 属性走 Set 覆盖。 + let ok_cases = [ + ( + "var o = {}; var d = Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor'); \ + d.set.call(o, 42); o.constructor === 42 && o.hasOwnProperty('constructor')", + true, + ), + ( + "var o = { constructor: 1 }; var d = Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor'); \ + d.set.call(o, 2); o.constructor === 2", + true, + ), + // home 不受污染。 + ("Iterator.prototype.constructor === Iterator", true), + ]; + for (src, expected) in ok_cases { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(result.as_bool(), expected, "for {}", src); + } +} + +#[test] +fn iterator_prototype_to_string_tag_accessor() { + let mut vm = Vm::new(); + let bool_cases = [ + ("Iterator.prototype[Symbol.toStringTag] === 'Iterator'", true), + ( + "typeof Object.getOwnPropertyDescriptor(Iterator.prototype, Symbol.toStringTag).get === 'function'", + true, + ), + ( + "typeof Object.getOwnPropertyDescriptor(Iterator.prototype, Symbol.toStringTag).set === 'function'", + true, + ), + ( + "Object.getOwnPropertyDescriptor(Iterator.prototype, Symbol.toStringTag).enumerable === false", + true, + ), + ( + "Object.getOwnPropertyDescriptor(Iterator.prototype, Symbol.toStringTag).configurable === true", + true, + ), + ( + "Object.getOwnPropertyDescriptor(Iterator.prototype, Symbol.toStringTag).get.call() === 'Iterator'", + true, + ), + // home 赋值抛 TypeError。 + ( + "try { Iterator.prototype[Symbol.toStringTag] = 'x'; false } catch (e) { e instanceof TypeError }", + true, + ), + // 普通对象 setter 建 own Symbol.toStringTag。 + ( + "var o = {}; var d = Object.getOwnPropertyDescriptor(Iterator.prototype, Symbol.toStringTag); \ + d.set.call(o, 'tag'); o[Symbol.toStringTag] === 'tag'", + true, + ), + // 原型继承场景:非 home 子对象赋值落到自身 own 属性(不污染祖先)。 + ( + "var p = Object.create(Iterator.prototype); p[Symbol.toStringTag] = 'sub'; \ + p[Symbol.toStringTag] === 'sub' && Iterator.prototype[Symbol.toStringTag] === 'Iterator'", + true, + ), + ]; + for (src, expected) in bool_cases { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(result.as_bool(), expected, "for {}", src); + } +} + +#[test] +fn symbol_dispose_and_async_dispose_registered() { + let mut vm = Vm::new(); + // well-known symbol 以空对象表示,typeof 为 object;两符号互不相同。 + let bool_cases = [ + ("typeof Symbol.dispose === 'object'", true), + ("typeof Symbol.asyncDispose === 'object'", true), + ("Symbol.dispose !== Symbol.asyncDispose", true), + ("Symbol.dispose !== Symbol.iterator", true), + // 键可作属性名使用(well-known 键映射一致)。 + ("({ [Symbol.dispose]: 1 })[Symbol.dispose] === 1", true), + // getOwnPropertySymbols 能反解出同一符号对象。 + ("Object.getOwnPropertySymbols({ [Symbol.dispose]: 1 })[0] === Symbol.dispose", true), + ]; + for (src, expected) in bool_cases { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(result.as_bool(), expected, "for {}", src); + } + // 描述性名称经 String 构造器反射(well_known_symbol_name 表)。 + let desc = eval(&mut vm, "String(Symbol.dispose)").unwrap(); + assert_eq!(to_str(&vm, desc), "Symbol(Symbol.dispose)"); +} + +#[test] +fn iterator_prototype_symbol_dispose() { + let mut vm = Vm::new(); + // @@dispose 调用 this 的 return 方法并返回 undefined。 + let bool_cases = [ + ("typeof Iterator.prototype[Symbol.dispose] === 'function'", true), + ("Iterator.prototype[Symbol.dispose].length === 0", true), + ("Iterator.prototype[Symbol.dispose].name === '[Symbol.dispose]'", true), + ( + "var called = false; var it = { return: function() { called = true; return { done: true }; } }; \ + var r = Iterator.prototype[Symbol.dispose].call(it); r === undefined && called === true", + true, + ), + // 无 return 方法:GetMethod 返回 undefined,跳过调用。 + ("Iterator.prototype[Symbol.dispose].call({ next: function() {} }) === undefined", true), + // return 返回任意值不影响 @@dispose 的 undefined 返回值。 + ( + "var it = { return: function() { return 42; } }; \ + Iterator.prototype[Symbol.dispose].call(it) === undefined", + true, + ), + ]; + for (src, expected) in bool_cases { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(result.as_bool(), expected, "for {}", src); + } + // return 抛错经 @@dispose 透传(原值,不二次包装)。 + let thrown = eval( + &mut vm, + "try { var it = { return: function() { throw new RangeError('boom'); } }; \ + Iterator.prototype[Symbol.dispose].call(it); false } catch (e) { e instanceof RangeError }", + ) + .unwrap(); + assert!(thrown.as_bool()); +} + +#[test] +fn iterator_helper_prototype_links_to_iterator_proto() { + let vm = Vm::new(); + // %IteratorHelperPrototype% 已建且链到 %IteratorPrototype%(链: + // helper → %IteratorPrototype% → Object.prototype)。 + let helper_ptr = vm.session().builtin_world().iterator_helper_proto.as_ptr() as *mut JsObject; + let helper_proto_val = unsafe { &*helper_ptr }.proto(); + let iter_ptr = vm.session().builtin_world().iterator_proto.as_ptr() as *mut JsObject; + assert!(helper_proto_val.is_object()); + assert!(std::ptr::eq(helper_proto_val.as_js_object_ptr(), iter_ptr)); +} + +#[test] +fn iterator_prototype_rebound_after_full_reset() { + let mut vm = Vm::new(); + // 用户改写 %IteratorPrototype%:object 家族世代递增(global 未动)。 + let old_iter_proto = vm.session().builtin_world().iterator_proto.as_ptr(); + unsafe { &mut *(old_iter_proto as *mut JsObject) }.bump_generation(); + + vm.full_reset(); + + // %IteratorPrototype% 重建后访问器 / @@dispose 重绑正确,构造器 identity 保留。 + let cases = [ + ("Iterator.prototype.constructor === Iterator", true), + ("Iterator.prototype[Symbol.toStringTag] === 'Iterator'", true), + ("typeof Iterator.prototype[Symbol.dispose] === 'function'", true), + ("Iterator.name === 'Iterator'", true), + ("Iterator.length === 0", true), + ("typeof Symbol.dispose === 'object'", true), + ("typeof Symbol.asyncDispose === 'object'", true), + ]; + for (src, expected) in cases { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(result.as_bool(), expected, "for {}", src); + } + // helper 原型随 object 家族重建并链到新 %IteratorPrototype%。 + let helper_ptr = vm.session().builtin_world().iterator_helper_proto.as_ptr() as *mut JsObject; + let helper_proto_val = unsafe { &*helper_ptr }.proto(); + let new_iter_ptr = vm.session().builtin_world().iterator_proto.as_ptr() as *mut JsObject; + assert!(std::ptr::eq(helper_proto_val.as_js_object_ptr(), new_iter_ptr)); + // 迭代器家族整体功能完好。 + let r = eval( + &mut vm, + "Iterator.prototype[Symbol.dispose].call({return: function(){return {done:true}}})", + ) + .unwrap(); + assert!(r.is_undefined()); +}