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:
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use oxide_kernel::builtin::ErrorMethods;
|
||||
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_types::object::{JsObject, NativeFnPtr};
|
||||
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
fn bind_error_subtype_constructor(
|
||||
@@ -24,13 +24,18 @@ fn bind_error_subtype_constructor(
|
||||
|
||||
let si_prototype = sf.intern("prototype").0;
|
||||
let si_name = sf.intern("name").0;
|
||||
let si_length = sf.intern("length").0;
|
||||
let name_si = sf.intern(name).0;
|
||||
|
||||
let ctor_shape1 = sh.make_shape(EMPTY_SHAPE_ID, si_prototype);
|
||||
let ctor_shape2 = sh.make_shape(ctor_shape1, si_name);
|
||||
ctor.set_shape_id(ctor_shape2);
|
||||
let ctor_shape3 = sh.make_shape(ctor_shape2, si_length);
|
||||
ctor.set_shape_id(ctor_shape3);
|
||||
ctor.ensure_hash_props().push(JsValue::from_js_object(proto_ptr));
|
||||
ctor.ensure_hash_props().push(JsValue::perm_string(sf.string_ptr(name_si)));
|
||||
ctor.ensure_hash_props().push(JsValue::int(1));
|
||||
// 构造器 length 按规范为不可写不可枚举(Function.length 属性描述符约定)。
|
||||
ctor.set_data_meta(2u32, PropAttributes::new(false, false, true));
|
||||
|
||||
let ctor_ptr = Box::into_raw(ctor);
|
||||
|
||||
@@ -38,6 +43,9 @@ fn bind_error_subtype_constructor(
|
||||
let proto_ctor_shape = sh.make_shape(proto.shape_id(), sf.intern("constructor").0);
|
||||
proto.set_shape_id(proto_ctor_shape);
|
||||
proto.ensure_hash_props().push(JsValue::from_js_object(ctor_ptr));
|
||||
// 原型上的 constructor 按规范为非枚举数据属性(与 Error.prototype.constructor 一致)。
|
||||
let ctor_pos = proto.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
|
||||
proto.set_data_meta(ctor_pos, PropAttributes::new(true, false, true));
|
||||
|
||||
let global_shape = sh.make_shape(global.shape_id(), name_si);
|
||||
global.set_shape_id(global_shape);
|
||||
@@ -128,5 +136,14 @@ pub fn bind_error(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
err_ctor.set_native_fn(Some(unsafe {
|
||||
NativeFnPtr::from_raw(oxide_builtins::error::error_constructor::<crate::vm::Vm> as *const ())
|
||||
}));
|
||||
// 主 Error 构造器补 length 槽(值 1,与子类型构造器一致)。
|
||||
let sf = core.perm_interner().as_ref();
|
||||
let sh = core.shape_forge().as_ref();
|
||||
let si_length = sf.intern("length").0;
|
||||
let length_shape = sh.make_shape(err_ctor.shape_id(), si_length);
|
||||
err_ctor.set_shape_id(length_shape);
|
||||
err_ctor.ensure_hash_props().push(JsValue::int(1));
|
||||
let length_pos = err_ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
|
||||
err_ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,6 +327,79 @@ fn error_to_string_non_object_throws() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── 错误对象语义修复测试 ──
|
||||
|
||||
#[test]
|
||||
fn error_message_property_non_enumerable() {
|
||||
let mut vm = make_vm();
|
||||
// message 描述符 enumerable=false(规范 CreateNonEnumerableDataPropertyOrThrow)。
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{}",
|
||||
eval_in(&mut vm, "Object.getOwnPropertyDescriptor(new Error('msg'), 'message').enumerable").unwrap()
|
||||
),
|
||||
"false"
|
||||
);
|
||||
// 可枚举自身键为空:Object.keys / JSON.stringify 不再泄漏 message。
|
||||
assert_eq!(format!("{}", eval_in(&mut vm, "Object.keys(new Error('msg')).length").unwrap()), "0");
|
||||
let r = eval_in(&mut vm, "JSON.stringify(new Error('secret'))").unwrap();
|
||||
assert_eq!(vm.lookup_str(r), Some("{}".to_string()));
|
||||
// Error.prototype 的 name/message 同样非枚举。
|
||||
assert_eq!(format!("{}", eval_in(&mut vm, "Object.keys(Error.prototype).length").unwrap()), "0");
|
||||
// 子类型原型上的 name/constructor 非枚举,for-in 不泄漏。
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{}",
|
||||
eval_in(&mut vm, "var s=[]; for (var k in new TypeError('t')) s.push(k); s.length").unwrap()
|
||||
),
|
||||
"0"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_to_string_tag_is_error() {
|
||||
let mut vm = make_vm();
|
||||
// Error 家族(含子类型与用户子类)经 Object.prototype.toString 得 `[object Error]`。
|
||||
let r = eval_in(&mut vm, "Object.prototype.toString.call(new Error('x'))").unwrap();
|
||||
assert_eq!(vm.lookup_str(r), Some("[object Error]".to_string()));
|
||||
let r = eval_in(&mut vm, "Object.prototype.toString.call(new TypeError('x'))").unwrap();
|
||||
assert_eq!(vm.lookup_str(r), Some("[object Error]".to_string()));
|
||||
let r = eval_in(
|
||||
&mut vm,
|
||||
"class MyError extends Error {}; Object.prototype.toString.call(new MyError('x'))",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(vm.lookup_str(r), Some("[object Error]".to_string()));
|
||||
// Error.prototype 自身同样判定为 Error。
|
||||
let r = eval_in(&mut vm, "Object.prototype.toString.call(Error.prototype)").unwrap();
|
||||
assert_eq!(vm.lookup_str(r), Some("[object Error]".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_to_string_primitive_this_throws() {
|
||||
let mut vm = make_vm();
|
||||
// 规范:Error.prototype.toString 对非对象 this 抛 TypeError(不兜底为 "Error")。
|
||||
let r = eval_in(&mut vm, "try { Error.prototype.toString.call(1); 'no-throw' } catch (e) { e.name }").unwrap();
|
||||
assert_eq!(vm.lookup_str(r), Some("TypeError".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_constructor_length_is_one() {
|
||||
let mut vm = make_vm();
|
||||
assert_eq!(format!("{}", eval_in(&mut vm, "Error.length").unwrap()), "1");
|
||||
assert_eq!(format!("{}", eval_in(&mut vm, "TypeError.length").unwrap()), "1");
|
||||
assert_eq!(format!("{}", eval_in(&mut vm, "EvalError.length").unwrap()), "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_ctor_as_function_call_creates_new_object() {
|
||||
let mut vm = make_vm();
|
||||
// Error.call(obj) 忽略传入 this,总是返回新 Error 对象。
|
||||
assert_eq!(format!("{}", eval_in(&mut vm, "var o = {x:1}; Error.call(o) === o").unwrap()), "false");
|
||||
let r = eval_in(&mut vm, "var o = {x:1}; TypeError.call(o).name").unwrap();
|
||||
assert_eq!(vm.lookup_str(r), Some("TypeError".to_string()));
|
||||
}
|
||||
|
||||
// ── format_error_message 测试 ──
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user