fix: symbol 作属性键独立编码,不再塌缩为空串
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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]));
|
||||
|
||||
|
||||
@@ -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 枚举
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 区分类型。
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user