diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 1dbd891..064e8df 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -500,29 +500,94 @@ pub fn object_get_own_property_names(vm: &mut H, args: &[u8]) -> Nati pub fn object_define_properties(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 3 { - return NativeResult::Ok(vm.reg(args[1])); + return NativeResult::Err(crate::error::create_type_error( + vm, + "Object.defineProperties: expected at least 2 arguments", + )); } - let _obj_ptr = native_try!(require_obj_arg(vm, args, "defineProperties")); + let target_val = vm.reg(args[1]); + let target_ptr = match require_obj_arg(vm, args, "defineProperties") { + Ok(ptr) => ptr, + Err(err) => return NativeResult::Err(err), + }; let desc_val = vm.reg(args[2]); if !desc_val.is_object() { - return NativeResult::Ok(vm.reg(args[1])); + return NativeResult::Err(crate::error::create_type_error( + vm, + "Object.defineProperties: descriptor must be an object", + )); } - NativeResult::Ok(vm.reg(args[1])) + let desc_ptr = desc_val.as_js_object_ptr(); + if desc_ptr.is_null() { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Object.defineProperties: descriptor must be an object", + )); + } + let prop_keys: Vec<(u32, u32)> = { + let desc = unsafe { &*desc_ptr }; + walk_own_keys(vm, desc) + }; + for (key_si, offset) in prop_keys { + let desc = unsafe { &*desc_ptr }; + let prop_desc_val = desc.get_prop_at(offset); + if !prop_desc_val.is_object() { + continue; + } + let value_si = vm.kernel_core().perm_interner().intern("value").0; + let desc_obj_ptr = prop_desc_val.as_js_object_ptr(); + if desc_obj_ptr.is_null() { + continue; + } + let desc_obj = unsafe { &*desc_obj_ptr }; + let prop_val = if let Some(pos) = vm.kernel_core().shape_forge().lookup_position(desc_obj.shape_id(), value_si) + { + desc_obj.get_prop_at(pos) + } else { + JsValue::undefined() + }; + let target = unsafe { &mut *target_ptr }; + let _ = vm.define_data_property(target, key_si, prop_val, PropAttributes::DEFAULT_DATA); + } + NativeResult::Ok(target_val) } pub fn object_from_entries(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { - return NativeResult::Ok(JsValue::null()); + return NativeResult::Err(crate::error::create_type_error(vm, "Object.fromEntries: expected 1 argument")); } let entries = vm.reg(args[1]); if !entries.is_object() { - return NativeResult::Ok(JsValue::null()); + return NativeResult::Err(crate::error::create_type_error(vm, "Object.fromEntries: argument must be iterable")); } - let obj_ptr = vm.alloc_object(JsObject::new_empty( + let entries_ptr = entries.as_js_object_ptr(); + if entries_ptr.is_null() { + return NativeResult::Err(crate::error::create_type_error(vm, "Object.fromEntries: argument must be iterable")); + } + let obj = vm.alloc_object(JsObject::new_empty( EMPTY_SHAPE_ID, JsValue::from_js_object(vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject), )); - NativeResult::Ok(JsValue::from_js_object(obj_ptr)) + let target_val = JsValue::from_js_object(obj); + let n: usize = unsafe { (*entries_ptr).prop_vec_len() }; + for i in 0..n { + let pair_val = unsafe { (*entries_ptr).get_prop_at(i) }; + if !pair_val.is_object() { + continue; + } + let pair_ptr = pair_val.as_js_object_ptr(); + if pair_ptr.is_null() { + continue; + } + let pair = unsafe { &*pair_ptr }; + let key_val = pair.get_prop_at(0); + let value_val = pair.get_prop_at(1); + let key_str = oxide_runtime_api::to_string(key_val); + let si = vm.kernel_core().perm_interner().intern(&key_str).0; + let promoted = vm.promote_if_needed_for_write_ptr(obj, value_val); + let _ = vm.ordinary_set(unsafe { &mut *obj }, si, promoted, target_val); + } + NativeResult::Ok(target_val) } pub fn object_get_prototype_of(vm: &mut H, args: &[u8]) -> NativeResult { diff --git a/crates/oxide_builtins/tests/object_boundary_tests.rs b/crates/oxide_builtins/tests/object_boundary_tests.rs index 06dc718..9475bb9 100644 --- a/crates/oxide_builtins/tests/object_boundary_tests.rs +++ b/crates/oxide_builtins/tests/object_boundary_tests.rs @@ -142,4 +142,57 @@ fn test_is_extensible_non_object_returns_false() { assert!(result.is_bool() && !result.as_bool(), "non-object should not be extensible per ES spec"); } +#[test] +fn test_is_extensible_no_args_type_error() { + assert!(eval("Object.isExtensible()").is_err()); +} +#[test] +fn test_get_prototype_of_null_throws_type_error() { + assert!(eval("Object.getPrototypeOf(null)").is_err()); +} + +#[test] +fn test_get_prototype_of_undefined_throws_type_error() { + assert!(eval("Object.getPrototypeOf(undefined)").is_err()); +} + +#[test] +fn test_get_prototype_of_plain_object() { + let (_vm, result) = eval("var o = {}; Object.getPrototypeOf(o)").unwrap(); + assert!(result.is_object() || result.is_null()); +} + +#[test] +fn test_has_own_own_property() { + let (_vm, result) = eval("var o = {a:1}; Object.hasOwn(o, 'a')").unwrap(); + assert!(result.is_bool() && result.as_bool()); +} + +#[test] +fn test_has_own_inherited_property() { + let (_vm, result) = eval("Object.hasOwn({}, 'toString')").unwrap(); + assert!(result.is_bool() && !result.as_bool()); +} + +#[test] +fn test_define_properties_no_args_type_error() { + assert!(eval("Object.defineProperties()").is_err()); +} + +#[test] +fn test_define_properties_applies_multiple_props() { + let (_vm, result) = eval("var o = {}; Object.defineProperties(o, {a: {value: 1}, b: {value: 2}}); o.a").unwrap(); + assert!(result.is_int() || result.is_double()); +} + +#[test] +fn test_from_entries_no_args_type_error() { + assert!(eval("Object.fromEntries()").is_err()); +} + +#[test] +fn test_from_entries_empty_array() { + let (_vm, result) = eval("Object.fromEntries([])").unwrap(); + assert!(result.is_object(), "fromEntries([]) should return an empty object"); +}