From d93215c83be4f312b493007e9afb9982336871e0 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 21 Sep 2026 03:30:25 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A1=A5=E5=85=A8=20Object.prototype?= =?UTF-8?q?=20=E6=96=B9=E6=B3=95=E6=97=8F=E4=B8=8E=20toString=20=E6=A0=87?= =?UTF-8?q?=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/object.rs | 374 ++++++++++++++---- crates/oxide_vm/src/async_func.rs | 5 +- crates/oxide_vm/src/async_generator.rs | 10 +- crates/oxide_vm/src/bindings/bind_object.rs | 36 +- .../oxide_vm/src/bindings/bind_typed_array.rs | 9 +- crates/oxide_vm/src/bindings/mod.rs | 38 ++ crates/oxide_vm/src/generator.rs | 10 +- crates/oxide_vm/src/promise/constructor.rs | 6 +- 8 files changed, 397 insertions(+), 91 deletions(-) diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index eb02e5d1..43be0feb 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -1456,11 +1456,227 @@ pub fn object_has_own(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(vm.get_own_property_slot(obj, key_si).is_some())) } -/// `Object.prototype.valueOf`:返回 this 本身(配合 OrdinaryToPrimitive 的兜底)。 +/// `Object.prototype.valueOf`:返回 this 的对象形式(配合 OrdinaryToPrimitive 的兜底)。 +/// +/// # 步骤 +/// 1. ToObject 装箱 this(null/undefined 抛 TypeError)。 +/// +/// # 副作用 +/// 无;返回装箱后的对象值,OrdinaryToPrimitive 因结果非原始值而继续走 toString。 pub fn object_proto_value_of(vm: &mut H, args: &[u8]) -> NativeResult { - // Object.prototype.valueOf 原样返回 this 对象;OrdinaryToPrimitive - // 因结果非原始值而继续走 toString。 - NativeResult::Ok(vm.reg(args[0])) + if args.is_empty() { + return NativeResult::Err(crate::error::create_type_error(vm, "Object.prototype.valueOf called on non-object")); + } + let this_val = vm.reg(args[0]); + let obj_val = match oxide_runtime_api::to_object(this_val, vm) { + Ok(v) => v, + Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)), + }; + NativeResult::Ok(obj_val) +} + +/// `Object.prototype.isPrototypeOf(arg)`:this 是否在 arg 的原型链上。 +/// +/// # 步骤 +/// 1. arg 非对象 → false(this 的转换不做,与 V8 行为一致)。 +/// 2. ToObject 装箱 this(null/undefined 抛 TypeError)。 +/// 3. 沿 arg 原型链逐节点比较指针恒等,命中 true,链尽 false。 +pub fn object_proto_is_prototype_of(vm: &mut H, args: &[u8]) -> NativeResult { + if args.is_empty() { + return NativeResult::Ok(JsValue::bool(false)); + } + let this_val = vm.reg(args[0]); + let arg = vm.reg(if args.len() < 2 { 0 } else { args[1] }); + if !arg.is_object() { + return NativeResult::Ok(JsValue::bool(false)); + } + let obj_val = match oxide_runtime_api::to_object(this_val, vm) { + Ok(v) => v, + Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)), + }; + let target = obj_val.as_js_object_ptr(); + let mut cur = arg.as_js_object_ptr(); + while !cur.is_null() { + if cur == target { + return NativeResult::Ok(JsValue::bool(true)); + } + // SAFETY: cur 沿原型链遍历,链上每个节点都是合法 JsObject(proto 非对象时为空指针终止)。 + let o = unsafe { &*cur }; + cur = o.proto().as_js_object_ptr(); + } + NativeResult::Ok(JsValue::bool(false)) +} + +/// `Object.prototype.toLocaleString`:调用 this 的 `toString` 并以字符串返回。 +/// +/// # 步骤 +/// 1. ToObject 装箱 this(null/undefined 抛 TypeError)。 +/// 2. 以原始 this 值作接收者读 `toString`:accessor getter 的 this 保持原始值 +/// (装箱副本不外泄),getter 抛错透传原异常。 +/// 3. `toString` 非 callable → TypeError;调用结果经 ToString 返回。 +pub fn object_proto_to_locale_string(vm: &mut H, args: &[u8]) -> NativeResult { + if args.is_empty() { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Object.prototype.toLocaleString called on non-object", + )); + } + let this_val = vm.reg(args[0]); + let obj_val = match oxide_runtime_api::to_object(this_val, vm) { + Ok(v) => v, + Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)), + }; + let obj = unsafe { &*obj_val.as_js_object_ptr() }; + let si_to_string = vm.kernel_core().perm_interner().intern("toString").0; + let to_str = match vm.ordinary_get(obj, si_to_string, this_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), + }; + if !is_callable(to_str) { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Object.prototype.toLocaleString: toString is not callable", + )); + } + let result = match vm.call_function_sync(to_str, this_val, &[]) { + Ok(v) => v, + Err(e) => { + let exc = vm + .take_uncaught_value() + .unwrap_or_else(|| crate::error::create_type_error(vm, &e)); + return NativeResult::Err(exc); + } + }; + match oxide_runtime_api::to_string_value_full(result, vm) { + Ok(s) => NativeResult::Ok(s), + Err(e) => { + let exc = vm + .take_uncaught_value() + .unwrap_or_else(|| crate::error::create_type_error(vm, &e)); + NativeResult::Err(exc) + } + } +} + +/// `Object.prototype.__defineSetter__(key, setter)`:把 key 定义为访问器属性, +/// setter 作 `[[Set]]`,描述符 `{ enumerable: true, configurable: true }`。 +/// +/// # 步骤 +/// 1. ToObject 装箱 this(null/undefined 抛 TypeError) +/// 2. setter 非 callable → TypeError +/// 3. ToPropertyKey 求键(转换异常原样传播) +/// 4. DefinePropertyOrThrow(复用 `define_accessor_property` 的拒绝语义: +/// 不可扩展对象 / 不可配置属性覆盖均抛 TypeError) +/// +/// # 副作用 +/// - 修改 this 的 shape 链与属性表;返回 undefined +pub fn object_proto_define_setter(vm: &mut H, args: &[u8]) -> NativeResult { + if args.len() < 3 { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Object.prototype.__defineSetter__ requires 2 arguments", + )); + } + let this_val = vm.reg(args[0]); + let obj_val = match oxide_runtime_api::to_object(this_val, vm) { + Ok(v) => v, + Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)), + }; + let setter = vm.reg(args[2]); + if !is_callable(setter) { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Object.prototype.__defineSetter__: setter must be callable", + )); + } + let key_si = match vm.to_property_key_si(vm.reg(args[1])) { + Ok(si) => si, + Err(e) => { + let exc = vm + .take_uncaught_value() + .unwrap_or_else(|| crate::error::create_type_error(vm, &e)); + return NativeResult::Err(exc); + } + }; + let obj = unsafe { &mut *obj_val.as_js_object_ptr() }; + let attrs = PropAttributes::new(false, true, true); + // DefinePropertyOrThrow 的 desc 无 [[Get]] 键:覆盖既有访问器时保留其 getter + // (define_accessor_property 是全量替换,get 缺省须显式传现有值)。 + let existing_get = vm + .get_own_property_slot(obj, key_si) + .and_then(|pos| obj.prop_meta_at(pos)) + .filter(|m| m.is_accessor) + .map(|m| m.get) + .unwrap_or(JsValue::undefined()); + if let Err(e) = vm.define_accessor_property(obj, key_si, existing_get, setter, attrs) { + return NativeResult::Err(crate::error::create_type_error(vm, &e)); + } + NativeResult::Ok(JsValue::undefined()) +} + +/// 沿 own+proto 链查找指定键的访问器字段(getter 或 setter):数据属性与 +/// 缺侧(get/set 为 undefined)继续上爬,链尽返回 undefined。 +/// +/// # 步骤 +/// 1. ToObject 装箱 this(null/undefined 抛 TypeError) +/// 2. ToPropertyKey 求键(转换异常原样传播) +/// 3. 逐节点 GetOwnProperty:命名空间未初始化导出抛 ReferenceError; +/// accessor 命中且目标侧非 undefined 即返回 +fn lookup_accessor_field(vm: &mut H, args: &[u8], want_getter: bool) -> NativeResult { + if args.len() < 2 { + return NativeResult::Err(crate::error::create_type_error( + vm, + if want_getter { + "Object.prototype.__lookupGetter__ requires 1 argument" + } else { + "Object.prototype.__lookupSetter__ requires 1 argument" + }, + )); + } + let this_val = vm.reg(args[0]); + let obj_val = match oxide_runtime_api::to_object(this_val, vm) { + Ok(v) => v, + Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)), + }; + let key_si = match vm.to_property_key_si(vm.reg(args[1])) { + Ok(si) => si, + Err(e) => { + let exc = vm + .take_uncaught_value() + .unwrap_or_else(|| crate::error::create_type_error(vm, &e)); + return NativeResult::Err(exc); + } + }; + let mut cur = obj_val.as_js_object_ptr(); + while !cur.is_null() { + // SAFETY: cur 沿原型链遍历,链上每个节点都是合法 JsObject。 + let o = unsafe { &*cur }; + if let Some(pos) = vm.get_own_property_slot(o, key_si) { + if let Err(msg) = namespace_export_get(o, key_si) { + return NativeResult::Err(crate::error::create_reference_error(vm, msg)); + } + if let Some(meta) = o.prop_meta_at(pos) { + if meta.is_accessor { + let field = if want_getter { meta.get } else { meta.set }; + if !field.is_undefined() { + return NativeResult::Ok(field); + } + } + } + } + cur = o.proto().as_js_object_ptr(); + } + NativeResult::Ok(JsValue::undefined()) +} + +/// `Object.prototype.__lookupGetter__(key)`:沿原型链取 key 的 `[[Get]]`。 +pub fn object_proto_lookup_getter(vm: &mut H, args: &[u8]) -> NativeResult { + lookup_accessor_field(vm, args, true) +} + +/// `Object.prototype.__lookupSetter__(key)`:沿原型链取 key 的 `[[Set]]`。 +pub fn object_proto_lookup_setter(vm: &mut H, args: &[u8]) -> NativeResult { + lookup_accessor_field(vm, args, false) } /// 沿原型链判断是否为 Error 家族对象:对象自身带 `OBJ_TYPE_ERROR` 标签,或原型链上 @@ -1482,77 +1698,71 @@ fn is_error_family(vm: &H, ptr: *mut JsObject) -> bool { false } -/// `Object.prototype.toString`:返回 `[object Tag]`。对象路径先按内置类型判定标签, -/// 再读 `@@toStringTag`——为字符串时覆盖内置标签(TypedArray 依赖它区分具体类型)。 +/// `Object.prototype.toString`:返回 `[object Tag]`。 +/// +/// # 步骤 +/// 1. undefined/null 走快路径(`[object Undefined]` / `[object Null]`)。 +/// 2. ToObject 装箱 this(原始值装箱后按对象品牌判定)。 +/// 3. 品牌表定内置名(String/Number/Boolean/Symbol/Array/ArrayBuffer/DataView/ +/// Function/RegExp/Date/TypedArray/Arguments/Error 族),其余一律 "Object" +/// (Math/JSON/Promise 等靠自身 `@@toStringTag` 覆盖)。 +/// 4. 读 `Symbol.toStringTag`:字符串覆盖品牌名,非字符串/缺失回退品牌名, +/// getter 抛错透传原异常。 pub fn object_proto_to_string(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); - let tag = if !this_val.is_object() { - if this_val.is_string() { - "String" - } else if this_val.is_int() || this_val.is_double() { - "Number" - } else if this_val.is_bool() { - "Boolean" - } else if this_val.is_symbol() { - "Symbol" - } else { - "Object" - } - } else { - let ptr = this_val.as_js_object_ptr(); - if ptr.is_null() { - "Object" - } else { - let obj = unsafe { &*ptr }; - let builtin = if obj.is_array() { - "Array" - } else if obj.is_function() { - "Function" - } else if obj.is_string_obj() { - "String" - } else if obj.is_number_obj() { - "Number" - } else if obj.is_boolean_obj() { - "Boolean" - } else if obj.is_regexp_obj() { - "RegExp" - } else if obj.is_date_obj() { - "Date" - } else if obj.is_typed_array_obj() { - "TypedArray" - } else if obj.is_array_buffer_obj() { - "ArrayBuffer" - } else if obj.is_arguments_obj() { - "Arguments" - } else if is_error_family(vm, ptr) { - "Error" - } else if std::ptr::eq(ptr, vm.session().builtin_world().math_object.as_ptr()) { - "Math" - } else if std::ptr::eq(ptr, vm.session().builtin_world().json_object.as_ptr()) { - "JSON" - } else { - "Object" - }; - // @@toStringTag 为字符串时覆盖内置标签;getter 抛错透传原异常。 - let tag_key = make_well_known_symbol_key(9); - let tag_value = match vm.ordinary_get(obj, tag_key, this_val) { - Ok(value) if !value.is_undefined() => Ok(value), - Ok(_) => { - let legacy_key = vm.kernel_core().perm_interner().intern("@@toStringTag").0; - vm.ordinary_get(obj, legacy_key, this_val) - } - Err(err) => Err(err), - }; - match tag_value { - Ok(v) => match vm.lookup_str(v) { - Some(s) => return NativeResult::Ok(vm.new_string(&format!("[object {s}]"))), - None => builtin, - }, - Err(err) => return NativeResult::Err(crate::iterator::engine_error(vm, &err)), - } - } + if this_val.is_undefined() { + return NativeResult::Ok(vm.new_string("[object Undefined]")); + } + if this_val.is_null() { + return NativeResult::Ok(vm.new_string("[object Null]")); + } + let obj_val = match oxide_runtime_api::to_object(this_val, vm) { + Ok(v) => v, + Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)), }; - NativeResult::Ok(vm.new_string(&format!("[object {tag}]"))) + let obj = unsafe { &*obj_val.as_js_object_ptr() }; + // 装箱 Symbol 无内置品牌(原型链 tag 缺失时回退 "Object",与 V8 一致), + // 故品牌表不含 symbol。 + let builtin = if obj.is_string_obj() { + "String" + } else if obj.is_number_obj() { + "Number" + } else if obj.is_boolean_obj() { + "Boolean" + } else if obj.is_array() { + "Array" + } else if obj.is_array_buffer_obj() { + "ArrayBuffer" + } else if obj.is_data_view_obj() { + "DataView" + } else if obj.is_function() { + "Function" + } else if obj.is_regexp_obj() { + "RegExp" + } else if obj.is_date_obj() { + "Date" + } else if obj.is_typed_array_obj() { + "TypedArray" + } else if obj.is_arguments_obj() { + "Arguments" + } else if is_error_family(vm, obj_val.as_js_object_ptr()) { + "Error" + } else { + "Object" + }; + // @@toStringTag 为字符串时覆盖品牌名;非字符串(含缺失)回退品牌名, + // getter 抛错透传原异常。 + let tag_key = make_well_known_symbol_key(9); + match vm.ordinary_get(obj, tag_key, obj_val) { + Ok(v) => { + // 先把 tag 字符串取出(结束对 vm 的不可变借用),再拼结果串。 + let tag = vm.lookup_str(v); + let name = tag.as_deref().unwrap_or(builtin); + let text = format!("[object {name}]"); + NativeResult::Ok(vm.new_string(&text)) + } + Err(err) => NativeResult::Err(crate::iterator::engine_error(vm, &err)), + } } /// `Object.prototype.hasOwnProperty(key)`:this 是否有指定自身属性。 @@ -1564,7 +1774,15 @@ pub fn object_proto_has_own_property(vm: &mut H, args: &[u8]) -> Nati if args.len() < 2 { return NativeResult::Ok(JsValue::bool(false)); } - let key_si = vm.property_key_si(vm.reg(args[1])); + let key_si = match vm.to_property_key_si(vm.reg(args[1])) { + Ok(si) => si, + Err(e) => { + let exc = vm + .take_uncaught_value() + .unwrap_or_else(|| crate::error::create_type_error(vm, &e)); + return NativeResult::Err(exc); + } + }; let this_val = vm.reg(args[0]); let obj_val = match oxide_runtime_api::to_object(this_val, vm) { Ok(v) => v, @@ -1590,7 +1808,15 @@ pub fn object_proto_property_is_enumerable(vm: &mut H, args: &[u8]) - if args.len() < 2 { return NativeResult::Ok(JsValue::bool(false)); } - let key_si = vm.property_key_si(vm.reg(args[1])); + let key_si = match vm.to_property_key_si(vm.reg(args[1])) { + Ok(si) => si, + Err(e) => { + let exc = vm + .take_uncaught_value() + .unwrap_or_else(|| crate::error::create_type_error(vm, &e)); + return NativeResult::Err(exc); + } + }; let this_val = vm.reg(args[0]); let obj_val = match oxide_runtime_api::to_object(this_val, vm) { Ok(v) => v, diff --git a/crates/oxide_vm/src/async_func.rs b/crates/oxide_vm/src/async_func.rs index 1662e8f4..bccd8d30 100644 --- a/crates/oxide_vm/src/async_func.rs +++ b/crates/oxide_vm/src/async_func.rs @@ -437,8 +437,9 @@ pub(crate) fn init_async_intrinsics(vm: &mut Vm) { let cpos = af_proto.push_prop(JsValue::from_js_object(af_ctor_ptr)); af_proto.set_data_meta(cpos, PropAttributes::new(false, false, true)); // af_proto[Symbol.toStringTag] = "AsyncFunction"(数据属性,w/e/c = false/false/true)。 - let tag_si = sf.intern("@@toStringTag").0; - let tag_shape = sh.make_shape(af_proto.shape_id(), tag_si); + let tag_key = + oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG); + let tag_shape = sh.make_shape(af_proto.shape_id(), tag_key); af_proto.set_shape_id(tag_shape); let tag_pos = af_proto.push_prop(JsValue::perm_string(sf.string_ptr(sf.intern("AsyncFunction").0))); af_proto.set_data_meta(tag_pos, PropAttributes::new(false, false, true)); diff --git a/crates/oxide_vm/src/async_generator.rs b/crates/oxide_vm/src/async_generator.rs index f0fb0d95..fc09ec06 100644 --- a/crates/oxide_vm/src/async_generator.rs +++ b/crates/oxide_vm/src/async_generator.rs @@ -748,9 +748,10 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) { ("return", async_generator_return as *const (), 1), ("throw", async_generator_throw as *const (), 1), ); - // @@toStringTag(Object.prototype.toString → "[object AsyncGenerator]")。 - let tag_si = sf.intern("@@toStringTag").0; - let tag_shape = sh.make_shape(ag_proto.shape_id(), tag_si); + // Symbol.toStringTag(Object.prototype.toString → "[object AsyncGenerator]")。 + let tag_key = + oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG); + let tag_shape = sh.make_shape(ag_proto.shape_id(), tag_key); ag_proto.set_shape_id(tag_shape); let tag_pos = ag_proto.push_prop(JsValue::perm_string(sf.string_ptr(sf.intern("AsyncGenerator").0))); ag_proto.set_data_meta(tag_pos, PropAttributes::new(false, false, true)); @@ -811,7 +812,8 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) { let ppos2 = agf_proto.push_prop(JsValue::from_js_object(vm.async_generator_proto.as_ptr() as *mut JsObject)); agf_proto.set_data_meta(ppos2, PropAttributes::new(false, false, false)); // agf_proto[Symbol.toStringTag] = "AsyncGeneratorFunction"(数据属性,w/e/c = false/false/true)。 - let tag2_key = oxide_types::private_key::make_well_known_symbol_key(0); + let tag2_key = + oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG); let tag2_shape = sh.make_shape(agf_proto.shape_id(), tag2_key); agf_proto.set_shape_id(tag2_shape); let tag2_pos = agf_proto.push_prop(JsValue::perm_string(sf.string_ptr(sf.intern("AsyncGeneratorFunction").0))); diff --git a/crates/oxide_vm/src/bindings/bind_object.rs b/crates/oxide_vm/src/bindings/bind_object.rs index c2cf51b0..ebc65d22 100644 --- a/crates/oxide_vm/src/bindings/bind_object.rs +++ b/crates/oxide_vm/src/bindings/bind_object.rs @@ -56,15 +56,49 @@ pub fn bind_object(core: &Arc, session: &KernelSession, global: &mut 0, ), ("valueOf", oxide_builtins::object::object_proto_value_of:: as *const (), 0), + ( + "isPrototypeOf", + oxide_builtins::object::object_proto_is_prototype_of:: as *const (), + 1, + ), + ( + "toLocaleString", + oxide_builtins::object::object_proto_to_locale_string:: as *const (), + 0, + ), ( "__defineGetter__", oxide_builtins::object::object_proto_define_getter:: as *const (), 2, ), + ( + "__defineSetter__", + oxide_builtins::object::object_proto_define_setter:: as *const (), + 2, + ), + ( + "__lookupGetter__", + oxide_builtins::object::object_proto_lookup_getter:: as *const (), + 1, + ), + ( + "__lookupSetter__", + oxide_builtins::object::object_proto_lookup_setter:: as *const (), + 1, + ), ], ); // 内置原型方法不可枚举(否则会泄漏进 for-in)。 - for name in ["toString", "valueOf", "__defineGetter__"] { + for name in [ + "toString", + "valueOf", + "isPrototypeOf", + "toLocaleString", + "__defineGetter__", + "__defineSetter__", + "__lookupGetter__", + "__lookupSetter__", + ] { let si = core.perm_interner().intern(name).0; if let Some(pos) = core.shape_forge().lookup_position(object_proto.shape_id(), si) { object_proto.set_data_meta(pos, PropAttributes::new(true, false, true)); diff --git a/crates/oxide_vm/src/bindings/bind_typed_array.rs b/crates/oxide_vm/src/bindings/bind_typed_array.rs index 604747d5..b1d591c0 100644 --- a/crates/oxide_vm/src/bindings/bind_typed_array.rs +++ b/crates/oxide_vm/src/bindings/bind_typed_array.rs @@ -2,8 +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, bind_well_known_method_alias, - configure_native_constructor, + apply_binding_table, bind_accessor_getter, bind_accessor_getter_key, 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}; @@ -229,11 +229,12 @@ pub fn bind_typed_array(core: &Arc, session: &KernelSession, global: "length", oxide_builtins::typed_array::typed_array_length_getter:: as *const (), ); - bind_accessor_getter( + bind_accessor_getter_key( core, session, shared_proto, - "@@toStringTag", + oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG), + "get @@toStringTag", oxide_builtins::typed_array::typed_array_to_string_tag_getter:: as *const (), ); diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index e493e92d..5f6d5649 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -617,6 +617,41 @@ fn bind_iterator_protos(core: &Arc, session: &KernelSession) { ); } +/// 给标准内置原型补装缺失的 `@@toStringTag` 数据属性 +/// (`{ writable:false, enumerable:false, configurable:true }`)。 +/// +/// 幂等:目标已有 tag 键槽位时跳过(dirty reset 重复经过不追加重复属性槽)。 +/// `%IteratorPrototype%` / BigInt 原型的 tag 已由各家族绑定点安装,不在此列。 +fn install_to_string_tags(core: &Arc, session: &KernelSession) { + let world = session.builtin_world(); + let sf = core.perm_interner().as_ref(); + let sh = core.shape_forge().as_ref(); + let tag_id = oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG; + let tag_key = oxide_types::private_key::make_well_known_symbol_key(tag_id); + let cases: [(*mut JsObject, &str); 11] = [ + (world.map_proto.as_ptr() as *mut JsObject, "Map"), + (world.set_proto.as_ptr() as *mut JsObject, "Set"), + (world.data_view_proto.as_ptr() as *mut JsObject, "DataView"), + (world.array_iterator_proto.as_ptr() as *mut JsObject, "Array Iterator"), + (world.map_iterator_proto.as_ptr() as *mut JsObject, "Map Iterator"), + (world.set_iterator_proto.as_ptr() as *mut JsObject, "Set Iterator"), + (world.string_iterator_proto.as_ptr() as *mut JsObject, "String Iterator"), + (world.regexp_string_iterator_proto.as_ptr() as *mut JsObject, "String Iterator"), + (world.math_object.as_ptr() as *mut JsObject, "Math"), + (world.json_object.as_ptr() as *mut JsObject, "JSON"), + (world.symbol_proto.as_ptr() as *mut JsObject, "Symbol"), + ]; + for (ptr, tag) in cases { + // SAFETY: ptr 为当前 builtin world 的内置原型,session 独占期间始终有效。 + let proto = unsafe { &mut *ptr }; + if sh.lookup_position(proto.shape_id(), tag_key).is_some() { + continue; + } + let tag_val = JsValue::perm_string(sf.string_ptr(sf.intern(tag).0)); + bind_well_known_data_property(core, proto, tag_id, tag_val, PropAttributes::new(false, false, true)); + } +} + /// 对齐保留 global 上 `Iterator` 函数对象的 `prototype` 属性:object 家族重建会 /// 产生新的 `%IteratorPrototype%`,保留的 Iterator 函数对象须指向新原型。 /// @@ -1174,6 +1209,9 @@ pub fn rebind_dirty_builtins(core: &Arc, session: &mut KernelSession if dirty.map_or(true, |d| d.console) { bind_console::bind_console(core, session, global); } + // tag 目标横跨多个脏分组(Map/Set/DataView/Math/JSON/迭代器原型), + // 各组各自重绑后统一补装;安装点自带幂等检查。 + install_to_string_tags(core, session); } /// 完整初始化一个 session 的内置对象(全量绑定 + `globalThis` + 快照记录)。 diff --git a/crates/oxide_vm/src/generator.rs b/crates/oxide_vm/src/generator.rs index e37948b9..03256231 100644 --- a/crates/oxide_vm/src/generator.rs +++ b/crates/oxide_vm/src/generator.rs @@ -742,9 +742,10 @@ pub(crate) fn init_generator_intrinsics(vm: &mut Vm) { ("return", generator_return as *const (), 1), ("throw", generator_throw as *const (), 1), ); - // @@toStringTag(Object.prototype.toString → "[object Generator]")。 - let tag_si = sf.intern("@@toStringTag").0; - let tag_shape = sh.make_shape(gen_proto.shape_id(), tag_si); + // Symbol.toStringTag(Object.prototype.toString → "[object Generator]")。 + let tag_key = + oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG); + let tag_shape = sh.make_shape(gen_proto.shape_id(), tag_key); gen_proto.set_shape_id(tag_shape); let tag_pos = gen_proto.push_prop(JsValue::perm_string(sf.string_ptr(sf.intern("Generator").0))); gen_proto.set_data_meta(tag_pos, oxide_types::object::PropAttributes::new(false, false, true)); @@ -805,7 +806,8 @@ pub(crate) fn init_generator_intrinsics(vm: &mut Vm) { let ppos2 = gf_proto.push_prop(JsValue::from_js_object(vm.generator_proto.as_ptr() as *mut JsObject)); gf_proto.set_data_meta(ppos2, oxide_types::object::PropAttributes::new(false, false, false)); // gf_proto[Symbol.toStringTag] = "GeneratorFunction"(数据属性,w/e/c = false/false/true)。 - let tag2_key = oxide_types::private_key::make_well_known_symbol_key(0); + let tag2_key = + oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG); let tag2_shape = sh.make_shape(gf_proto.shape_id(), tag2_key); gf_proto.set_shape_id(tag2_shape); let tag2_pos = gf_proto.push_prop(JsValue::perm_string(sf.string_ptr(sf.intern("GeneratorFunction").0))); diff --git a/crates/oxide_vm/src/promise/constructor.rs b/crates/oxide_vm/src/promise/constructor.rs index b288d318..efb60b93 100644 --- a/crates/oxide_vm/src/promise/constructor.rs +++ b/crates/oxide_vm/src/promise/constructor.rs @@ -186,8 +186,10 @@ impl Vm { ("catch", promise_catch as *const (), 1), ("finally", promise_finally as *const (), 1), ); - let tag_si = sf.intern("@@toStringTag").0; - let tag_shape = sh.make_shape(proto.shape_id(), tag_si); + let tag_key = oxide_types::private_key::make_well_known_symbol_key( + oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG, + ); + let tag_shape = sh.make_shape(proto.shape_id(), tag_key); proto.set_shape_id(tag_shape); let tag_pos = proto.push_prop(JsValue::perm_string(sf.string_ptr(sf.intern("Promise").0))); proto.set_data_meta(tag_pos, PropAttributes::new(false, false, true));