From 33d269ca3f966d7990b8e9c0b77815b6ad971b60 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 9 Jun 2026 19:46:50 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20harness=20=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E5=8E=9F=E5=9E=8B=E5=92=8C=20Object.assign=20ToObject?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_compiler/src/compiler.rs | 10 +++++ crates/oxide_compiler/src/emitter.rs | 14 +++---- crates/oxide_kernel/src/builtin.rs | 10 ++++- crates/oxide_vm/src/bindings/bind_string.rs | 1 + crates/oxide_vm/src/builtins/object.rs | 7 ++-- crates/oxide_vm/src/builtins/string.rs | 27 ++++++++++++ crates/oxide_vm/src/coercion.rs | 10 ++++- crates/oxide_vm/src/vm.rs | 41 +++++++++++++++++-- .../oxide_vm/tests/builtins_object_tests.rs | 8 ++++ crates/oxide_vm/tests/function_tests.rs | 19 +++++++++ 10 files changed, 131 insertions(+), 16 deletions(-) diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index d3b4dd63..997273f0 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -129,6 +129,16 @@ impl CompileCtx { self.next_reg = checkpoint; } + pub(crate) fn reserve_reg(&mut self, reg: u8) { + let next = reg.wrapping_add(1); + if self.next_reg <= reg { + self.next_reg = next; + } + if self.max_regs < next { + self.max_regs = next; + } + } + pub(crate) fn add_constant(&mut self, c: Constant) -> u16 { if let Some(idx) = self.constants.iter().position(|x| x == &c) { return idx as u16; diff --git a/crates/oxide_compiler/src/emitter.rs b/crates/oxide_compiler/src/emitter.rs index fa854dae..44a6394f 100644 --- a/crates/oxide_compiler/src/emitter.rs +++ b/crates/oxide_compiler/src/emitter.rs @@ -556,14 +556,14 @@ impl Compiler { // 1-indexed: 0 = no sub_module (sentinel) let sub_idx = ctx.sub_modules.len() as u32; - // Add BytecodeFunc constant and emit LOAD_CONST + STORE_VAR + // Use the register allocated during the count/hoist pass. Calling + // lookup_or_global here would allocate an extra register during + // emission and desynchronize later function declaration bindings. + let var_reg = ctx.lookup(&name)?; + ctx.reserve_reg(var_reg); let const_idx = ctx.add_constant(Constant::BytecodeFunc(sub_idx)); - let func_reg = ctx.alloc_reg(); - ctx.emit_load_const(func_reg, const_idx); - - // Get the register that was allocated for this FD in the counter pass - let var_reg = ctx.lookup_or_global(&name); - ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, func_reg, 0)); + ctx.emit_load_const(var_reg, const_idx); + ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, var_reg, 0)); Ok(None) } diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index b367715a..d06fd8c2 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -1,5 +1,5 @@ use oxide_types::mem::P; -use oxide_types::object::JsObject; +use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::value::JsValue; use crate::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; @@ -132,6 +132,7 @@ pub struct StringMethods { pub normalize: *const (), pub match_all: *const (), pub replace_all: *const (), + pub value_of: *const (), } pub struct RegExpMethods { @@ -421,6 +422,12 @@ impl BuiltinWorld { ("hasOwnProperty", methods.has_own_property, 1), ("propertyIsEnumerable", methods.property_is_enumerable, 1), ); + for name in ["hasOwnProperty", "propertyIsEnumerable"] { + let si = string_forge.intern(name).0; + if let Some(pos) = shape_forge.lookup_position(proto.shape_id(), si) { + proto.set_data_meta(pos, PropAttributes::new(true, false, true)); + } + } } pub fn bind_array_methods(&self, methods: &ArrayMethods, string_forge: &StringForge, shape_forge: &ShapeForge) { @@ -528,6 +535,7 @@ impl BuiltinWorld { ("normalize", methods.normalize, 0), ("matchAll", methods.match_all, 1), ("replaceAll", methods.replace_all, 2), + ("valueOf", methods.value_of, 0), ); } diff --git a/crates/oxide_vm/src/bindings/bind_string.rs b/crates/oxide_vm/src/bindings/bind_string.rs index 997cf229..1a222b5a 100644 --- a/crates/oxide_vm/src/bindings/bind_string.rs +++ b/crates/oxide_vm/src/bindings/bind_string.rs @@ -32,6 +32,7 @@ pub fn bind_string(kernel: &Arc, global: &mut JsObject) { normalize: crate::builtins::string::string_normalize as *const (), match_all: crate::builtins::string::string_match_all as *const (), replace_all: crate::builtins::string::string_replace_all as *const (), + value_of: crate::builtins::string::string_value_of as *const (), }; kernel.builtin_world().bind_string_methods( &string_methods, diff --git a/crates/oxide_vm/src/builtins/object.rs b/crates/oxide_vm/src/builtins/object.rs index feca50fa..c5634a6c 100644 --- a/crates/oxide_vm/src/builtins/object.rs +++ b/crates/oxide_vm/src/builtins/object.rs @@ -99,12 +99,11 @@ pub fn object_create(vm: &mut Vm, args: &[u8]) -> NativeResult { pub fn object_assign(vm: &mut Vm, args: &[u8]) -> NativeResult { if args.len() < 2 { - return Err(JsValue::undefined()); + return Err(crate::builtins::error::create_type_error(vm, "Object.assign requires a target")); } let target_val = vm.reg(args[1]); - if !target_val.is_object() { - return Ok(target_val); - } + let target_val = + coercion::to_object(target_val, vm).map_err(|msg| crate::builtins::error::create_type_error(vm, msg))?; let target_ptr = target_val.as_js_object_ptr(); if target_ptr.is_null() { return Ok(target_val); diff --git a/crates/oxide_vm/src/builtins/string.rs b/crates/oxide_vm/src/builtins/string.rs index b9e34e72..ec46b8d9 100644 --- a/crates/oxide_vm/src/builtins/string.rs +++ b/crates/oxide_vm/src/builtins/string.rs @@ -11,6 +11,33 @@ fn this_string(vm: &Vm, args: &[u8]) -> String { coercion::to_string(vm.kernel().string_forge().as_ref(), vm.reg(args[0])) } +fn boxed_primitive_value(this_val: JsValue) -> Option { + if !this_val.is_object() { + return None; + } + let ptr = this_val.as_js_object_ptr(); + if ptr.is_null() { + return None; + } + let obj = unsafe { &*ptr }; + let val = obj.get_prop_at(0); + if val.is_undefined() && obj.prop_count() == 0 { + None + } else { + Some(val) + } +} + +pub fn string_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult { + let this_val = vm.reg(args[0]); + if this_val.is_string() { + return Ok(this_val); + } + boxed_primitive_value(this_val).ok_or_else(|| { + crate::builtins::error::create_type_error(vm, "String.prototype.valueOf called on non-String object") + }) +} + fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue { let proto = vm.kernel().builtin_world().array_proto.as_ptr() as *mut JsObject; let n = parts.len(); diff --git a/crates/oxide_vm/src/coercion.rs b/crates/oxide_vm/src/coercion.rs index 9dfed91e..5a6358cb 100644 --- a/crates/oxide_vm/src/coercion.rs +++ b/crates/oxide_vm/src/coercion.rs @@ -293,7 +293,15 @@ pub fn to_object(val: JsValue, vm: &mut Vm) -> Result { if val.is_null() || val.is_undefined() { return Err("TypeError: Cannot convert null or undefined to object"); } - let proto_ptr = &*vm.object_prototype as *const JsObject as *mut JsObject; + let proto_ptr = if val.is_string() { + vm.kernel().builtin_world().string_proto.as_ptr() as *mut JsObject + } else if val.is_int() || val.is_double() { + vm.kernel().builtin_world().number_proto.as_ptr() as *mut JsObject + } else if val.is_bool() { + vm.kernel().builtin_world().boolean_proto.as_ptr() as *mut JsObject + } else { + &*vm.object_prototype as *const JsObject as *mut JsObject + }; let proto_val = JsValue::from_js_object(proto_ptr); let obj = vm.epoch.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val)); let obj_val = JsValue::from_js_object(obj); diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 97fce85b..2c45afae 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -266,7 +266,32 @@ impl Vm { obj.set_captured_this(self.regs[254]); } let obj_ptr = self.epoch.alloc(obj); - JsValue::object(obj_ptr as *mut u8) + let func_val = JsValue::object(obj_ptr as *mut u8); + + if !is_arrow { + let object_proto_ptr = self.kernel.builtin_world().object_proto.as_ptr() as *mut JsObject; + let prototype_obj = self + .epoch + .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto_ptr))); + let prototype_val = JsValue::from_js_object(prototype_obj); + + let constructor_si = self.kernel.string_forge().intern("constructor").0; + let constructor_shape = self.kernel.shape_forge().make_shape(EMPTY_SHAPE_ID, constructor_si); + let prototype = unsafe { &mut *prototype_obj }; + prototype.set_shape_id(constructor_shape); + let constructor_pos = prototype.push_prop(func_val); + prototype.set_data_meta(constructor_pos, PropAttributes::new(true, false, true)); + prototype.bump_generation(); + + let prototype_si = self.kernel.string_forge().intern("prototype").0; + let func = unsafe { &mut *obj_ptr }; + let prototype_shape = self.kernel.shape_forge().make_shape(func.shape_id(), prototype_si); + func.set_shape_id(prototype_shape); + func.ensure_hash_props().push(Box::new(prototype_val)); + func.bump_generation(); + } + + func_val } fn error_text(&self, val: JsValue) -> String { @@ -1812,8 +1837,18 @@ impl Vm { } if let Some(shape) = self.kernel.shape_forge().get_shape(id) { if shape.property_name != u32::MAX && seen.insert(shape.property_name) { - let hash = self.kernel.string_forge().get_hash(shape.property_name).unwrap_or(0); - keys_vec.push(JsValue::string(shape.property_name, hash)); + let enumerable = self + .kernel + .shape_forge() + .lookup_position(cur.shape_id(), shape.property_name) + .and_then(|pos| cur.prop_meta_at(pos)) + .map(|meta| meta.attributes.enumerable()) + .unwrap_or(PropAttributes::DEFAULT_DATA.enumerable()); + if enumerable { + let hash = + self.kernel.string_forge().get_hash(shape.property_name).unwrap_or(0); + keys_vec.push(JsValue::string(shape.property_name, hash)); + } } cursor = shape.parent; } else { diff --git a/crates/oxide_vm/tests/builtins_object_tests.rs b/crates/oxide_vm/tests/builtins_object_tests.rs index c5873095..cada4ed1 100644 --- a/crates/oxide_vm/tests/builtins_object_tests.rs +++ b/crates/oxide_vm/tests/builtins_object_tests.rs @@ -37,6 +37,14 @@ fn object_assign_copies() { assert!(result.is_object()); } +#[test] +fn object_assign_boxes_primitive_target() { + let (_vm, result) = eval("var result = Object.assign('a'); result.valueOf()").unwrap(); + assert!(result.is_string()); + let rendered = _vm.kernel().string_forge().lookup(result.as_string_index()).unwrap_or_default(); + assert_eq!(rendered, "a"); +} + #[test] fn object_is_uses_same_value_semantics() { let (_vm, result) = eval("Object.is(NaN, NaN)").unwrap(); diff --git a/crates/oxide_vm/tests/function_tests.rs b/crates/oxide_vm/tests/function_tests.rs index 94f69255..8f6f696b 100644 --- a/crates/oxide_vm/tests/function_tests.rs +++ b/crates/oxide_vm/tests/function_tests.rs @@ -188,3 +188,22 @@ fn this_in_constructor_sets_proto() { let result = eval(&mut vm, "function Ctor(m) { this.message = m; } Ctor.prototype = new Object(); 1").unwrap(); assert_eq!(result.as_int(), 1); } + +#[test] +fn function_declaration_has_default_prototype_object() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "function Ctor() {} Ctor.prototype.constructor === Ctor").unwrap(); + assert_eq!(result, JsValue::bool(true)); +} + +#[test] +fn function_default_prototype_accepts_member_assignment() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "function Test262Error(message) { this.message = message || ''; } Test262Error.prototype.toString = function () { return this.message; }; new Test262Error('ok').toString()", + ) + .unwrap(); + let rendered = vm.kernel().string_forge().lookup(result.as_string_index()).unwrap_or_default(); + assert_eq!(rendered, "ok"); +}