From 7c2e9ec1ff8c6adedda6f2267f47b16f0f1786a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Sun, 9 Aug 2026 16:09:49 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20symbol=20=E4=BD=9C=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=E9=94=AE=E7=8B=AC=E7=AB=8B=E7=BC=96=E7=A0=81=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E5=A1=8C=E7=BC=A9=E4=B8=BA=E7=A9=BA=E4=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/iterator.rs | 5 +- crates/oxide_builtins/src/object.rs | 103 ++++++++++++++++- crates/oxide_kernel/src/builtin.rs | 45 ++++++-- crates/oxide_types/src/private_key.rs | 104 +++++++++++++++++- crates/oxide_vm/src/bindings/bind_map.rs | 8 +- crates/oxide_vm/src/bindings/bind_object.rs | 1 + crates/oxide_vm/src/bindings/bind_regexp.rs | 63 ++++++++--- crates/oxide_vm/src/bindings/bind_set.rs | 9 +- .../oxide_vm/src/bindings/bind_typed_array.rs | 6 +- crates/oxide_vm/src/bindings/mod.rs | 42 +++++++ crates/oxide_vm/src/vm.rs | 38 ++++--- crates/oxide_vm/src/vm_dispatch_misc.rs | 4 +- .../oxide_vm/tests/builtins_symbol_tests.rs | 78 +++++++++++++ .../oxide_vm/tests/function_length_tests.rs | 4 +- 14 files changed, 453 insertions(+), 57 deletions(-) diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index fc126fb..77b061c 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -1,5 +1,6 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::JsObject; +use oxide_types::private_key::make_well_known_symbol_key; use oxide_types::value::JsValue; use oxide_runtime_api::{NativeResult, VmHost}; @@ -167,7 +168,7 @@ pub(crate) fn peek_iterator_method(vm: &mut H, value: JsValue) -> Res } if value.is_object() { let obj = unsafe { &*value.as_js_object_ptr() }; - let sym_iter_si = vm.kernel_core().perm_interner().intern("@@iterator").0; + let sym_iter_si = make_well_known_symbol_key(0); let method = match vm.ordinary_get(obj, sym_iter_si, value) { Ok(m) => m, Err(err) => { @@ -205,7 +206,7 @@ fn get_iterator(vm: &mut H, value: JsValue) -> Result if value.is_object() { let obj = unsafe { &*value.as_js_object_ptr() }; // 迭代协议:GetIterator 先取 value[Symbol.iterator] 并调用。 - let sym_iter_si = vm.kernel_core().perm_interner().intern("@@iterator").0; + let sym_iter_si = make_well_known_symbol_key(0); let method = match vm.ordinary_get(obj, sym_iter_si, value) { Ok(m) => m, Err(err) => { diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 1c70acd..bc405a8 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -1,7 +1,9 @@ use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; use oxide_kernel::string_forge::PermInterner; use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry}; -use oxide_types::private_key::is_private_name_key; +use oxide_types::private_key::{ + is_private_name_key, is_symbol_key, symbol_index_from_key, well_known_symbol_id_from_key, +}; use oxide_types::value::JsValue; use oxide_runtime_api::{NativeResult, VmHost}; @@ -27,7 +29,11 @@ pub fn walk_own_keys(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> { } if let Some(shape) = vm.kernel_core().shape_forge().get_shape(id) { cursor = shape.parent; - if shape.property_name != u32::MAX && !is_private_name_key(shape.property_name) { + // Symbol 键/私有名键非字符串属性名,排除在字符串枚举之外。 + if shape.property_name != u32::MAX + && !is_symbol_key(shape.property_name) + && !is_private_name_key(shape.property_name) + { shape_ids.push(id); } } else { @@ -36,7 +42,10 @@ pub fn walk_own_keys(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> { } for id in shape_ids.iter().rev() { if let Some(shape) = vm.kernel_core().shape_forge().get_shape(*id) { - if shape.property_name != 0 && !is_private_name_key(shape.property_name) { + if shape.property_name != 0 + && !is_symbol_key(shape.property_name) + && !is_private_name_key(shape.property_name) + { keys.push((shape.property_name, pos)); } } @@ -55,6 +64,92 @@ pub fn walk_own_keys(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> { keys } +/// 收集对象自身全部 Symbol 键(shape 链),按键序排列(根→叶,即插入序)。 +/// 与 [`walk_own_keys`] 互补:只返回 Symbol 键,供 `getOwnPropertySymbols` 使用。 +fn walk_own_symbol_keys(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> { + let mut keys: Vec<(u32, u32)> = Vec::new(); + let shape_id = obj.shape_id(); + let mut pos: u32 = 0; + let mut shape_ids = Vec::new(); + let mut cursor = Some(shape_id); + while let Some(id) = cursor { + if id == EMPTY_SHAPE_ID { + break; + } + if let Some(shape) = vm.kernel_core().shape_forge().get_shape(id) { + cursor = shape.parent; + if shape.property_name != u32::MAX && is_symbol_key(shape.property_name) { + shape_ids.push(id); + } + } else { + break; + } + } + for id in shape_ids.iter().rev() { + if let Some(shape) = vm.kernel_core().shape_forge().get_shape(*id) { + if shape.property_name != 0 && is_symbol_key(shape.property_name) { + keys.push((shape.property_name, pos)); + } + } + pos += 1; + } + keys +} + +/// 把 Symbol 键反解为对应的 Symbol 值:well-known 键还原为内置 symbol 对象, +/// 用户 symbol 键还原为 `JsValue::symbol` 值。 +fn decode_symbol_key(vm: &H, key: u32) -> JsValue { + if let Some(id) = well_known_symbol_id_from_key(key) { + let world = vm.session().builtin_world(); + let ptr = match id { + 0 => world.sym_iterator.as_ptr(), + 1 => world.sym_match.as_ptr(), + 2 => world.sym_replace.as_ptr(), + 3 => world.sym_search.as_ptr(), + 4 => world.sym_split.as_ptr(), + 5 => world.sym_to_primitive.as_ptr(), + 6 => world.sym_has_instance.as_ptr(), + _ => world.sym_match_all.as_ptr(), + }; + return JsValue::from_js_object(ptr as *mut JsObject); + } + JsValue::symbol(symbol_index_from_key(key)) +} + +/// `Object.getOwnPropertySymbols(obj)`:返回全部自身 Symbol 键数组。 +pub fn object_get_own_property_symbols(vm: &mut H, args: &[u8]) -> NativeResult { + let obj_ptr = match require_obj_arg(vm, args, "getOwnPropertySymbols") { + Ok(ptr) => ptr, + Err(err) => return NativeResult::Err(err), + }; + + let symbols: Vec = { + let obj = unsafe { &*obj_ptr }; + walk_own_symbol_keys(vm, obj) + .iter() + .map(|(key, _)| decode_symbol_key(vm, *key)) + .collect() + }; + + let n = symbols.len(); + let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject; + let arr = vm.alloc_object(JsObject::new_array( + EMPTY_SHAPE_ID, + JsValue::from_js_object(array_proto), + n, + vm.epoch().bump(), + )); + for (i, v) in symbols.iter().enumerate() { + unsafe { + (*arr).set_prop_at(i, *v); + } + } + unsafe { + (*arr).set_prop_count(n); + } + NativeResult::Ok(JsValue::from_js_object(arr)) +} + /// 字符串键是否为数组下标("0"~"4294967294",无前导零)。 fn array_index_of(vm: &H, key_si: u32) -> Option { let key = vm.kernel_core().perm_interner().lookup(key_si)?; @@ -516,7 +611,7 @@ pub fn object_define_property(vm: &mut H, args: &[u8]) -> NativeResul if obj_ptr.is_null() { return NativeResult::Err(crate::error::create_type_error(vm, "Object.defineProperty called on non-object")); } - // well-known symbol 等特殊键统一走 property_key_si(映射到 @@iterator 等别名), + // well-known symbol 等特殊键统一走 property_key_si(映射到各自的 Symbol 键), // 保证与计算属性访问、Reflect.defineProperty 等读键路径一致。 let si = vm.property_key_si(vm.reg(args[2])); diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index ac7eb19..fe4fa25 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -1,5 +1,6 @@ use oxide_types::mem::P; use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; +use oxide_types::private_key::make_well_known_symbol_key; use oxide_types::value::JsValue; use crate::kernel::{BuiltinDirtySet, BuiltinId}; @@ -56,6 +57,7 @@ pub struct ObjectMethods { pub is_sealed: *const (), pub is_extensible: *const (), pub get_own_property_names: *const (), + pub get_own_property_symbols: *const (), pub define_properties: *const (), pub from_entries: *const (), pub get_prototype_of: *const (), @@ -552,7 +554,7 @@ fn wire_builtin_world_links(world: &BuiltinWorld) { } impl BuiltinWorld { - fn fn_proto_val(&self) -> JsValue { + pub fn fn_proto_val(&self) -> JsValue { JsValue::from_js_object(self.function_proto.as_ptr() as *mut JsObject) } @@ -1007,6 +1009,7 @@ impl BuiltinWorld { ("isSealed", methods.is_sealed, 1), ("isExtensible", methods.is_extensible, 1), ("getOwnPropertyNames", methods.get_own_property_names, 1), + ("getOwnPropertySymbols", methods.get_own_property_symbols, 1), ("defineProperties", methods.define_properties, 2), ("fromEntries", methods.from_entries, 1), ("getPrototypeOf", methods.get_prototype_of, 1), @@ -1085,13 +1088,21 @@ impl BuiltinWorld { ("values", methods.values, 0), ); - let si = string_forge.intern("@@iterator").0; + let iterator_key = make_well_known_symbol_key(0); let raw = methods.values; // SAFETY: methods.values 是 VM 绑定层传入的 NativeFn 函数项。 let func_ptr = unsafe { NativeFnPtr::from_raw(raw) }; - let _ = - Self::bind_method_static(proto, shape_forge, string_forge, "@@iterator", func_ptr, 0, self.fn_proto_val()); - debug_assert!(shape_forge.lookup_position(proto.shape_id(), si).is_some()); + let _ = Self::bind_method_key_static( + proto, + shape_forge, + string_forge, + iterator_key, + "@@iterator", + func_ptr, + 0, + self.fn_proto_val(), + ); + debug_assert!(shape_forge.lookup_position(proto.shape_id(), iterator_key).is_some()); } /// 把 Error 家族方法安装到 Error 及各子类型构造器与原型上。 @@ -1254,6 +1265,26 @@ impl BuiltinWorld { native_fn_ptr: NativeFnPtr, arg_count: u8, wrapper_proto: JsValue, ) -> Result<(), String> { let si = string_forge.intern(method_name).0; + Self::bind_method_key_static( + proto, + shape_forge, + string_forge, + si, + method_name, + native_fn_ptr, + arg_count, + wrapper_proto, + ) + } + + /// 按指定属性键安装方法 wrapper(键不要求字符串 intern,well-known symbol 键用此路径)。 + /// + /// `method_name` 只用于 wrapper 的 `name` 属性;`key` 是属性的实际存储键。 + #[expect(clippy::too_many_arguments)] + pub fn bind_method_key_static( + proto: &mut JsObject, shape_forge: &ShapeForge, string_forge: &PermInterner, key: u32, method_name: &str, + native_fn_ptr: NativeFnPtr, arg_count: u8, wrapper_proto: JsValue, + ) -> Result<(), String> { let wrapper_proto_ptr = if wrapper_proto.is_object() { wrapper_proto.as_js_object_ptr() } else { @@ -1282,11 +1313,11 @@ impl BuiltinWorld { wrapper.set_shape_id(name_shape); wrapper .ensure_hash_props() - .push(JsValue::perm_string(string_forge.string_ptr(si))); + .push(JsValue::perm_string(string_forge.string_ptr(string_forge.intern(method_name).0))); let name_pos = wrapper.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); wrapper.set_data_meta(name_pos, oxide_types::object::PropAttributes::new(false, false, true)); let wrapper_val = JsValue::from_js_object(Box::into_raw(wrapper)); - let new_shape = shape_forge.make_shape(proto.shape_id(), si); + let new_shape = shape_forge.make_shape(proto.shape_id(), key); proto.set_shape_id(new_shape); proto.ensure_hash_props().push(wrapper_val); // 内置原型方法按 ES 规范非枚举;否则会泄漏进 for-in 枚举 diff --git a/crates/oxide_types/src/private_key.rs b/crates/oxide_types/src/private_key.rs index 94e3b91..0e2f85d 100644 --- a/crates/oxide_types/src/private_key.rs +++ b/crates/oxide_types/src/private_key.rs @@ -1,14 +1,27 @@ -//! 私有名(private name)键编码。 +//! 私有名(private name)与 Symbol 属性键编码。 //! //! ECMAScript 私有字段(`#x`)与 `Symbol` 使用全局注册符号以外的独立命名空间。 -//! 本模块用 `u32` 键的高半区(`>= 0x8000_0000`)表示私有名,与普通属性名区分, -//! 避免与用户可见的字符串键冲突。 +//! 本模块用 `u32` 键的高半区表示它们,与普通属性名区分,避免与用户可见的字符串键冲突: +//! - 私有名键:`[PRIVATE_NAME_BASE, SYMBOL_KEY_BASE)`,`make_private_name_id` 生成; +//! - Symbol 键:`>= SYMBOL_KEY_BASE`,其中最低 `WELL_KNOWN_SYMBOL_COUNT` 个槽位 +//! 保留给 well-known symbol,其余编码用户 symbol 的 VM 内下标。 +//! +//! Symbol 键不被字符串 interner 收录,枚举路径须按 `is_symbol_key` 显式跳过。 /// 私有名键区间的起始值。 /// /// 键值 `>= PRIVATE_NAME_BASE` 一律视为私有名。 pub const PRIVATE_NAME_BASE: u32 = 0x8000_0000; +/// Symbol 键区间的起始值,避开私有名区间。 +pub const SYMBOL_KEY_BASE: u32 = 0xA000_0000; + +/// 保留给 well-known symbol 的键槽位数(位于 Symbol 键区间最低端)。 +pub const WELL_KNOWN_SYMBOL_COUNT: u32 = 8; + +/// 用户 symbol 下标的保留位掩码(低 28 位)。 +const SYMBOL_INDEX_MASK: u32 = 0x0FFF_FFFF; + /// 判断键是否为私有名键(即位于高半区)。 #[inline] pub const fn is_private_name_key(key: u32) -> bool { @@ -24,6 +37,46 @@ pub const fn make_private_name_id(local_id: u32) -> u32 { PRIVATE_NAME_BASE | (local_id & !PRIVATE_NAME_BASE) } +/// 判断键是否为 Symbol 键(位于 Symbol 键区间)。 +/// +/// 私有名键与 Symbol 键互不重叠:私有名经 `make_private_name_id` 生成, +/// 其 `local_id` 来自字节码局部序号,远小于 `SYMBOL_KEY_BASE` 的偏移量。 +#[inline] +pub const fn is_symbol_key(key: u32) -> bool { + key >= SYMBOL_KEY_BASE +} + +/// 把用户 symbol 的表下标编码为属性键。 +/// +/// 键 = Symbol 区间起点 + well-known 预留槽 + 下标(截断到 28 位防加法回绕)。 +#[inline] +pub const fn make_symbol_key(idx: u32) -> u32 { + SYMBOL_KEY_BASE + WELL_KNOWN_SYMBOL_COUNT + (idx & SYMBOL_INDEX_MASK) +} + +/// 从用户 symbol 键反解表下标(与 [`make_symbol_key`] 互逆)。 +#[inline] +pub const fn symbol_index_from_key(key: u32) -> u32 { + (key - SYMBOL_KEY_BASE - WELL_KNOWN_SYMBOL_COUNT) & SYMBOL_INDEX_MASK +} + +/// 把 well-known symbol 序号编码为属性键(Symbol 键区间最低槽位)。 +#[inline] +pub const fn make_well_known_symbol_key(id: u32) -> u32 { + SYMBOL_KEY_BASE + id +} + +/// 若键是 well-known symbol 键,返回其序号;否则返回 `None`。 +#[inline] +pub const fn well_known_symbol_id_from_key(key: u32) -> Option { + let offset = key - SYMBOL_KEY_BASE; + if offset < WELL_KNOWN_SYMBOL_COUNT { + Some(offset) + } else { + None + } +} + #[cfg(test)] mod tests { use super::*; @@ -35,4 +88,49 @@ mod tests { assert_ne!(id, u32::MAX); assert!(!is_private_name_key(7)); } + + #[test] + fn symbol_keys_roundtrip() { + let key = make_symbol_key(0); + assert!(is_symbol_key(key)); + assert!(is_private_name_key(key)); + assert_eq!(symbol_index_from_key(key), 0); + assert!(well_known_symbol_id_from_key(key).is_none()); + } + + #[test] + fn symbol_keys_distinct_per_index() { + let a = make_symbol_key(1); + let b = make_symbol_key(2); + assert_ne!(a, b); + assert!(is_symbol_key(a)); + assert!(is_symbol_key(b)); + } + + #[test] + fn symbol_keys_avoid_private_band() { + let id = make_private_name_id(12); + let key = make_symbol_key(12); + assert!(!is_symbol_key(id)); + assert!(is_symbol_key(key)); + assert_ne!(id, key); + } + + #[test] + fn well_known_keys_reserve_low_slots() { + for id in 0..WELL_KNOWN_SYMBOL_COUNT { + let key = make_well_known_symbol_key(id); + assert!(is_symbol_key(key)); + assert_eq!(well_known_symbol_id_from_key(key), Some(id)); + } + let user_key = make_well_known_symbol_key(WELL_KNOWN_SYMBOL_COUNT); + assert!(well_known_symbol_id_from_key(user_key).is_none()); + } + + #[test] + fn symbol_index_mask_prevents_wrap() { + let key = make_symbol_key(SYMBOL_INDEX_MASK); + assert_eq!(symbol_index_from_key(key), SYMBOL_INDEX_MASK); + assert!(is_symbol_key(key)); + } } diff --git a/crates/oxide_vm/src/bindings/bind_map.rs b/crates/oxide_vm/src/bindings/bind_map.rs index 93e29da..9a40ba1 100644 --- a/crates/oxide_vm/src/bindings/bind_map.rs +++ b/crates/oxide_vm/src/bindings/bind_map.rs @@ -1,7 +1,9 @@ use std::sync::Arc; use crate::bind_constructor; -use crate::bindings::{apply_binding_table, bind_accessor_getter, bind_method_alias, configure_native_constructor}; +use crate::bindings::{ + apply_binding_table, bind_accessor_getter, bind_well_known_method_alias, configure_native_constructor, +}; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::object::JsObject; @@ -32,8 +34,8 @@ pub fn bind_map(core: &Arc, session: &KernelSession, global: &mut Js ], ); - // Map 的 @@iterator 是 entries 的同一函数对象。 - bind_method_alias(core, proto, "entries", "@@iterator"); + // Map 的 @@iterator(Symbol.iterator)是 entries 的同一函数对象。 + bind_well_known_method_alias(core, proto, "entries", 0); bind_accessor_getter(core, session, proto, "size", oxide_builtins::map::map_size:: as *const ()); bind_constructor!(core, global, "Map", ctor_ptr, oxide_builtins::map::map_constructor::, 1, hash: true); diff --git a/crates/oxide_vm/src/bindings/bind_object.rs b/crates/oxide_vm/src/bindings/bind_object.rs index 1c4035f..a855e67 100644 --- a/crates/oxide_vm/src/bindings/bind_object.rs +++ b/crates/oxide_vm/src/bindings/bind_object.rs @@ -24,6 +24,7 @@ pub fn bind_object(core: &Arc, session: &KernelSession, global: &mut is_sealed: oxide_builtins::object::object_is_sealed:: as *const (), is_extensible: oxide_builtins::object::object_is_extensible:: as *const (), get_own_property_names: oxide_builtins::object::object_get_own_property_names:: as *const (), + get_own_property_symbols: oxide_builtins::object::object_get_own_property_symbols:: as *const (), define_properties: oxide_builtins::object::object_define_properties:: as *const (), from_entries: oxide_builtins::object::object_from_entries:: as *const (), get_prototype_of: oxide_builtins::object::object_get_prototype_of:: as *const (), diff --git a/crates/oxide_vm/src/bindings/bind_regexp.rs b/crates/oxide_vm/src/bindings/bind_regexp.rs index 1bdcbed..019498d 100644 --- a/crates/oxide_vm/src/bindings/bind_regexp.rs +++ b/crates/oxide_vm/src/bindings/bind_regexp.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::bindings::{apply_binding_table, configure_native_constructor}; +use crate::bindings::{apply_binding_table, bind_well_known_method, configure_native_constructor}; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::object::JsObject; @@ -23,21 +23,56 @@ pub fn bind_regexp(core: &Arc, session: &KernelSession, global: &mut ("exec", oxide_builtins::regexp::regexp_exec:: as *const (), 1), ("test", oxide_builtins::regexp::regexp_test:: as *const (), 1), ("toString", oxide_builtins::regexp::regexp_to_string:: as *const (), 0), - ("@@match", oxide_builtins::regexp::regexp_symbol_match:: as *const (), 1), - ( - "@@replace", - oxide_builtins::regexp::regexp_symbol_replace:: as *const (), - 2, - ), - ("@@search", oxide_builtins::regexp::regexp_symbol_search:: as *const (), 1), - ("@@split", oxide_builtins::regexp::regexp_symbol_split:: as *const (), 2), - ( - "@@matchAll", - oxide_builtins::regexp::regexp_symbol_match_all:: as *const (), - 1, - ), ], ); + // Symbol.match 等 well-known symbol 方法按 Symbol 键安装,供 `re[Symbol.match]` 等读取。 + let world = session.builtin_world(); + bind_well_known_method( + world, + core, + proto, + 1, + "@@match", + oxide_builtins::regexp::regexp_symbol_match:: as *const (), + 1, + ); + bind_well_known_method( + world, + core, + proto, + 2, + "@@replace", + oxide_builtins::regexp::regexp_symbol_replace:: as *const (), + 2, + ); + bind_well_known_method( + world, + core, + proto, + 3, + "@@search", + oxide_builtins::regexp::regexp_symbol_search:: as *const (), + 1, + ); + bind_well_known_method( + world, + core, + proto, + 4, + "@@split", + oxide_builtins::regexp::regexp_symbol_split:: as *const (), + 2, + ); + bind_well_known_method( + world, + core, + proto, + 7, + "@@matchAll", + oxide_builtins::regexp::regexp_symbol_match_all:: as *const (), + 1, + ); + bind_constructor!(core, global, "RegExp", ctor_ptr, oxide_builtins::regexp::regexp_constructor::, 2, hash: true); } diff --git a/crates/oxide_vm/src/bindings/bind_set.rs b/crates/oxide_vm/src/bindings/bind_set.rs index 4094823..eada725 100644 --- a/crates/oxide_vm/src/bindings/bind_set.rs +++ b/crates/oxide_vm/src/bindings/bind_set.rs @@ -1,7 +1,10 @@ use std::sync::Arc; use crate::bind_constructor; -use crate::bindings::{apply_binding_table, bind_accessor_getter, bind_method_alias, configure_native_constructor}; +use crate::bindings::{ + apply_binding_table, bind_accessor_getter, bind_method_alias, bind_well_known_method_alias, + configure_native_constructor, +}; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::object::JsObject; @@ -45,9 +48,9 @@ pub fn bind_set(core: &Arc, session: &KernelSession, global: &mut Js ], ); - // keys 与 @@iterator 是 values 的同一函数对象。 + // keys 与 @@iterator(Symbol.iterator)是 values 的同一函数对象。 bind_method_alias(core, proto, "values", "keys"); - bind_method_alias(core, proto, "values", "@@iterator"); + bind_well_known_method_alias(core, proto, "values", 0); bind_accessor_getter(core, session, proto, "size", oxide_builtins::set::set_size:: as *const ()); bind_constructor!(core, global, "Set", ctor_ptr, oxide_builtins::set::set_constructor::, 1, hash: true); diff --git a/crates/oxide_vm/src/bindings/bind_typed_array.rs b/crates/oxide_vm/src/bindings/bind_typed_array.rs index aa64f89..0b65aee 100644 --- a/crates/oxide_vm/src/bindings/bind_typed_array.rs +++ b/crates/oxide_vm/src/bindings/bind_typed_array.rs @@ -2,7 +2,8 @@ use std::sync::Arc; use crate::bind_constructor; use crate::bindings::{ - apply_binding_table, bind_accessor_getter, bind_global_value, bind_method_alias, configure_native_constructor, + apply_binding_table, bind_accessor_getter, bind_global_value, bind_method_alias, bind_well_known_method_alias, + configure_native_constructor, }; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::object::{JsObject, PropAttributes}; @@ -195,7 +196,8 @@ pub fn bind_typed_array(core: &Arc, session: &KernelSession, global: ("with", oxide_builtins::typed_array::typed_array_with:: as *const (), 2), ], ); - bind_method_alias(core, shared_proto, "values", "@@iterator"); + bind_method_alias(core, shared_proto, "values", "keys"); + bind_well_known_method_alias(core, shared_proto, "values", 0); // 原型访问器:视图属性(buffer/byteOffset/byteLength/length)读内部数据槽, // @@toStringTag 返回具体类型名,供 Object.prototype.toString 区分类型。 diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 75bfa47..2eb3601 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -176,6 +176,48 @@ pub(crate) fn bind_method_alias(core: &Arc, proto: &mut JsObject, so proto.bump_generation(); } +/// 在原型上按 well-known symbol 键绑定方法(键不字符串 intern,读键经 +/// `property_key_si` 的 well-known 分支映射到同一键)。 +pub(crate) fn bind_well_known_method( + world: &BuiltinWorld, core: &Arc, target: &mut JsObject, well_known_id: u32, method_name: &str, + func: *const (), nargs: u8, +) { + let shape_forge = core.shape_forge().as_ref(); + let string_forge = core.perm_interner().as_ref(); + // SAFETY: func 是转成 *const () 的 NativeFn 函数项指针。 + let fn_ptr = unsafe { oxide_types::object::NativeFnPtr::from_raw(func) }; + let key = oxide_types::private_key::make_well_known_symbol_key(well_known_id); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method_key_static( + target, + shape_forge, + string_forge, + key, + method_name, + fn_ptr, + nargs, + world.fn_proto_val(), + ); +} + +/// 把原型上 `source` 属性已绑定的函数值复制到 well-known symbol 键名下(共享同一函数对象)。 +pub(crate) fn bind_well_known_method_alias( + core: &Arc, proto: &mut JsObject, source: &str, well_known_id: u32, +) { + let shape_forge = core.shape_forge().as_ref(); + let string_forge = core.perm_interner().as_ref(); + let src_si = string_forge.intern(source).0; + let Some(pos) = shape_forge.lookup_position(proto.shape_id(), src_si) else { + return; + }; + let value = proto.get_prop_at(pos); + let key = oxide_types::private_key::make_well_known_symbol_key(well_known_id); + let new_shape = shape_forge.make_shape(proto.shape_id(), key); + proto.set_shape_id(new_shape); + let alias_pos = proto.push_prop(value); + proto.set_data_meta(alias_pos, PropAttributes::new(true, false, true)); + proto.bump_generation(); +} + pub(crate) fn bind_global_value(core: &Arc, global: &mut JsObject, name: &str, value: JsValue) { let si = core.perm_interner().intern(name).0; let shape = core.shape_forge().make_shape(global.shape_id(), si); diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index e8e7087..7075bf0 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -18,6 +18,7 @@ use oxide_runtime_api::NativeResult; use oxide_types::error::{JsError, JsErrorKind}; use oxide_types::mem::{Epoch, P}; use oxide_types::object::{Cell, JsObject, NativeFnPtr, PropAttributes}; +use oxide_types::private_key::{make_symbol_key, make_well_known_symbol_key}; use oxide_types::value::{JsValue, PTR_MASK}; pub(crate) const MAX_PROTO_CHAIN_DEPTH: usize = 1024; @@ -304,10 +305,8 @@ impl Vm { } // ECMA-262 §7.1.1 step 1: an exotic obj[Symbol.toPrimitive] takes precedence - // over OrdinaryToPrimitive. ponytail: symbol-object keys all collapse to - // to_string()="[object]" inside property_key_si (an engine-wide symbol-key - // limitation), so this resolves obj[@@toPrimitive] the same way the setter - // stores it; the real fix belongs to a dedicated symbol-key phase. + // over OrdinaryToPrimitive. well-known symbol 键经 property_key_si 映射为 + // 固定 Symbol 键,读键路径与写键路径一致。 let sym_key = { let sym_ptr = self.session.builtin_world().sym_to_primitive.as_ptr() as *mut JsObject; JsValue::from_js_object(sym_ptr) @@ -699,31 +698,36 @@ impl Vm { let s = unsafe { &(*val.as_string_ptr()).data }; return self.kernel_core.perm_interner().intern(s).0; } - // well-known symbol 是空对象:按指针比对映射为各自专属键,避免全部塌缩。 + // Symbol 值直接编码为 Symbol 键(不进字符串 interner,键相互独立)。 + if val.is_symbol() { + return make_symbol_key(val.as_symbol_index()); + } + // well-known symbol 是空对象:按指针比对映射到各自的 well-known Symbol 键, + // 避免全部塌缩成同一个键。 if val.is_object() { let world = self.session.builtin_world(); let ptr = val.as_js_object_ptr(); - let key = if std::ptr::eq(ptr, world.sym_iterator.as_ptr()) { - "@@iterator" + let well_known_id = if std::ptr::eq(ptr, world.sym_iterator.as_ptr()) { + Some(0) } else if std::ptr::eq(ptr, world.sym_match.as_ptr()) { - "@@match" + Some(1) } else if std::ptr::eq(ptr, world.sym_replace.as_ptr()) { - "@@replace" + Some(2) } else if std::ptr::eq(ptr, world.sym_search.as_ptr()) { - "@@search" + Some(3) } else if std::ptr::eq(ptr, world.sym_split.as_ptr()) { - "@@split" + Some(4) } else if std::ptr::eq(ptr, world.sym_to_primitive.as_ptr()) { - "@@toPrimitive" + Some(5) } else if std::ptr::eq(ptr, world.sym_has_instance.as_ptr()) { - "@@hasInstance" + Some(6) } else if std::ptr::eq(ptr, world.sym_match_all.as_ptr()) { - "@@matchAll" + Some(7) } else { - "" + None }; - if !key.is_empty() { - return self.kernel_core.perm_interner().intern(key).0; + if let Some(id) = well_known_id { + return make_well_known_symbol_key(id); } } let key = coercion::to_string(val); diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 0189c5c..e3dfb47 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -5,7 +5,7 @@ use crate::vm::{native_fn_ptr_to_fn, CallFrame, ForInIter, FrameContinuation, Vm use crate::vm_trace; use oxide_runtime_api::{to_boolean, NativeResult}; use oxide_types::object::{JsObject, PropAttributes}; -use oxide_types::private_key::is_private_name_key; +use oxide_types::private_key::{is_private_name_key, is_symbol_key}; use oxide_types::value::JsValue; impl Vm { @@ -357,7 +357,9 @@ impl Vm { break; } if let Some(shape) = self.kernel_core.shape_forge().get_shape(id) { + // Symbol 键/私有名键不参与 for-in 枚举。 if shape.property_name != u32::MAX + && !is_symbol_key(shape.property_name) && !is_private_name_key(shape.property_name) && seen.insert(shape.property_name) { diff --git a/crates/oxide_vm/tests/builtins_symbol_tests.rs b/crates/oxide_vm/tests/builtins_symbol_tests.rs index 819ab58..0080b01 100644 --- a/crates/oxide_vm/tests/builtins_symbol_tests.rs +++ b/crates/oxide_vm/tests/builtins_symbol_tests.rs @@ -205,3 +205,81 @@ fn has_instance_preserves_ordinary_has_instance() { let result = eval(&mut vm, "[] instanceof Array").unwrap(); assert!(result.as_bool()); } + +// --- Symbol 作属性键 --- + +#[test] +fn symbol_keys_stay_distinct() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var s1=Symbol('a'), s2=Symbol('b'); var o={}; o[s1]=1; o[s2]=2; [o[s1],o[s2]]").unwrap(); + let arr = unsafe { &*result.as_js_object_ptr() }; + assert_eq!(arr.get_prop_at(0).as_int(), 1); + assert_eq!(arr.get_prop_at(1).as_int(), 2); +} + +#[test] +fn symbol_keys_excluded_from_object_keys() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var s=Symbol('x'); var o={a:1}; o[s]=2; Object.keys(o).length").unwrap(); + assert_eq!(result.as_int(), 1); +} + +#[test] +fn symbol_keys_excluded_from_own_property_names() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var s=Symbol('x'); var o={}; o[s]=1; Object.getOwnPropertyNames(o).length").unwrap(); + assert_eq!(result.as_int(), 0); +} + +#[test] +fn symbol_keys_excluded_from_json_stringify() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var s=Symbol('x'); var o={a:1}; o[s]=2; JSON.stringify(o)").unwrap(); + assert_eq!(to_str(&vm, result), "{\"a\":1}"); +} + +#[test] +fn symbol_keys_excluded_from_for_in() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var s=Symbol('x'); var o={}; o[s]=1; var n=0; for (var k in o) n++; n").unwrap(); + assert_eq!(result.as_int(), 0); +} + +#[test] +fn get_own_property_symbols_roundtrip() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var s=Symbol('x'); var o={}; o[s]=7; var k=Object.getOwnPropertySymbols(o)[0]; o[k]").unwrap(); + assert_eq!(result.as_int(), 7); +} + +#[test] +fn get_own_property_symbols_length() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var s=Symbol('x'); var o={}; o[s]=1; Object.getOwnPropertySymbols(o).length").unwrap(); + assert_eq!(result.as_int(), 1); +} + +#[test] +fn computed_symbol_key_object_literal() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var o={[Symbol('k')]: 5, a: 1}; Object.keys(o).length").unwrap(); + assert_eq!(result.as_int(), 1); +} + +#[test] +fn well_known_symbol_key_excluded_from_keys() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var o={}; o[Symbol.iterator]=1; Object.keys(o).length").unwrap(); + assert_eq!(result.as_int(), 0); +} + +#[test] +fn symbol_key_read_via_get_own_property_symbols_index() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var s1=Symbol('a'), s2=Symbol('b'); var o={}; o[s1]=1; o[s2]=2; var ks=Object.getOwnPropertySymbols(o); var t=0; for (var i=0;i