fix: 修复 harness 函数原型和 Object.assign ToObject
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ pub fn bind_string(kernel: &Arc<OxideKernel>, 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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<JsValue> {
|
||||
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();
|
||||
|
||||
@@ -293,7 +293,15 @@ pub fn to_object(val: JsValue, vm: &mut Vm) -> Result<JsValue, &'static str> {
|
||||
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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user