diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index 2397c9e..72fc030 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -151,7 +151,9 @@ impl PropIndex for i32 { /// [29] is_array /// [30] is_extensible /// [31] is_function -/// native_arg_count: u8 (1 byte + 3 pad) +/// native_arg_count: u8 (1 byte) +/// type_tag: u8 — OBJ_TYPE_* constant identifying wrapper/exotic object kind (1 byte) +/// _pad: [u8; 2] /// hash_props: *mut u8 (8 bytes, points to Box>) /// prop_meta: *mut u8 (8 bytes, points to Box>>) /// proto: JsValue (8 bytes) @@ -165,7 +167,8 @@ impl PropIndex for i32 { pub struct JsObject { header: u32, native_arg_count: u8, - _pad: [u8; 3], + pub type_tag: u8, + _pad: [u8; 2], hash_props: *mut u8, prop_meta: *mut u8, proto: JsValue, @@ -179,11 +182,25 @@ pub struct JsObject { } impl JsObject { + pub const OBJ_TYPE_PLAIN: u8 = 0; + pub const OBJ_TYPE_DATE: u8 = 1; + pub const OBJ_TYPE_REGEXP: u8 = 2; + pub const OBJ_TYPE_BOOLEAN_OBJ: u8 = 3; + pub const OBJ_TYPE_NUMBER_OBJ: u8 = 4; + pub const OBJ_TYPE_STRING_OBJ: u8 = 5; + + #[inline] pub fn is_date_obj(&self) -> bool { self.type_tag == Self::OBJ_TYPE_DATE } + #[inline] pub fn is_regexp_obj(&self) -> bool { self.type_tag == Self::OBJ_TYPE_REGEXP } + #[inline] pub fn is_boolean_obj(&self) -> bool { self.type_tag == Self::OBJ_TYPE_BOOLEAN_OBJ } + #[inline] pub fn is_number_obj(&self) -> bool { self.type_tag == Self::OBJ_TYPE_NUMBER_OBJ } + #[inline] pub fn is_string_obj(&self) -> bool { self.type_tag == Self::OBJ_TYPE_STRING_OBJ } + pub fn new_empty(shape_id: ShapeId, proto: JsValue) -> Self { Self { header: (shape_id & 0x00FF_FFFF) | (1 << 30), native_arg_count: 0, - _pad: [0; 3], + type_tag: 0, + _pad: [0; 2], hash_props: std::ptr::null_mut(), prop_meta: std::ptr::null_mut(), proto, @@ -201,7 +218,8 @@ impl JsObject { let mut obj = Self { header: (shape_id & 0x00FF_FFFF) | (1 << 30) | (1 << 29), native_arg_count: 0, - _pad: [0; 3], + type_tag: 0, + _pad: [0; 2], hash_props: std::ptr::null_mut(), prop_meta: std::ptr::null_mut(), proto, diff --git a/crates/oxide_types/src/value.rs b/crates/oxide_types/src/value.rs index 1077fd3..01e6b0b 100644 --- a/crates/oxide_types/src/value.rs +++ b/crates/oxide_types/src/value.rs @@ -158,16 +158,28 @@ impl JsValue { pub fn as_double(&self) -> f64 { debug_assert!(self.is_double(), "JsValue is not a double"); + #[cfg(not(debug_assertions))] + if !self.is_double() { + return f64::NAN; + } f64::from_bits(self.0) } pub fn as_int(&self) -> i32 { debug_assert!(self.is_int(), "JsValue is not an int"); + #[cfg(not(debug_assertions))] + if !self.is_int() { + return 0; + } (self.0 & INT_MASK) as i32 } pub fn as_bool(&self) -> bool { debug_assert!(self.is_bool(), "JsValue is not a bool"); + #[cfg(not(debug_assertions))] + if !self.is_bool() { + return false; + } (self.0 & 1) != 0 } diff --git a/crates/oxide_vm/src/builtins/boolean.rs b/crates/oxide_vm/src/builtins/boolean.rs index 8e65dfc..9450ed2 100644 --- a/crates/oxide_vm/src/builtins/boolean.rs +++ b/crates/oxide_vm/src/builtins/boolean.rs @@ -46,6 +46,7 @@ pub fn boolean_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { } let obj = unsafe { &mut *this_val.as_js_object_ptr() }; + obj.type_tag = JsObject::OBJ_TYPE_BOOLEAN_OBJ; obj.push_prop(JsValue::bool(bool_val)); NativeResult::Ok(this_val) } @@ -59,9 +60,7 @@ pub fn boolean_prototype_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult { )); } let obj = unsafe { &*this_val.as_js_object_ptr() }; - let boolean_proto = vm.kernel().builtin_world().boolean_proto.as_ptr() as *mut JsObject; - let proto_ptr = obj.proto().as_js_object_ptr(); - if proto_ptr.is_null() || !std::ptr::eq(proto_ptr, boolean_proto) { + if !obj.is_boolean_obj() { return NativeResult::Err(crate::builtins::error::create_type_error( vm, "Boolean.prototype.valueOf called on non-Boolean object", @@ -80,9 +79,7 @@ pub fn boolean_prototype_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { )); } let obj = unsafe { &*this_val.as_js_object_ptr() }; - let boolean_proto = vm.kernel().builtin_world().boolean_proto.as_ptr() as *mut JsObject; - let proto_ptr = obj.proto().as_js_object_ptr(); - if proto_ptr.is_null() || !std::ptr::eq(proto_ptr, boolean_proto) { + if !obj.is_boolean_obj() { return NativeResult::Err(crate::builtins::error::create_type_error( vm, "Boolean.prototype.toString called on non-Boolean object", diff --git a/crates/oxide_vm/src/builtins/date.rs b/crates/oxide_vm/src/builtins/date.rs index 6c979f7..486d6cd 100644 --- a/crates/oxide_vm/src/builtins/date.rs +++ b/crates/oxide_vm/src/builtins/date.rs @@ -9,27 +9,16 @@ use crate::native::NativeResult; use crate::vm::Vm; fn get_timestamp(obj: &JsObject) -> f64 { - obj.get_prop_at(0).as_double() + let v = obj.get_prop_at(0); + if v.is_double() { v.as_double() } else { f64::NAN } } fn set_timestamp(obj: &mut JsObject, ms: f64) { obj.set_prop_at(0, JsValue::float(ms)); } -fn is_date(vm: &Vm, obj: &JsObject) -> bool { - let date_proto = vm.kernel().builtin_world().date_proto.as_ptr() as *mut JsObject; - if date_proto.is_null() { - return false; - } - let proto_ptr = obj.proto().as_js_object_ptr(); - if proto_ptr.is_null() { - return false; - } - std::ptr::eq(proto_ptr, date_proto) -} - fn ensure_date(vm: &mut Vm, obj: &JsObject) -> Result<(), JsValue> { - if !is_date(vm, obj) { + if !obj.is_date_obj() { return Err(crate::builtins::error::create_type_error(vm, "called on incompatible receiver")); } Ok(()) @@ -176,7 +165,7 @@ pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { if ptr.is_null() { false } else { - is_date(vm, unsafe { &*ptr }) + unsafe { &*ptr }.is_date_obj() } } { let obj = unsafe { &*val.as_js_object_ptr() }; @@ -201,6 +190,7 @@ pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { EMPTY_SHAPE_ID, JsValue::from_js_object(vm.kernel().builtin_world().date_proto.as_ptr() as *mut JsObject), ); + obj.type_tag = JsObject::OBJ_TYPE_DATE; obj.set_prop_at(0, JsValue::float(timestamp)); let ptr = vm.epoch().alloc(obj); diff --git a/crates/oxide_vm/src/builtins/regexp.rs b/crates/oxide_vm/src/builtins/regexp.rs index 26109cf..1a7f430 100644 --- a/crates/oxide_vm/src/builtins/regexp.rs +++ b/crates/oxide_vm/src/builtins/regexp.rs @@ -21,6 +21,12 @@ fn get_regexp_ptr(vm: &Vm, args: &[u8]) -> Result<*mut JsObject, JsValue> { if ptr.is_null() { return Err(JsValue::string(vm.kernel().string_forge().intern("TypeError: null object").0, 0)); } + if !unsafe { &*ptr }.is_regexp_obj() { + return Err(JsValue::string( + vm.kernel().string_forge().intern("TypeError: RegExp.prototype method called on non-RegExp object").0, + 0, + )); + } Ok(ptr) } @@ -112,6 +118,7 @@ pub fn regexp_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { set_prop(&mut obj, "dotAll", JsValue::bool(false), vm); set_prop(&mut obj, "sticky", JsValue::bool(false), vm); set_prop(&mut obj, "unicode", JsValue::bool(false), vm); + obj.type_tag = JsObject::OBJ_TYPE_REGEXP; let obj_ptr = vm.epoch().alloc(obj); NativeResult::Ok(JsValue::from_js_object(obj_ptr)) diff --git a/crates/oxide_vm/src/builtins/string.rs b/crates/oxide_vm/src/builtins/string.rs index 8aedc69..99afb67 100644 --- a/crates/oxide_vm/src/builtins/string.rs +++ b/crates/oxide_vm/src/builtins/string.rs @@ -11,35 +11,24 @@ 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 NativeResult::Ok(this_val); } - match boxed_primitive_value(this_val) { - Some(v) => NativeResult::Ok(v), - None => NativeResult::Err(crate::builtins::error::create_type_error( - vm, - "String.prototype.valueOf called on non-String object", - )), + if this_val.is_object() { + let ptr = this_val.as_js_object_ptr(); + if !ptr.is_null() { + let obj = unsafe { &*ptr }; + if obj.is_string_obj() { + return NativeResult::Ok(obj.get_prop_at(0)); + } + } } + NativeResult::Err(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 { diff --git a/crates/oxide_vm/src/coercion.rs b/crates/oxide_vm/src/coercion.rs index a26a226..45920ae 100644 --- a/crates/oxide_vm/src/coercion.rs +++ b/crates/oxide_vm/src/coercion.rs @@ -293,19 +293,20 @@ 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 = if val.is_string() { - vm.kernel().builtin_world().string_proto.as_ptr() as *mut JsObject + let (proto_ptr, type_tag) = if val.is_string() { + (vm.kernel().builtin_world().string_proto.as_ptr() as *mut JsObject, JsObject::OBJ_TYPE_STRING_OBJ) } else if val.is_int() || val.is_double() { - vm.kernel().builtin_world().number_proto.as_ptr() as *mut JsObject + (vm.kernel().builtin_world().number_proto.as_ptr() as *mut JsObject, JsObject::OBJ_TYPE_NUMBER_OBJ) } else if val.is_bool() { - vm.kernel().builtin_world().boolean_proto.as_ptr() as *mut JsObject + (vm.kernel().builtin_world().boolean_proto.as_ptr() as *mut JsObject, JsObject::OBJ_TYPE_BOOLEAN_OBJ) } else { - &*vm.object_prototype as *const JsObject as *mut JsObject + (&*vm.object_prototype as *const JsObject as *mut JsObject, JsObject::OBJ_TYPE_PLAIN) }; 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); let obj_ref = unsafe { &mut *obj }; + obj_ref.type_tag = type_tag; obj_ref.ensure_hash_props().push(val); obj_ref.set_prop_count(1); Ok(obj_val)