mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
feat: 补全 Object.prototype 方法族与 toString 标签
This commit is contained in:
@@ -1456,11 +1456,227 @@ pub fn object_has_own<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
lookup_accessor_field(vm, args, true)
|
||||
}
|
||||
|
||||
/// `Object.prototype.__lookupSetter__(key)`:沿原型链取 key 的 `[[Set]]`。
|
||||
pub fn object_proto_lookup_setter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
lookup_accessor_field(vm, args, false)
|
||||
}
|
||||
|
||||
/// 沿原型链判断是否为 Error 家族对象:对象自身带 `OBJ_TYPE_ERROR` 标签,或原型链上
|
||||
@@ -1482,77 +1698,71 @@ fn is_error_family<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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,
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -56,15 +56,49 @@ pub fn bind_object(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
0,
|
||||
),
|
||||
("valueOf", oxide_builtins::object::object_proto_value_of::<crate::vm::Vm> as *const (), 0),
|
||||
(
|
||||
"isPrototypeOf",
|
||||
oxide_builtins::object::object_proto_is_prototype_of::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
),
|
||||
(
|
||||
"toLocaleString",
|
||||
oxide_builtins::object::object_proto_to_locale_string::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
(
|
||||
"__defineGetter__",
|
||||
oxide_builtins::object::object_proto_define_getter::<crate::vm::Vm> as *const (),
|
||||
2,
|
||||
),
|
||||
(
|
||||
"__defineSetter__",
|
||||
oxide_builtins::object::object_proto_define_setter::<crate::vm::Vm> as *const (),
|
||||
2,
|
||||
),
|
||||
(
|
||||
"__lookupGetter__",
|
||||
oxide_builtins::object::object_proto_lookup_getter::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
),
|
||||
(
|
||||
"__lookupSetter__",
|
||||
oxide_builtins::object::object_proto_lookup_setter::<crate::vm::Vm> 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));
|
||||
|
||||
@@ -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<KernelCore>, session: &KernelSession, global:
|
||||
"length",
|
||||
oxide_builtins::typed_array::typed_array_length_getter::<crate::vm::Vm> 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::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
|
||||
|
||||
@@ -617,6 +617,41 @@ fn bind_iterator_protos(core: &Arc<KernelCore>, session: &KernelSession) {
|
||||
);
|
||||
}
|
||||
|
||||
/// 给标准内置原型补装缺失的 `@@toStringTag` 数据属性
|
||||
/// (`{ writable:false, enumerable:false, configurable:true }`)。
|
||||
///
|
||||
/// 幂等:目标已有 tag 键槽位时跳过(dirty reset 重复经过不追加重复属性槽)。
|
||||
/// `%IteratorPrototype%` / BigInt 原型的 tag 已由各家族绑定点安装,不在此列。
|
||||
fn install_to_string_tags(core: &Arc<KernelCore>, 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<KernelCore>, 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` + 快照记录)。
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user