From 75b429fac7a0a9e33e7b1e2982496a3cb60b53ae Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 16 Aug 2026 19:10:51 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Error=20=E5=AD=90=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E6=9E=84=E9=80=A0=E5=99=A8=E6=8F=8F=E8=BF=B0=E7=AC=A6=E4=B8=8E?= =?UTF-8?q?=E5=8E=9F=E5=9E=8B=E9=93=BE=E5=AF=B9=E9=BD=90=E8=A7=84=E8=8C=83?= =?UTF-8?q?=EF=BC=88prototype/name=20meta=20+=20[[Prototype]]=20=E6=8C=87?= =?UTF-8?q?=E5=90=91=20Error=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/bindings/bind_error.rs | 11 ++- crates/oxide_vm/src/bindings/mod.rs | 9 ++- crates/oxide_vm/tests/builtins_error_tests.rs | 80 +++++++++++++++++++ 3 files changed, 95 insertions(+), 5 deletions(-) diff --git a/crates/oxide_vm/src/bindings/bind_error.rs b/crates/oxide_vm/src/bindings/bind_error.rs index 361dd346..599a4bc6 100644 --- a/crates/oxide_vm/src/bindings/bind_error.rs +++ b/crates/oxide_vm/src/bindings/bind_error.rs @@ -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)); diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index ad5725cc..82d2ce5c 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -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)); diff --git a/crates/oxide_vm/tests/builtins_error_tests.rs b/crates/oxide_vm/tests/builtins_error_tests.rs index dccb16ac..eacd4059 100644 --- a/crates/oxide_vm/tests/builtins_error_tests.rs +++ b/crates/oxide_vm/tests/builtins_error_tests.rs @@ -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()); +}