From e588fa4c8ca888e648ae0b0f024994567f61d9cb 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: Tue, 9 Jun 2026 19:31:46 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BB=91=E5=AE=9A=20Function=20?= =?UTF-8?q?=E5=92=8C=20Object=20prototype=20harness=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_kernel/src/builtin.rs | 13 ++++++ crates/oxide_vm/src/bindings/bind_function.rs | 9 +++- crates/oxide_vm/src/bindings/bind_object.rs | 2 + crates/oxide_vm/src/bindings/mod.rs | 2 +- crates/oxide_vm/src/builtins/object.rs | 45 +++++++++++++++++-- .../oxide_vm/tests/builtins_function_tests.rs | 19 ++++++++ .../oxide_vm/tests/builtins_object_tests.rs | 20 ++++++++- 7 files changed, 103 insertions(+), 7 deletions(-) diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index 27a0f10..b367715 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -58,6 +58,8 @@ pub struct ObjectMethods { pub has_own: *const (), pub entries: *const (), pub values: *const (), + pub has_own_property: *const (), + pub property_is_enumerable: *const (), } pub struct ArrayMethods { @@ -408,6 +410,17 @@ impl BuiltinWorld { ("entries", methods.entries, 1), ("values", methods.values, 1), ); + + let proto_ptr = P::as_ptr(&self.object_proto) as *mut JsObject; + let proto = unsafe { &mut *proto_ptr }; + bind_methods!( + self, + proto, + string_forge, + shape_forge, + ("hasOwnProperty", methods.has_own_property, 1), + ("propertyIsEnumerable", methods.property_is_enumerable, 1), + ); } pub fn bind_array_methods(&self, methods: &ArrayMethods, string_forge: &StringForge, shape_forge: &ShapeForge) { diff --git a/crates/oxide_vm/src/bindings/bind_function.rs b/crates/oxide_vm/src/bindings/bind_function.rs index 2987541..28a7f37 100644 --- a/crates/oxide_vm/src/bindings/bind_function.rs +++ b/crates/oxide_vm/src/bindings/bind_function.rs @@ -2,8 +2,12 @@ use std::sync::Arc; use oxide_kernel::builtin::FunctionMethods; use oxide_kernel::kernel::OxideKernel; +use oxide_types::object::JsObject; +use oxide_types::value::JsValue; -pub fn bind_function(kernel: &Arc) { +use super::bind_global_value; + +pub fn bind_function(kernel: &Arc, global: &mut JsObject) { let function_methods = FunctionMethods { call: crate::builtins::function::function_call as *const (), apply: crate::builtins::function::function_apply as *const (), @@ -15,4 +19,7 @@ pub fn bind_function(kernel: &Arc) { kernel.string_forge().as_ref(), kernel.shape_forge().as_ref(), ); + + let function_ctor = kernel.builtin_world().function_constructor.as_ptr() as *mut JsObject; + bind_global_value(kernel, global, "Function", JsValue::from_js_object(function_ctor)); } diff --git a/crates/oxide_vm/src/bindings/bind_object.rs b/crates/oxide_vm/src/bindings/bind_object.rs index 7e0d81c..6a661c4 100644 --- a/crates/oxide_vm/src/bindings/bind_object.rs +++ b/crates/oxide_vm/src/bindings/bind_object.rs @@ -26,6 +26,8 @@ pub fn bind_object(kernel: &Arc, global: &mut JsObject) { has_own: crate::builtins::object::object_has_own as *const (), entries: crate::builtins::object::object_entries as *const (), values: crate::builtins::object::object_values as *const (), + has_own_property: crate::builtins::object::object_proto_has_own_property as *const (), + property_is_enumerable: crate::builtins::object::object_proto_property_is_enumerable as *const (), }; kernel .builtin_world() diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 751929f..38c01d1 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -87,7 +87,7 @@ pub fn init_kernel_builtins(kernel: &Arc) { bind_set::bind_set(kernel, global); bind_map::bind_map(kernel, global); bind_boolean::bind_boolean(kernel, global); - bind_function::bind_function(kernel); + bind_function::bind_function(kernel, global); bind_regexp::bind_regexp(kernel, global); bind_symbol::bind_symbol(kernel, global); diff --git a/crates/oxide_vm/src/builtins/object.rs b/crates/oxide_vm/src/builtins/object.rs index 89ce3c9..feca50f 100644 --- a/crates/oxide_vm/src/builtins/object.rs +++ b/crates/oxide_vm/src/builtins/object.rs @@ -447,12 +447,49 @@ pub fn object_has_own(vm: &mut Vm, args: &[u8]) -> NativeResult { if args.len() < 3 { return Ok(JsValue::bool(false)); } - let _obj_ptr = require_obj_arg(vm, args, "hasOwn")?; - let key_val = vm.reg(args[2]); - if !key_val.is_string() { + let obj_ptr = require_obj_arg(vm, args, "hasOwn")?; + let key_si = vm.property_key_si(vm.reg(args[2])); + let obj = unsafe { &*obj_ptr }; + Ok(JsValue::bool(vm.get_own_property_slot(obj, key_si).is_some())) +} + +pub fn object_proto_has_own_property(vm: &mut Vm, args: &[u8]) -> NativeResult { + if args.len() < 2 { return Ok(JsValue::bool(false)); } - 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 Err(crate::builtins::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() }; + Ok(JsValue::bool(vm.get_own_property_slot(obj, key_si).is_some())) +} + +pub fn object_proto_property_is_enumerable(vm: &mut Vm, args: &[u8]) -> NativeResult { + if args.len() < 2 { + return 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 Err(crate::builtins::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 Some(pos) = vm.get_own_property_slot(obj, key_si) else { + return Ok(JsValue::bool(false)); + }; + let enumerable = obj + .prop_meta_at(pos) + .map(|meta| meta.attributes.enumerable()) + .unwrap_or(PropAttributes::DEFAULT_DATA.enumerable()); + Ok(JsValue::bool(enumerable)) } pub fn object_entries(vm: &mut Vm, args: &[u8]) -> NativeResult { diff --git a/crates/oxide_vm/tests/builtins_function_tests.rs b/crates/oxide_vm/tests/builtins_function_tests.rs index d2b294a..9bd8d45 100644 --- a/crates/oxide_vm/tests/builtins_function_tests.rs +++ b/crates/oxide_vm/tests/builtins_function_tests.rs @@ -30,6 +30,25 @@ fn function_bind_creates_wrapper() { assert!((result.as_double() - 5.0).abs() < 0.0001); } +#[test] +fn function_constructor_is_global() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "typeof Function").unwrap(); + let rendered = vm.kernel().string_forge().lookup(result.as_string_index()).unwrap_or_default(); + assert_eq!(rendered, "function"); +} + +#[test] +fn function_call_bind_supports_uncurried_native_methods() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var __push = Function.prototype.call.bind(Array.prototype.push); var a = []; __push(a, 'x'); a.length", + ) + .unwrap(); + assert_eq!(result, JsValue::int(1)); +} + #[test] fn function_call_invokes_bytecode_function() { let mut vm = Vm::new(); diff --git a/crates/oxide_vm/tests/builtins_object_tests.rs b/crates/oxide_vm/tests/builtins_object_tests.rs index daba6a2..c587309 100644 --- a/crates/oxide_vm/tests/builtins_object_tests.rs +++ b/crates/oxide_vm/tests/builtins_object_tests.rs @@ -118,7 +118,25 @@ fn object_get_prototype_of() { #[test] fn object_has_own_true() { let (_vm, result) = eval("Object.hasOwn({a:1}, 'a')").unwrap(); - assert!(result.is_bool()); + assert_eq!(result, JsValue::bool(true)); +} + +#[test] +fn object_has_own_false_for_inherited_property() { + let (_vm, result) = eval("Object.hasOwn({}, 'toString')").unwrap(); + assert_eq!(result, JsValue::bool(false)); +} + +#[test] +fn object_proto_has_own_property_call_works() { + let (_vm, result) = eval("Object.prototype.hasOwnProperty.call(Object, 'assign')").unwrap(); + assert_eq!(result, JsValue::bool(true)); +} + +#[test] +fn object_proto_property_is_enumerable_call_works() { + let (_vm, result) = eval("Object.prototype.propertyIsEnumerable.call(Object, 'assign')").unwrap(); + assert_eq!(result, JsValue::bool(true)); } #[test]