diff --git a/crates/oxide_vm/src/builtins/error.rs b/crates/oxide_vm/src/builtins/error.rs index ef4fb60a..b704b9c6 100644 --- a/crates/oxide_vm/src/builtins/error.rs +++ b/crates/oxide_vm/src/builtins/error.rs @@ -36,34 +36,41 @@ fn create_error_object(vm: &mut Vm, error_proto_ptr: *mut JsObject, name: &str, JsValue::from_js_object(obj) } +pub fn create_kind_error(vm: &mut Vm, kind: &str, msg: &str) -> JsValue { + let proto_ptr = match kind { + "TypeError" => P::as_ptr(&vm.session().builtin_world().type_error_proto) as *mut JsObject, + "RangeError" => P::as_ptr(&vm.session().builtin_world().range_error_proto) as *mut JsObject, + "ReferenceError" => P::as_ptr(&vm.session().builtin_world().reference_error_proto) as *mut JsObject, + "SyntaxError" => P::as_ptr(&vm.session().builtin_world().syntax_error_proto) as *mut JsObject, + "URIError" => P::as_ptr(&vm.session().builtin_world().uri_error_proto) as *mut JsObject, + "EvalError" => P::as_ptr(&vm.session().builtin_world().eval_error_proto) as *mut JsObject, + _ => P::as_ptr(&vm.session().builtin_world().error_proto) as *mut JsObject, + }; + create_error_object(vm, proto_ptr, kind, msg) +} + pub fn create_type_error(vm: &mut Vm, msg: &str) -> JsValue { - let proto_ptr = P::as_ptr(&vm.session().builtin_world().type_error_proto) as *mut JsObject; - create_error_object(vm, proto_ptr, "TypeError", msg) + create_kind_error(vm, "TypeError", msg) } pub fn create_error(vm: &mut Vm, msg: &str) -> JsValue { - let proto_ptr = P::as_ptr(&vm.session().builtin_world().error_proto) as *mut JsObject; - create_error_object(vm, proto_ptr, "Error", msg) + create_kind_error(vm, "Error", msg) } pub fn create_reference_error(vm: &mut Vm, msg: &str) -> JsValue { - let proto_ptr = P::as_ptr(&vm.session().builtin_world().reference_error_proto) as *mut JsObject; - create_error_object(vm, proto_ptr, "ReferenceError", msg) + create_kind_error(vm, "ReferenceError", msg) } pub fn create_range_error(vm: &mut Vm, msg: &str) -> JsValue { - let proto_ptr = P::as_ptr(&vm.session().builtin_world().range_error_proto) as *mut JsObject; - create_error_object(vm, proto_ptr, "RangeError", msg) + create_kind_error(vm, "RangeError", msg) } pub fn create_syntax_error(vm: &mut Vm, msg: &str) -> JsValue { - let proto_ptr = P::as_ptr(&vm.session().builtin_world().syntax_error_proto) as *mut JsObject; - create_error_object(vm, proto_ptr, "SyntaxError", msg) + create_kind_error(vm, "SyntaxError", msg) } pub fn create_uri_error(vm: &mut Vm, msg: &str) -> JsValue { - let proto_ptr = P::as_ptr(&vm.session().builtin_world().uri_error_proto) as *mut JsObject; - create_error_object(vm, proto_ptr, "URIError", msg) + create_kind_error(vm, "URIError", msg) } fn get_msg(vm: &mut Vm, args: &[u8]) -> String { @@ -137,7 +144,7 @@ pub fn error_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { } else { ("Error".to_string(), String::new()) }; - let result = if msg.is_empty() { name } else { format!("{}: {}", name, msg) }; + let result = crate::vm::format_error_message(&name, &msg); let si = vm.kernel_core().string_forge().intern(&result).0; NativeResult::Ok(JsValue::string(si, 0)) } diff --git a/crates/oxide_vm/src/builtins/object.rs b/crates/oxide_vm/src/builtins/object.rs index e317aad8..a2445d8e 100644 --- a/crates/oxide_vm/src/builtins/object.rs +++ b/crates/oxide_vm/src/builtins/object.rs @@ -105,7 +105,7 @@ pub fn object_assign(vm: &mut Vm, args: &[u8]) -> NativeResult { let target_val = vm.reg(args[1]); let target_val = match coercion::to_object(target_val, vm) { Ok(val) => val, - Err(msg) => return NativeResult::Err(crate::builtins::error::create_type_error(vm, msg)), + Err(msg) => return NativeResult::Err(crate::builtins::error::create_type_error(vm, &msg)), }; let target_ptr = target_val.as_js_object_ptr(); if target_ptr.is_null() { diff --git a/crates/oxide_vm/src/builtins/regexp.rs b/crates/oxide_vm/src/builtins/regexp.rs index 629c2288..1c32d52c 100644 --- a/crates/oxide_vm/src/builtins/regexp.rs +++ b/crates/oxide_vm/src/builtins/regexp.rs @@ -6,28 +6,22 @@ use crate::coercion; use crate::native::NativeResult; use crate::vm::Vm; -fn get_regexp_ptr(vm: &Vm, args: &[u8]) -> Result<*mut JsObject, JsValue> { +fn get_regexp_ptr(vm: &mut Vm, args: &[u8]) -> Result<*mut JsObject, JsValue> { let this_val = vm.reg(args[0]); if !this_val.is_object() { - return Err(JsValue::string( - vm.kernel_core() - .string_forge() - .intern("TypeError: RegExp.prototype method called on non-object") - .0, - 0, + return Err(crate::builtins::error::create_type_error( + vm, + "RegExp.prototype method called on non-object", )); } let ptr = this_val.as_js_object_ptr(); if ptr.is_null() { - return Err(JsValue::string(vm.kernel_core().string_forge().intern("TypeError: null object").0, 0)); + return Err(crate::builtins::error::create_type_error(vm, "null object")); } if !unsafe { &*ptr }.is_regexp_obj() { - return Err(JsValue::string( - vm.kernel_core() - .string_forge() - .intern("TypeError: RegExp.prototype method called on non-RegExp object") - .0, - 0, + return Err(crate::builtins::error::create_type_error( + vm, + "RegExp.prototype method called on non-RegExp object", )); } Ok(ptr) @@ -107,8 +101,10 @@ pub fn regexp_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(re_ptr as *const ()) })); } Err(e) => { - let msg = format!("SyntaxError: Invalid regular expression: {}", e); - return NativeResult::Err(JsValue::string(vm.kernel_core().string_forge().intern(&msg).0, 0)); + return NativeResult::Err(crate::builtins::error::create_syntax_error( + vm, + &format!("Invalid regular expression: {e}"), + )); } } @@ -152,10 +148,7 @@ pub fn regexp_test(vm: &mut Vm, args: &[u8]) -> NativeResult { let fn_ptr = match re.native_fn() { None => { - return NativeResult::Err(JsValue::string( - vm.kernel_core().string_forge().intern("TypeError: invalid RegExp").0, - 0, - )); + return NativeResult::Err(crate::builtins::error::create_type_error(vm, "invalid RegExp")); } Some(p) => p, }; @@ -187,10 +180,7 @@ pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult { let re = unsafe { &*re_ptr }; match re.native_fn() { None => { - return NativeResult::Err(JsValue::string( - vm.kernel_core().string_forge().intern("TypeError: invalid RegExp").0, - 0, - )); + return NativeResult::Err(crate::builtins::error::create_type_error(vm, "invalid RegExp")); } Some(p) => p, } diff --git a/crates/oxide_vm/src/builtins/stubs.rs b/crates/oxide_vm/src/builtins/stubs.rs index 60b22df3..1f985c5d 100644 --- a/crates/oxide_vm/src/builtins/stubs.rs +++ b/crates/oxide_vm/src/builtins/stubs.rs @@ -1,12 +1,8 @@ -use oxide_types::value::JsValue; - use crate::native::NativeResult; use crate::vm::Vm; fn stub_error(vm: &mut Vm, name: &str) -> NativeResult { - let msg = format!("TypeError: {} is not implemented", name); - let si = vm.kernel_core().string_forge().intern(&msg).0; - NativeResult::Err(JsValue::string(si, 0)) + NativeResult::Err(crate::builtins::error::create_type_error(vm, &format!("{name} is not implemented"))) } // Architecturally deferred — see 13.5-CONTEXT.md for rationale diff --git a/crates/oxide_vm/src/builtins/symbol.rs b/crates/oxide_vm/src/builtins/symbol.rs index e23afa26..501a3570 100644 --- a/crates/oxide_vm/src/builtins/symbol.rs +++ b/crates/oxide_vm/src/builtins/symbol.rs @@ -17,12 +17,9 @@ pub fn symbol_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { if !proto_ptr.is_null() { let sp = vm.session().builtin_world().symbol_proto.as_ptr() as *mut JsObject; if std::ptr::eq(proto_ptr, sp) { - return NativeResult::Err(JsValue::string( - vm.kernel_core() - .string_forge() - .intern("TypeError: Symbol is not a constructor") - .0, - 0, + return NativeResult::Err(crate::builtins::error::create_type_error( + vm, + "Symbol is not a constructor", )); } } @@ -45,12 +42,9 @@ pub fn symbol_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { pub fn symbol_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { let this_val = vm.reg(args[0]); if !this_val.is_symbol() { - return NativeResult::Err(JsValue::string( - vm.kernel_core() - .string_forge() - .intern("TypeError: Symbol.prototype.toString requires a Symbol") - .0, - 0, + return NativeResult::Err(crate::builtins::error::create_type_error( + vm, + "Symbol.prototype.toString requires a Symbol", )); } diff --git a/crates/oxide_vm/src/coercion.rs b/crates/oxide_vm/src/coercion.rs index 3cd83104..2cc428a0 100644 --- a/crates/oxide_vm/src/coercion.rs +++ b/crates/oxide_vm/src/coercion.rs @@ -286,12 +286,12 @@ pub fn strict_equality(lhs: JsValue, rhs: JsValue) -> bool { same_value(lhs, rhs) } -pub fn to_object(val: JsValue, vm: &mut Vm) -> Result { +pub fn to_object(val: JsValue, vm: &mut Vm) -> Result { if val.is_object() { return Ok(val); } if val.is_null() || val.is_undefined() { - return Err("TypeError: Cannot convert null or undefined to object"); + return Err(vm.error_message_text("TypeError", "Cannot convert null or undefined to object")); } let (proto_ptr, type_tag) = if val.is_string() { ( diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 97a073b3..75613f2b 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -54,6 +54,14 @@ fn js_error_kind_name(kind: JsErrorKind) -> &'static str { } } +pub(crate) fn format_error_message(kind: &str, msg: &str) -> String { + if msg.is_empty() || msg == kind || msg.starts_with(&format!("{kind}:")) { + msg.to_string() + } else { + format!("{kind}: {msg}") + } +} + #[allow(unused_macros)] macro_rules! throw_err { ($self:ident, $kind:ident, $msg:expr) => {{ @@ -213,7 +221,7 @@ impl Vm { } } - Err("TypeError: Cannot convert object to primitive value".into()) + Err(self.error_message_text("TypeError", "Cannot convert object to primitive value")) } pub(crate) fn coerce_number_bounded(&mut self, value: JsValue) -> Result { @@ -342,12 +350,7 @@ impl Vm { pub(crate) fn raise_js_error(&mut self, err: JsError) -> Result<(), String> { let kind = js_error_kind_name(err.kind); - let error = match kind { - "TypeError" => crate::builtins::error::create_type_error(self, &err.message), - "ReferenceError" => crate::builtins::error::create_reference_error(self, &err.message), - "SyntaxError" => crate::builtins::error::create_syntax_error(self, &err.message), - _ => crate::builtins::error::create_error(self, &err.message), - }; + let error = crate::builtins::error::create_kind_error(self, kind, &err.message); self.exception_value = Some(error); self.pending_error_kind = Some(kind); self.unwind() @@ -357,6 +360,10 @@ impl Vm { self.raise_error_kind("TypeError", msg) } + pub(crate) fn error_message_text(&self, kind: &str, msg: &str) -> String { + format_error_message(kind, msg) + } + pub fn step_rng(&mut self) { if self.math_rng_state == 0 { self.math_rng_state = std::time::SystemTime::now() @@ -521,11 +528,11 @@ impl Vm { &mut self, callee: JsValue, receiver: JsValue, args: &[JsValue], ) -> Result { if !callee.is_object() { - return Err("TypeError: accessor is not callable".into()); + return Err(self.error_message_text("TypeError", "accessor is not callable")); } let callee_obj = unsafe { &*callee.as_js_object_ptr() }; if !callee_obj.is_function() { - return Err("TypeError: accessor is not callable".into()); + return Err(self.error_message_text("TypeError", "accessor is not callable")); } if let Some(native_fn) = callee_obj.native_fn() { @@ -562,11 +569,11 @@ impl Vm { constructed_this: Option, new_target: JsValue, continuation: FrameContinuation, ) -> Result<(), String> { if !callee.is_object() { - return Err("TypeError: CALL target is not callable".into()); + return Err(self.error_message_text("TypeError", "CALL target is not callable")); } let obj = unsafe { &*callee.as_js_object_ptr() }; if !obj.is_function() || obj.sub_module_index() == 0 { - return Err("TypeError: CALL target is not callable".into()); + return Err(self.error_message_text("TypeError", "CALL target is not callable")); } let sub_idx = obj.sub_module_index() as usize - 1; if sub_idx >= self.sub_modules.len() { @@ -866,7 +873,7 @@ impl Vm { } NativeResult::Err(err_val) => { self.exception_value = Some(err_val); - self.pending_error_kind = Some("Error"); + self.pending_error_kind = Some(self.thrown_error_kind(err_val)); match self.unwind() { Ok(()) => continue, Err(e) => return Err(e), @@ -894,7 +901,7 @@ impl Vm { )); } if self.frames.len() >= self.kernel_core.config.max_call_depth { - return Err("RangeError: Maximum call stack size exceeded".into()); + return Err(self.error_message_text("RangeError", "Maximum call stack size exceeded")); } let sub_bytecode = self.sub_modules[sub_idx].bytecode.clone(); diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index b62cbb50..5c72005e 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -71,7 +71,7 @@ impl Vm { } NativeResult::Err(err_val) => { self.exception_value = Some(err_val); - self.pending_error_kind = Some("Error"); + self.pending_error_kind = Some(self.thrown_error_kind(err_val)); self.unwind().map(|_| true) } NativeResult::TailCall { .. } => { @@ -89,7 +89,7 @@ impl Vm { } if self.frames.len() >= self.kernel_core.config.max_call_depth { - return Err("RangeError: Maximum call stack size exceeded".into()); + return Err(self.error_message_text("RangeError", "Maximum call stack size exceeded")); } let new_obj_val = JsValue::object(new_obj as *mut u8); @@ -154,7 +154,7 @@ impl Vm { let error = crate::builtins::error::create_error(self, "NEW_EXPRESSION: bytecode constructors not yet supported"); self.exception_value = Some(error); - self.pending_error_kind = Some("Error"); + self.pending_error_kind = Some(self.thrown_error_kind(error)); self.unwind().map(|_| true) } } diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index b49eb59a..93e9caa5 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -66,11 +66,11 @@ impl Vm { &mut self, getter: JsValue, receiver: JsValue, target_reg: u8, ) -> Result { if !getter.is_object() { - return Err("TypeError: getter is not callable".into()); + return Err(self.error_message_text("TypeError", "getter is not callable")); } let getter_obj = unsafe { &*getter.as_js_object_ptr() }; if !getter_obj.is_function() { - return Err("TypeError: getter is not callable".into()); + return Err(self.error_message_text("TypeError", "getter is not callable")); } if getter_obj.native_fn().is_some() { diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index bbfa564d..7f421b86 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -10,7 +10,7 @@ impl Vm { &mut self, callee: JsValue, callee_obj: &JsObject, receiver: JsValue, args: &[JsValue], ) -> Result { if callee_obj.sub_module_index() == 0 { - return Err("TypeError: accessor is not callable".into()); + return Err(self.error_message_text("TypeError", "accessor is not callable")); } let sub_idx = callee_obj.sub_module_index() as usize - 1; if sub_idx >= self.sub_modules.len() { diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index b092f91b..1e532baf 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -257,7 +257,7 @@ impl Vm { .resolve_property(obj, message_si) .and_then(|v| self.lookup_str(v)) .unwrap_or_default(); - return if message.is_empty() { name } else { format!("{name}: {message}") }; + return crate::vm::format_error_message(&name, &message); } format!("{val}") }