fix: Error 子类型构造器描述符与原型链对齐规范(prototype/name meta + [[Prototype]] 指向 Error)

This commit is contained in:
2026-08-16 19:10:51 +08:00
parent d213d697d4
commit 2b74fa81a6
3 changed files with 95 additions and 5 deletions
+8 -3
View File
@@ -12,9 +12,10 @@ fn bind_error_subtype_constructor(
) {
let sf = core.perm_interner().as_ref();
let sh = core.shape_forge().as_ref();
let function_proto_ptr = session.builtin_world().function_proto.as_ptr() as *mut JsObject;
let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto_ptr)));
// 子类型构造器 [[Prototype]] 指向 Error 构造器(规范:NativeError 构造器继承 Error
// 构造器,而非直接继承 Function.prototype;instanceof 走 @@hasInstance 不受影响)。
let error_ctor_ptr = session.builtin_world().error_constructor.as_ptr() as *mut JsObject;
let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(error_ctor_ptr)));
ctor.set_function(true);
// Error 子类型须标记可构造:new TypeError(...) 走 NEW_EXPRESSION 校验。
ctor.type_tag = JsObject::OBJ_TYPE_CONSTRUCTOR;
@@ -34,6 +35,10 @@ fn bind_error_subtype_constructor(
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(arg_count as i32));
// 构造器 prototype 槽按规范 { writable:false, enumerable:false, configurable:false }。
ctor.set_data_meta(0u32, PropAttributes::new(false, false, false));
// 构造器 name 槽按规范 { writable:false, enumerable:false, configurable:true }。
ctor.set_data_meta(1u32, PropAttributes::new(false, false, true));
// 构造器 length 按规范为不可写不可枚举(Function.length 属性描述符约定)。
ctor.set_data_meta(2u32, PropAttributes::new(false, false, true));
+7 -2
View File
@@ -588,8 +588,10 @@ fn bind_error_subtype_global(
}
}
let function_proto_ptr = session.builtin_world().function_proto.as_ptr() as *mut JsObject;
let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto_ptr)));
// fallback 自建路径与 bind_error 的 Box 自建路径保持同一形态:[[Prototype]] 指向
// Error 构造器(NativeError 构造器继承 Error 构造器),prototype/name 描述符按规范。
let error_ctor_ptr = session.builtin_world().error_constructor.as_ptr() as *mut JsObject;
let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(error_ctor_ptr)));
ctor.set_function(true);
configure_native_constructor(&mut ctor, ctor_fn, arg_count);
@@ -604,6 +606,9 @@ fn bind_error_subtype_global(
ctor.ensure_hash_props()
.push(JsValue::from_js_object(proto.as_ptr() as *mut JsObject));
ctor.ensure_hash_props().push(JsValue::perm_string(sf.string_ptr(name_si)));
// prototype/name 描述符与 Box 自建路径一致:{f,f,f} / {f,f,t}。
ctor.set_data_meta(0u32, PropAttributes::new(false, false, false));
ctor.set_data_meta(1u32, PropAttributes::new(false, false, true));
let ctor_ptr = Box::into_raw(ctor);
bind_existing_global(core, global, name, JsValue::from_js_object(ctor_ptr));
@@ -1,6 +1,7 @@
use oxide_builtins::error;
use oxide_compiler::compiler::Compiler;
use oxide_types::mem::P;
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
@@ -575,3 +576,82 @@ fn create_suppressed_error_direct_call() {
let meta = obj.prop_meta_at(err_pos).unwrap();
assert!(!meta.attributes.enumerable(), "error 槽应非枚举");
}
// ── 子类型构造器描述符与原型链测试 ──
#[test]
fn error_subtype_constructor_descriptors() {
let mut vm = make_vm();
// 构造器 prototype 槽描述符 {f,f,f}(子类型与 SuppressedError 同规)。
let r = eval_in(
&mut vm,
"var d = Object.getOwnPropertyDescriptor(TypeError, 'prototype'); [d.writable, d.enumerable, d.configurable].join(',')",
)
.unwrap();
assert_eq!(vm.lookup_str(r), Some("false,false,false".to_string()));
// name 槽描述符 {f,f,t}。
let r = eval_in(
&mut vm,
"var d = Object.getOwnPropertyDescriptor(SuppressedError, 'name'); [d.writable, d.enumerable, d.configurable].join(',')",
)
.unwrap();
assert_eq!(vm.lookup_str(r), Some("false,false,true".to_string()));
// length 槽描述符 {f,f,t}。
let r = eval_in(
&mut vm,
"var d = Object.getOwnPropertyDescriptor(TypeError, 'length'); [d.writable, d.enumerable, d.configurable].join(',')",
)
.unwrap();
assert_eq!(vm.lookup_str(r), Some("false,false,true".to_string()));
}
#[test]
fn error_subtype_constructor_proto_is_error_ctor() {
let mut vm = make_vm();
// NativeError 子类型构造器 [[Prototype]] === Error 构造器(instanceof 走 @@hasInstance 不受影响)。
let r = eval_in(&mut vm, "Object.getPrototypeOf(TypeError) === Error").unwrap();
assert_eq!(format!("{}", r), "true");
let r = eval_in(&mut vm, "Object.getPrototypeOf(SuppressedError) === Error").unwrap();
assert_eq!(format!("{}", r), "true");
let r = eval_in(&mut vm, "new TypeError('x') instanceof TypeError").unwrap();
assert_eq!(format!("{}", r), "true");
}
// ── full_reset 重建路径测试 ──
#[test]
fn suppressed_error_survives_error_family_reset() {
let mut vm = make_vm();
// error_family 脏:suppressed_error_proto 与构造器整体重建。
let proto_ptr = P::as_ptr(&vm.session().builtin_world().suppressed_error_proto) as *mut JsObject;
unsafe { (&mut *proto_ptr).bump_generation() };
vm.full_reset();
// 重建后 length=3、构造器 [[Prototype]]=Error、原型链到 Error.prototype 均不回落。
let r = eval_in(&mut vm, "SuppressedError.length").unwrap();
assert_eq!(format!("{}", r), "3");
let r = eval_in(&mut vm, "Object.getPrototypeOf(SuppressedError) === Error").unwrap();
assert_eq!(format!("{}", r), "true");
let r = eval_in(&mut vm, "Object.getPrototypeOf(SuppressedError.prototype) === Error.prototype").unwrap();
assert_eq!(format!("{}", r), "true");
let r = eval_in(&mut vm, "new SuppressedError('e','s','m').message").unwrap();
assert_eq!(vm.lookup_str(r), Some("m".to_string()));
assert!(!vm.session().is_dirty_since_snapshot());
}
#[test]
fn suppressed_error_length_kept_after_global_only_reset() {
let mut vm = make_vm();
// 仅 global 脏(error_family 干净):fallback 自建构造器分支须保留 length=3。
let g_ptr = vm.session().global_object().as_ptr() as *mut JsObject;
unsafe { (&mut *g_ptr).bump_generation() };
vm.full_reset();
let r = eval_in(&mut vm, "SuppressedError.length").unwrap();
assert_eq!(format!("{}", r), "3");
let r = eval_in(&mut vm, "Object.getPrototypeOf(SuppressedError) === Error").unwrap();
assert_eq!(format!("{}", r), "true");
let r = eval_in(&mut vm, "new SuppressedError('e','s').error").unwrap();
assert_eq!(vm.lookup_str(r), Some("e".to_string()));
assert!(!vm.session().is_dirty_since_snapshot());
}