fix: 错误对象属性枚举性、toString 标签与构造器调用语义

- message/name 属性改非枚举(CreateNonEnumerableDataPropertyOrThrow),
  Object.keys/for-in/JSON.stringify 不再泄漏错误信息
- 新增 Error 家族对象标签,Object.prototype.toString 输出 [object Error],
  含子类型与用户子类(沿原型链判定)
- Error 家族构造器补 length=1,函数调用忽略 this 一律新建对象
- JSON.stringify 序列化仅含可枚举自身属性(规范 EnumerableOwnPropertyNames)
This commit is contained in:
2026-08-15 21:30:28 +08:00
parent c4961fae29
commit 07d6c83c6b
7 changed files with 156 additions and 18 deletions
+19 -11
View File
@@ -3,7 +3,7 @@ use std::sync::Arc;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_runtime_api::{to_string, NativeResult, VmHost};
use oxide_types::mem::P;
use oxide_types::object::JsObject;
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::value::JsValue;
fn set_own_message<H: VmHost>(host: &mut H, this: *mut JsObject, args: &[u8]) {
@@ -22,7 +22,9 @@ fn set_own_message<H: VmHost>(host: &mut H, this: *mut JsObject, args: &[u8]) {
let perm_val = host.new_string(&msg_str);
unsafe {
(*this).set_shape_id(new_shape);
(*this).push_prop(perm_val);
let pos = (*this).push_prop(perm_val);
// message 按规范为非枚举数据属性(CreateNonEnumerableDataPropertyOrThrow)。
(*this).set_data_meta(pos, PropAttributes::new(true, false, true));
}
}
@@ -49,9 +51,15 @@ pub fn create_kind_error<H: VmHost>(host: &mut H, kind: &str, msg: &str) -> JsVa
let msg_val = host.new_string(msg);
unsafe {
(*obj).set_shape_id(shape);
(*obj).push_prop(msg_val);
let pos = (*obj).push_prop(msg_val);
// message 按规范为非枚举数据属性,避免泄漏进 Object.keys/for-in/JSON。
(*obj).set_data_meta(pos, PropAttributes::new(true, false, true));
}
}
// 标记 Error 家族标签:Object.prototype.toString 据此输出 `[object Error]`。
unsafe {
(*obj).type_tag = JsObject::OBJ_TYPE_ERROR;
}
JsValue::from_js_object(obj)
}
@@ -115,15 +123,15 @@ macro_rules! error_ctor {
($name:ident, $proto_field:ident) => {
/// 对应 Error 子类(如 `TypeError`)的构造函数:接收第一个实参作为 message,
/// 返回原型链指向对应 prototype 的 Error 对象。
///
/// # 注意事项
/// 无论以 `new` 还是普通函数调用(如 `TypeError.call(obj)`)都新建对象,
/// 忽略调用方传入的 this——与规范构造器语义一致。
pub fn $name<H: VmHost>(host: &mut H, args: &[u8]) -> NativeResult {
let this_val = host.reg(255);
let this = if this_val.is_object() {
this_val.as_js_object_ptr()
} else {
let proto_ptr = P::as_ptr(&host.session().builtin_world().$proto_field) as *mut JsObject;
host.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr)))
};
let proto_ptr = P::as_ptr(&host.session().builtin_world().$proto_field) as *mut JsObject;
let this = host
.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr)));
set_own_message(host, this, args);
NativeResult::Ok(JsValue::from_js_object(this))
}
+7 -2
View File
@@ -2,7 +2,7 @@ use std::collections::HashSet;
use std::fmt::Write;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::JsObject;
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::private_key::make_int_key;
use oxide_types::value::JsValue;
@@ -361,9 +361,14 @@ fn stringify_object<H: VmHost>(
out.push('{');
let keys = walk_own_keys(vm, obj);
// 预计算 (名称, 槽位) 对,并应用白名单过滤。
// 仅序列化可枚举自身属性(规范 EnumerableOwnPropertyNames)。
let entries: Vec<(String, u32)> = keys
.into_iter()
.filter(|(_si, pos)| {
obj.prop_meta_at(*pos)
.map(|m| m.attributes.enumerable())
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
})
.filter_map(|(si, pos)| {
let name = key_si_to_string(vm, si);
if let Some(whitelist) = replacer_whitelist {
+21
View File
@@ -1096,6 +1096,25 @@ pub fn object_proto_value_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
NativeResult::Ok(vm.reg(args[0]))
}
/// 沿原型链判断是否为 Error 家族对象:对象自身带 `OBJ_TYPE_ERROR` 标签,或原型链上
/// 含 Error.prototype(等价 [[ErrorData]] 内部槽语义,覆盖 Error 用户子类)。
fn is_error_family<H: VmHost>(vm: &H, ptr: *mut JsObject) -> bool {
let error_proto_ptr = vm.session().builtin_world().error_proto.as_ptr() as *mut JsObject;
let mut cur = ptr;
while !cur.is_null() {
if std::ptr::eq(cur, error_proto_ptr) {
return true;
}
// SAFETY: cur 沿原型链遍历,链上每个节点都是合法 JsObject(proto 非对象时为空指针终止)。
let o = unsafe { &*cur };
if o.is_error_obj() {
return true;
}
cur = o.proto().as_js_object_ptr();
}
false
}
/// `Object.prototype.toString`:返回 `[object Tag]`。对象路径先按内置类型判定标签,
/// 再读 `@@toStringTag`——为字符串时覆盖内置标签(TypedArray 依赖它区分具体类型)。
pub fn object_proto_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
@@ -1138,6 +1157,8 @@ pub fn object_proto_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
"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()) {