fix: symbol 作属性键独立编码,不再塌缩为空串

This commit is contained in:
2026-08-09 16:09:49 +08:00
parent c8740bfb49
commit cd1bc206a5
14 changed files with 453 additions and 57 deletions
+3 -2
View File
@@ -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<H: VmHost>(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<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<JsValue>
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) => {
+99 -4
View File
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> {
keys
}
/// 收集对象自身全部 Symbol 键(shape 链),按键序排列(根→叶,即插入序)。
/// 与 [`walk_own_keys`] 互补:只返回 Symbol 键,供 `getOwnPropertySymbols` 使用。
fn walk_own_symbol_keys<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<JsValue> = {
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<H: VmHost>(vm: &H, key_si: u32) -> Option<u32> {
let key = vm.kernel_core().perm_interner().lookup(key_si)?;
@@ -516,7 +611,7 @@ pub fn object_define_property<H: VmHost>(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]));
+38 -7
View File
@@ -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 枚举
+101 -3
View File
@@ -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<u32> {
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));
}
}
+5 -3
View File
@@ -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<KernelCore>, 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::<crate::vm::Vm> as *const ());
bind_constructor!(core, global, "Map", ctor_ptr, oxide_builtins::map::map_constructor::<crate::vm::Vm>, 1, hash: true);
@@ -24,6 +24,7 @@ pub fn bind_object(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
is_sealed: oxide_builtins::object::object_is_sealed::<crate::vm::Vm> as *const (),
is_extensible: oxide_builtins::object::object_is_extensible::<crate::vm::Vm> as *const (),
get_own_property_names: oxide_builtins::object::object_get_own_property_names::<crate::vm::Vm> as *const (),
get_own_property_symbols: oxide_builtins::object::object_get_own_property_symbols::<crate::vm::Vm> as *const (),
define_properties: oxide_builtins::object::object_define_properties::<crate::vm::Vm> as *const (),
from_entries: oxide_builtins::object::object_from_entries::<crate::vm::Vm> as *const (),
get_prototype_of: oxide_builtins::object::object_get_prototype_of::<crate::vm::Vm> as *const (),
+49 -14
View File
@@ -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<KernelCore>, session: &KernelSession, global: &mut
("exec", oxide_builtins::regexp::regexp_exec::<crate::vm::Vm> as *const (), 1),
("test", oxide_builtins::regexp::regexp_test::<crate::vm::Vm> as *const (), 1),
("toString", oxide_builtins::regexp::regexp_to_string::<crate::vm::Vm> as *const (), 0),
("@@match", oxide_builtins::regexp::regexp_symbol_match::<crate::vm::Vm> as *const (), 1),
(
"@@replace",
oxide_builtins::regexp::regexp_symbol_replace::<crate::vm::Vm> as *const (),
2,
),
("@@search", oxide_builtins::regexp::regexp_symbol_search::<crate::vm::Vm> as *const (), 1),
("@@split", oxide_builtins::regexp::regexp_symbol_split::<crate::vm::Vm> as *const (), 2),
(
"@@matchAll",
oxide_builtins::regexp::regexp_symbol_match_all::<crate::vm::Vm> 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::<crate::vm::Vm> as *const (),
1,
);
bind_well_known_method(
world,
core,
proto,
2,
"@@replace",
oxide_builtins::regexp::regexp_symbol_replace::<crate::vm::Vm> as *const (),
2,
);
bind_well_known_method(
world,
core,
proto,
3,
"@@search",
oxide_builtins::regexp::regexp_symbol_search::<crate::vm::Vm> as *const (),
1,
);
bind_well_known_method(
world,
core,
proto,
4,
"@@split",
oxide_builtins::regexp::regexp_symbol_split::<crate::vm::Vm> as *const (),
2,
);
bind_well_known_method(
world,
core,
proto,
7,
"@@matchAll",
oxide_builtins::regexp::regexp_symbol_match_all::<crate::vm::Vm> as *const (),
1,
);
bind_constructor!(core, global, "RegExp", ctor_ptr, oxide_builtins::regexp::regexp_constructor::<crate::vm::Vm>, 2, hash: true);
}
+6 -3
View File
@@ -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<KernelCore>, 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::<crate::vm::Vm> as *const ());
bind_constructor!(core, global, "Set", ctor_ptr, oxide_builtins::set::set_constructor::<crate::vm::Vm>, 1, hash: true);
@@ -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<KernelCore>, session: &KernelSession, global:
("with", oxide_builtins::typed_array::typed_array_with::<crate::vm::Vm> 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 区分类型。
+42
View File
@@ -176,6 +176,48 @@ pub(crate) fn bind_method_alias(core: &Arc<KernelCore>, 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<KernelCore>, 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<KernelCore>, 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<KernelCore>, 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);
+21 -17
View File
@@ -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);
+3 -1
View File
@@ -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)
{
@@ -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<ks.length;i++) t+=o[ks[i]]; t === 3",
)
.unwrap();
assert!(result.as_bool());
}
@@ -115,7 +115,9 @@ fn length_and_name_descriptors_are_non_writable_non_enumerable_configurable() {
#[test]
fn length_comes_before_name_in_property_order() {
assert_eq!(
eval_str("function f(a,b,c){}; var p=Object.getOwnPropertyNames(f); p[p.indexOf('length')] < p[p.indexOf('name')]"),
eval_str(
"function f(a,b,c){}; var p=Object.getOwnPropertyNames(f); p[p.indexOf('length')] < p[p.indexOf('name')]"
),
"true"
);
}