mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-26 03:31:12 +08:00
fix: symbol 作属性键独立编码,不再塌缩为空串
This commit is contained in:
@@ -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