diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 417f4d1e..51db67a1 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -1250,6 +1250,42 @@ pub fn object_get_prototype_of(vm: &mut H, args: &[u8]) -> NativeResu NativeResult::Ok(unsafe { (*obj_ptr).proto() }) } +/// `Object.setPrototypeOf(obj, proto)`:设置对象的 prototype,返回原值。 +/// +/// # 边界与前提 +/// - `obj` 为 null/undefined 抛 TypeError(RequireObjectCoercible);原始值原样返回, +/// 不装箱。 +/// - `proto` 既非对象也非 null 时抛 TypeError。 +/// - 目标不可设置(原型环)时抛 TypeError;普通对象恒可设置,与 Reflect 版 +/// 按布尔返回的语义不同。 +/// +/// # 副作用 +/// - 目标为对象时改写其 proto 槽并递增 generation。 +pub fn object_set_prototype_of(vm: &mut H, args: &[u8]) -> NativeResult { + let target_val = args.get(1).map(|reg| vm.reg(*reg)).unwrap_or_else(JsValue::undefined); + if target_val.is_null() || target_val.is_undefined() { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Object.setPrototypeOf called on null or undefined", + )); + } + let proto = args.get(2).map(|reg| vm.reg(*reg)).unwrap_or_else(JsValue::undefined); + if !proto.is_object() && !proto.is_null() { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Object.setPrototypeOf: prototype must be an object or null", + )); + } + if !target_val.is_object() { + return NativeResult::Ok(target_val); + } + let target_ptr = target_val.as_js_object_ptr(); + if unsafe { &mut *target_ptr }.set_proto(proto).is_err() { + return NativeResult::Err(crate::error::create_type_error(vm, "Object.setPrototypeOf: cannot set prototype")); + } + NativeResult::Ok(target_val) +} + /// `Object.hasOwn(obj, key)`:对象是否有指定自身属性。 pub fn object_has_own(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 3 { diff --git a/crates/oxide_emit/src/expr/operator/mod.rs b/crates/oxide_emit/src/expr/operator/mod.rs index c37e237b..dc4c8c3f 100644 --- a/crates/oxide_emit/src/expr/operator/mod.rs +++ b/crates/oxide_emit/src/expr/operator/mod.rs @@ -245,6 +245,14 @@ impl Emitter { ctx.inst(Inst::load_const(Operand::Reg(reg), idx)); Ok(reg) } + Expression::StaticMemberExpression(member) if matches!(&member.object, Expression::Super(_)) => { + // super 属性引用不可删除:在求值引用之前直接抛 ReferenceError。 + self.emit_throw_error("ReferenceError", "Cannot delete a super reference", ctx) + } + Expression::ComputedMemberExpression(member) if matches!(&member.object, Expression::Super(_)) => { + // super 属性引用不可删除:键表达式与基均不得先行求值。 + self.emit_throw_error("ReferenceError", "Cannot delete a super reference", ctx) + } Expression::StaticMemberExpression(member) => { let obj_reg = self.emit_expression(&member.object, ctx)?; let prop_name = member.property.name.as_str(); diff --git a/crates/oxide_kernel/src/builtin/bind.rs b/crates/oxide_kernel/src/builtin/bind.rs index abff51b9..b8154485 100644 --- a/crates/oxide_kernel/src/builtin/bind.rs +++ b/crates/oxide_kernel/src/builtin/bind.rs @@ -39,6 +39,7 @@ impl BuiltinWorld { ("defineProperties", methods.define_properties, 2), ("fromEntries", methods.from_entries, 1), ("getPrototypeOf", methods.get_prototype_of, 1), + ("setPrototypeOf", methods.set_prototype_of, 2), ("hasOwn", methods.has_own, 2), ("entries", methods.entries, 1), ("values", methods.values, 1), diff --git a/crates/oxide_kernel/src/builtin/methods.rs b/crates/oxide_kernel/src/builtin/methods.rs index 1ad04d78..b059737d 100644 --- a/crates/oxide_kernel/src/builtin/methods.rs +++ b/crates/oxide_kernel/src/builtin/methods.rs @@ -19,6 +19,7 @@ pub struct ObjectMethods { pub define_properties: *const (), pub from_entries: *const (), pub get_prototype_of: *const (), + pub set_prototype_of: *const (), pub has_own: *const (), pub entries: *const (), pub values: *const (), diff --git a/crates/oxide_vm/src/bindings/bind_object.rs b/crates/oxide_vm/src/bindings/bind_object.rs index 4a570d33..0891a5ba 100644 --- a/crates/oxide_vm/src/bindings/bind_object.rs +++ b/crates/oxide_vm/src/bindings/bind_object.rs @@ -28,6 +28,7 @@ pub fn bind_object(core: &Arc, session: &KernelSession, global: &mut define_properties: oxide_builtins::object::object_define_properties:: as *const (), from_entries: oxide_builtins::object::object_from_entries:: as *const (), get_prototype_of: oxide_builtins::object::object_get_prototype_of:: as *const (), + set_prototype_of: oxide_builtins::object::object_set_prototype_of:: as *const (), has_own: oxide_builtins::object::object_has_own:: as *const (), entries: oxide_builtins::object::object_entries:: as *const (), values: oxide_builtins::object::object_values:: as *const (), diff --git a/crates/oxide_vm/tests/builtins_object_tests.rs b/crates/oxide_vm/tests/builtins_object_tests.rs index 43233563..a3e86725 100644 --- a/crates/oxide_vm/tests/builtins_object_tests.rs +++ b/crates/oxide_vm/tests/builtins_object_tests.rs @@ -316,6 +316,20 @@ fn assign_to_frozen_target_throws() { // -- Symbol 键错位回归(shape 槽位计数含 symbol 键) -- +// Object.setPrototypeOf:对象目标设置原型并返回原对象;proto 非对象/null 抛 TypeError。 +#[test] +fn object_set_prototype_of_sets_prototype_and_validates() { + let (_vm, result) = + eval("var o = {}; (Object.setPrototypeOf(o, null) === o) && (Object.getPrototypeOf(o) === null)").unwrap(); + assert!(result.is_bool() && result.as_bool()); + + let err = match eval("Object.setPrototypeOf({}, 1)") { + Ok(_) => panic!("non-object prototype should fail"), + Err(err) => err, + }; + assert!(err.contains("TypeError"), "unexpected error: {err}"); +} + #[test] fn entries_values_with_interleaved_symbol_key() { let (_vm, result) = eval( diff --git a/crates/oxide_vm/tests/class_tests.rs b/crates/oxide_vm/tests/class_tests.rs index 76432ec7..88842a62 100644 --- a/crates/oxide_vm/tests/class_tests.rs +++ b/crates/oxide_vm/tests/class_tests.rs @@ -862,3 +862,74 @@ fn class_declaration_outer_binding_is_mutable() { let result = eval(&mut vm, "class A { m(){ return 1; } } A = 1; A").unwrap(); assert_num(result, 1.0); } + +// ── delete super 属性引用 ── + +// 派生构造器内 delete super.x:运行期抛 ReferenceError,不落入成员删除。 +#[test] +fn delete_super_property_in_derived_constructor_throws() { + let mut vm = Vm::new(); + let err = + eval(&mut vm, "class C extends Object { constructor() { super(); delete super.x; } } new C()").unwrap_err(); + assert!(err.contains("ReferenceError"), "expected ReferenceError, got: {err}"); +} + +// 实例方法/getter/setter 三种方法体内 delete super.x 均抛 ReferenceError。 +#[test] +fn delete_super_property_in_method_getter_and_setter_throws() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "class C extends Object { \ + method() { delete super.x; } \ + get g() { delete super.x; } \ + set s(v) { delete super.x; } \ + } \ + var names = []; var c = new C(); \ + try { c.method(); } catch (e) { names.push(e.name); } \ + try { c.g; } catch (e) { names.push(e.name); } \ + try { c.s = 1; } catch (e) { names.push(e.name); } \ + names.join(',')", + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap(), "ReferenceError,ReferenceError,ReferenceError"); +} + +// 静态方法 + null 原型链基:基限制不得先于删除判定抛出 TypeError。 +#[test] +fn delete_super_property_in_static_method_with_null_base_throws() { + let mut vm = Vm::new(); + let err = eval( + &mut vm, + "class C { static m() { delete super.x; } } Object.setPrototypeOf(C, null); C.m()", + ) + .unwrap_err(); + assert!(err.contains("ReferenceError"), "expected ReferenceError, got: {err}"); +} + +// 计算键 delete super[key]:键表达式不得求值(toString 不执行即抛 ReferenceError)。 +#[test] +fn delete_super_property_computed_key_not_evaluated_throws() { + let mut vm = Vm::new(); + let err = eval( + &mut vm, + "var key = { toString() { throw new Error('ToPropertyKey performed'); } }; \ + var obj = { m() { delete super[key]; } }; obj.m()", + ) + .unwrap_err(); + assert!(err.contains("ReferenceError"), "expected ReferenceError, got: {err}"); +} + +// this 未初始化时 delete super[键]:键表达式不得求值,基构造器也不得执行。 +#[test] +fn delete_super_property_uninitialized_this_key_not_evaluated_throws() { + let mut vm = Vm::new(); + let err = eval( + &mut vm, + "class Base { constructor() { throw new Error('base constructor called'); } } \ + class Derived extends Base { constructor() { delete super[(super(), 0)]; } } new Derived()", + ) + .unwrap_err(); + assert!(err.contains("ReferenceError"), "expected ReferenceError, got: {err}"); + assert!(!err.contains("base constructor called"), "base constructor must not run: {err}"); +}