From e8506e6f295145e6a4de51379c02d08d91a4049d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Mon, 10 Aug 2026 20:08:40 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Object=20=E5=86=85=E7=BD=AE=E6=96=B9?= =?UTF-8?q?=E6=B3=95=20ToObject=20=E8=A3=85=E7=AE=B1=EF=BC=88=E5=8E=9F?= =?UTF-8?q?=E5=A7=8B=E5=80=BC=E4=B8=8D=E5=86=8D=E8=AF=AF=E6=8A=9B=20TypeEr?= =?UTF-8?q?ror=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/object.rs | 72 +++++++++---------- .../tests/object_boundary_tests.rs | 20 +++--- 2 files changed, 45 insertions(+), 47 deletions(-) diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index a3c7661..0f62bcd 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -607,14 +607,11 @@ pub fn object_define_property(vm: &mut H, args: &[u8]) -> NativeResul "Object.defineProperty: expected at least 3 arguments", )); } - let obj_val = vm.reg(args[1]); - if !obj_val.is_object() { - return NativeResult::Err(crate::error::create_type_error(vm, "Object.defineProperty called on non-object")); - } + let obj_val = match oxide_runtime_api::to_object(vm.reg(args[1]), vm) { + Ok(v) => v, + Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)), + }; let obj_ptr = obj_val.as_js_object_ptr(); - if obj_ptr.is_null() { - return NativeResult::Err(crate::error::create_type_error(vm, "Object.defineProperty called on non-object")); - } // well-known symbol 等特殊键统一走 property_key_si(映射到各自的 Symbol 键), // 保证与计算属性访问、Reflect.defineProperty 等读键路径一致。 let si = vm.property_key_si(vm.reg(args[2])); @@ -634,14 +631,11 @@ pub fn object_get_own_property_descriptor(vm: &mut H, args: &[u8]) -> "Object.getOwnPropertyDescriptor called on non-object", )); } - let obj_val = vm.reg(args[1]); - if !obj_val.is_object() { - return NativeResult::Ok(JsValue::undefined()); - } + let obj_val = match oxide_runtime_api::to_object(vm.reg(args[1]), vm) { + Ok(v) => v, + Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)), + }; let obj_ptr = obj_val.as_js_object_ptr(); - if obj_ptr.is_null() { - return NativeResult::Ok(JsValue::undefined()); - } let prop_name_str = oxide_runtime_api::to_string(vm.reg(args[2])); let si = vm.kernel_core().perm_interner().intern(&prop_name_str).0; @@ -717,14 +711,12 @@ fn require_obj_arg(vm: &mut H, args: &[u8], fn_name: &str) -> Result< return Err(crate::error::create_type_error(vm, &format!("Object.{fn_name} called on non-object"))); } let val = vm.reg(args[1]); - if !val.is_object() { - return Err(crate::error::create_type_error(vm, &format!("Object.{fn_name} called on non-object"))); - } - let ptr = val.as_js_object_ptr(); - if ptr.is_null() { - return Err(crate::error::create_type_error(vm, &format!("Object.{fn_name} called on non-object"))); - } - Ok(ptr) + // ToObject:原始值装箱后返回其对象指针;null/undefined 抛 TypeError。 + let obj_val = match oxide_runtime_api::to_object(val, vm) { + Ok(v) => v, + Err(msg) => return Err(crate::error::create_type_error(vm, &msg)), + }; + Ok(obj_val.as_js_object_ptr()) } macro_rules! native_try { @@ -1044,36 +1036,40 @@ pub fn object_proto_to_string(vm: &mut H, args: &[u8]) -> NativeResul } /// `Object.prototype.hasOwnProperty(key)`:this 是否有指定自身属性。 +/// +/// # 步骤 +/// 1. 先 ToPropertyKey 求键(spec 顺序:键先于 ToObject)。 +/// 2. ToObject 装箱 this(null/undefined 抛 TypeError,原始值装箱后查 own 槽)。 pub fn object_proto_has_own_property(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::bool(false)); } - let this_val = vm.reg(args[0]); - if !this_val.is_object() || this_val.as_js_object_ptr().is_null() { - return NativeResult::Err(crate::error::create_type_error( - vm, - "Object.prototype.hasOwnProperty called on non-object", - )); - } let key_si = vm.property_key_si(vm.reg(args[1])); - let obj = unsafe { &*this_val.as_js_object_ptr() }; + let this_val = vm.reg(args[0]); + let obj_val = match oxide_runtime_api::to_object(this_val, vm) { + Ok(v) => v, + Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)), + }; + let obj = unsafe { &*obj_val.as_js_object_ptr() }; NativeResult::Ok(JsValue::bool(vm.get_own_property_slot(obj, key_si).is_some())) } /// `Object.prototype.propertyIsEnumerable(key)`:指定自身属性是否可枚举。 +/// +/// # 步骤 +/// 1. 先 ToPropertyKey 求键。 +/// 2. ToObject 装箱 this(null/undefined 抛 TypeError)。 pub fn object_proto_property_is_enumerable(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::bool(false)); } - let this_val = vm.reg(args[0]); - if !this_val.is_object() || this_val.as_js_object_ptr().is_null() { - return NativeResult::Err(crate::error::create_type_error( - vm, - "Object.prototype.propertyIsEnumerable called on non-object", - )); - } let key_si = vm.property_key_si(vm.reg(args[1])); - let obj = unsafe { &*this_val.as_js_object_ptr() }; + let this_val = vm.reg(args[0]); + let obj_val = match oxide_runtime_api::to_object(this_val, vm) { + Ok(v) => v, + Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)), + }; + let obj = unsafe { &*obj_val.as_js_object_ptr() }; let Some(pos) = vm.get_own_property_slot(obj, key_si) else { return NativeResult::Ok(JsValue::bool(false)); }; diff --git a/crates/oxide_builtins/tests/object_boundary_tests.rs b/crates/oxide_builtins/tests/object_boundary_tests.rs index 86a8a50..02ee479 100644 --- a/crates/oxide_builtins/tests/object_boundary_tests.rs +++ b/crates/oxide_builtins/tests/object_boundary_tests.rs @@ -17,8 +17,10 @@ fn object_keys_no_args_type_error() { } #[test] -fn object_keys_non_object_type_error() { - assert!(eval("Object.keys(42)").is_err(), "Object.keys(42) should throw TypeError"); +fn object_keys_boxes_primitive() { + // ToObject 装箱:Object.keys(42) 返回装箱对象的自身键(空)。 + let (_vm, result) = eval("Object.keys(42).join(',')").unwrap(); + assert!(result.is_string() && unsafe { &*result.as_string_ptr() }.as_str() == ""); } #[test] @@ -51,11 +53,9 @@ fn object_get_own_property_names_no_args_type_error() { } #[test] -fn object_get_own_property_names_non_object_type_error() { - assert!( - eval("Object.getOwnPropertyNames(42)").is_err(), - "Object.getOwnPropertyNames(42) should throw TypeError" - ); +fn object_get_own_property_names_boxes_primitive() { + let (_vm, result) = eval("Object.getOwnPropertyNames(42).join(',')").unwrap(); + assert!(result.is_string(), "Object.getOwnPropertyNames(42) should box the primitive"); } #[test] @@ -227,8 +227,10 @@ fn test_get_own_property_descriptor_no_args_type_error() { } #[test] -fn test_has_own_property_non_object_this_type_error() { - assert!(eval("Object.prototype.hasOwnProperty.call(42, 'x')").is_err()); +fn test_has_own_property_boxes_primitive_this() { + // ToObject 装箱 this:原始值上 hasOwnProperty 返回 false 而非抛错。 + let (_vm, result) = eval("Object.prototype.hasOwnProperty.call(42, 'x')").unwrap(); + assert!(result.is_bool() && !result.as_bool()); } #[test]