From f95841b3a17ccb46d4f993a9cb185be51575f255 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: Sun, 7 Jun 2026 22:10:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BB=9F=E4=B8=80=E5=86=85=E9=83=A8?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E6=A8=A1=E5=9E=8B=E5=BC=82=E5=B8=B8=E6=8A=9B?= =?UTF-8?q?=E5=87=BA=E9=A3=8E=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_compiler/src/emitter.rs | 3 +- crates/oxide_vm/src/builtins/error.rs | 5 ++ crates/oxide_vm/src/vm.rs | 113 +++++++++++++++++--------- crates/oxide_vm/tests/vm_tests.rs | 22 +++++ 4 files changed, 103 insertions(+), 40 deletions(-) diff --git a/crates/oxide_compiler/src/emitter.rs b/crates/oxide_compiler/src/emitter.rs index 3d9422c..b9c89d8 100644 --- a/crates/oxide_compiler/src/emitter.rs +++ b/crates/oxide_compiler/src/emitter.rs @@ -462,8 +462,7 @@ impl Compiler { ctx.label_map.insert(finally_label, finally_label_pc); if let Some(fb_pos) = try_finally_begin_pos { let offset = finally_label_pc as isize - (fb_pos as isize); - ctx.bytecode[fb_pos] = - opcode::encode_try_finally_begin(offset as i16); + ctx.bytecode[fb_pos] = opcode::encode_try_finally_begin(offset as i16); } if let Some(jmp_pos) = jmp_skip_pos { diff --git a/crates/oxide_vm/src/builtins/error.rs b/crates/oxide_vm/src/builtins/error.rs index 817d48d..7df0661 100644 --- a/crates/oxide_vm/src/builtins/error.rs +++ b/crates/oxide_vm/src/builtins/error.rs @@ -47,6 +47,11 @@ pub fn create_type_error(vm: &mut Vm, msg: &str) -> JsValue { create_error_object(vm, proto_ptr, "TypeError", msg) } +pub fn create_error(vm: &mut Vm, msg: &str) -> JsValue { + let proto_ptr = P::as_ptr(&vm.kernel().builtin_world().error_proto) as *mut JsObject; + create_error_object(vm, proto_ptr, "Error", msg) +} + pub fn create_reference_error(vm: &mut Vm, msg: &str) -> JsValue { let proto_ptr = P::as_ptr(&vm.kernel().builtin_world().reference_error_proto) as *mut JsObject; create_error_object(vm, proto_ptr, "ReferenceError", msg) diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index c394e63..e3455f3 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -36,6 +36,37 @@ macro_rules! native_call { }}; } +#[allow(unused_macros)] +macro_rules! throw_err { + ($self:ident, Error, $msg:expr) => {{ + let error = crate::builtins::error::create_error($self, $msg); + $self.exception_value = Some(error); + $self.pending_error_kind = Some("Error"); + match $self.unwind() { + Ok(()) => continue, + Err(e) => return Err(e), + } + }}; + ($self:ident, TypeError, $msg:expr) => {{ + let error = crate::builtins::error::create_type_error($self, $msg); + $self.exception_value = Some(error); + $self.pending_error_kind = Some("TypeError"); + match $self.unwind() { + Ok(()) => continue, + Err(e) => return Err(e), + } + }}; + ($self:ident, ReferenceError, $msg:expr) => {{ + let error = crate::builtins::error::create_reference_error($self, $msg); + $self.exception_value = Some(error); + $self.pending_error_kind = Some("ReferenceError"); + match $self.unwind() { + Ok(()) => continue, + Err(e) => return Err(e), + } + }}; +} + pub struct CallFrame { pub return_addr: usize, pub n_locals: u8, @@ -965,11 +996,9 @@ impl Vm { return Ok(()); } } - let msg = self - .exception_value - .take() - .map(|v| format!("uncaught {v}")) - .unwrap_or_else(|| "uncaught exception".to_string()); + let exc = self.exception_value.take().unwrap_or(JsValue::undefined()); + let kind_str = self.pending_error_kind.take().unwrap_or("Error"); + let msg = format!("uncaught {kind_str}: {exc}"); Err(msg) } @@ -1271,7 +1300,7 @@ impl Vm { } } - return Err("TypeError: CALL target is not callable".into()); + throw_err!(self, TypeError, "CALL target is not callable"); } OpCode::CALL_NATIVE => { @@ -1282,15 +1311,19 @@ impl Vm { let callee = self.regs[callee_reg]; if !callee.is_object() { - return Err("TypeError: CALL_NATIVE target is not an object".into()); + throw_err!(self, TypeError, "CALL_NATIVE target is not an object"); } let obj_ptr = callee.as_js_object_ptr(); if obj_ptr.is_null() { - return Err("TypeError: CALL_NATIVE target is null".into()); + throw_err!(self, TypeError, "CALL_NATIVE target is null"); } let obj = unsafe { &*obj_ptr }; if !obj.is_function() || obj.native_fn().is_none() { - return Err("TypeError: CALL_NATIVE target is not a native function".into()); + throw_err!( + self, + TypeError, + "CALL_NATIVE target is not a native function" + ); } let ext = self.bytecode[self.pc]; @@ -1320,18 +1353,22 @@ impl Vm { let constructor = self.regs[constructor_reg]; if !constructor.is_object() { - return Err( - "TypeError: NEW_EXPRESSION: constructor is not an object".into() + throw_err!( + self, + TypeError, + "NEW_EXPRESSION: constructor is not an object" ); } let ctor_ptr = constructor.as_js_object_ptr(); if ctor_ptr.is_null() { - return Err("TypeError: NEW_EXPRESSION: constructor is null".into()); + throw_err!(self, TypeError, "NEW_EXPRESSION: constructor is null"); } let ctor_obj = unsafe { &*ctor_ptr }; if !ctor_obj.is_function() { - return Err( - "TypeError: NEW_EXPRESSION: constructor is not a function".into() + throw_err!( + self, + TypeError, + "NEW_EXPRESSION: constructor is not a function" ); } @@ -1436,7 +1473,7 @@ impl Vm { continue; } } - return Err("TypeError: IC_GET_PROP on non-object".into()); + throw_err!(self, TypeError, "IC_GET_PROP on non-object"); } let obj = unsafe { &*obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); @@ -1491,7 +1528,7 @@ impl Vm { OpCode::IC_SET_PROP => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: IC_SET_PROP on non-object".into()); + throw_err!(self, TypeError, "IC_SET_PROP on non-object"); } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); @@ -1547,7 +1584,7 @@ impl Vm { OpCode::GET_PROP => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: GET_PROP on non-object".into()); + throw_err!(self, TypeError, "GET_PROP on non-object"); } let obj = unsafe { &*obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); @@ -1558,7 +1595,7 @@ impl Vm { OpCode::SET_PROP => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: SET_PROP on non-object".into()); + throw_err!(self, TypeError, "SET_PROP on non-object"); } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); @@ -1582,12 +1619,12 @@ impl Vm { OpCode::GET_PROP_DYNAMIC => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: GET_PROP_DYNAMIC on non-object".into()); + throw_err!(self, TypeError, "GET_PROP_DYNAMIC on non-object"); } let obj = unsafe { &*obj_ptr }; let key_val = self.regs[a]; if !key_val.is_string() { - return Err("TypeError: GET_PROP_DYNAMIC key not a string".into()); + throw_err!(self, TypeError, "GET_PROP_DYNAMIC key not a string"); } let prop_name_si = key_val.as_string_index(); self.regs[b] = self @@ -1598,12 +1635,12 @@ impl Vm { OpCode::SET_PROP_DYNAMIC => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: SET_PROP_DYNAMIC on non-object".into()); + throw_err!(self, TypeError, "SET_PROP_DYNAMIC on non-object"); } let obj = unsafe { &mut *obj_ptr }; let key_val = self.regs[a]; if !key_val.is_string() { - return Err("TypeError: SET_PROP_DYNAMIC key not a string".into()); + throw_err!(self, TypeError, "SET_PROP_DYNAMIC key not a string"); } let prop_name_si = key_val.as_string_index(); if let Some(pos) = self @@ -1649,7 +1686,7 @@ impl Vm { OpCode::SET_ELEM => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: SET_ELEM on non-object".into()); + throw_err!(self, TypeError, "SET_ELEM on non-object"); } let idx = self.regs[a].as_int() as usize; let obj = unsafe { &mut *obj_ptr }; @@ -1734,13 +1771,13 @@ impl Vm { let key_val = self.regs[a]; let obj_ptr = self.regs[b].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: IN right-hand side is not an object".into()); + throw_err!(self, TypeError, "IN right-hand side is not an object"); } let obj = unsafe { &*obj_ptr }; let prop_name_si = if key_val.is_string() { key_val.as_string_index() } else { - return Err("TypeError: IN key must be a string".into()); + throw_err!(self, TypeError, "IN key must be a string"); }; let found = self.resolve_property(obj, prop_name_si).is_some(); self.regs[rd] = JsValue::bool(found); @@ -1793,7 +1830,7 @@ impl Vm { OpCode::MEMBER_INC => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: MEMBER_INC on non-object".into()); + throw_err!(self, TypeError, "MEMBER_INC on non-object"); } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); @@ -1886,7 +1923,7 @@ impl Vm { OpCode::MEMBER_DEC => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: MEMBER_DEC on non-object".into()); + throw_err!(self, TypeError, "MEMBER_DEC on non-object"); } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); @@ -1979,12 +2016,12 @@ impl Vm { OpCode::DYN_MEMBER_INC => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: DYN_MEMBER_INC on non-object".into()); + throw_err!(self, TypeError, "DYN_MEMBER_INC on non-object"); } let obj = unsafe { &mut *obj_ptr }; let key_val = self.regs[a]; if !key_val.is_string() { - return Err("TypeError: DYN_MEMBER_INC key not a string".into()); + throw_err!(self, TypeError, "DYN_MEMBER_INC key not a string"); } let prop_name_si = key_val.as_string_index(); let prop_val = self @@ -2014,12 +2051,12 @@ impl Vm { OpCode::DYN_MEMBER_DEC => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: DYN_MEMBER_DEC on non-object".into()); + throw_err!(self, TypeError, "DYN_MEMBER_DEC on non-object"); } let obj = unsafe { &mut *obj_ptr }; let key_val = self.regs[a]; if !key_val.is_string() { - return Err("TypeError: DYN_MEMBER_DEC key not a string".into()); + throw_err!(self, TypeError, "DYN_MEMBER_DEC key not a string"); } let prop_name_si = key_val.as_string_index(); let prop_val = self @@ -2049,7 +2086,7 @@ impl Vm { OpCode::COMPOUND_MEMBER_ADD => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: COMPOUND_MEMBER_ADD on non-object".into()); + throw_err!(self, TypeError, "COMPOUND_MEMBER_ADD on non-object"); } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); @@ -2112,7 +2149,7 @@ impl Vm { OpCode::COMPOUND_MEMBER_SUB => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: COMPOUND_MEMBER_SUB on non-object".into()); + throw_err!(self, TypeError, "COMPOUND_MEMBER_SUB on non-object"); } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); @@ -2166,7 +2203,7 @@ impl Vm { OpCode::COMPOUND_MEMBER_MUL => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: COMPOUND_MEMBER_MUL on non-object".into()); + throw_err!(self, TypeError, "COMPOUND_MEMBER_MUL on non-object"); } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); @@ -2220,7 +2257,7 @@ impl Vm { OpCode::COMPOUND_MEMBER_DIV => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: COMPOUND_MEMBER_DIV on non-object".into()); + throw_err!(self, TypeError, "COMPOUND_MEMBER_DIV on non-object"); } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); @@ -2274,7 +2311,7 @@ impl Vm { OpCode::COMPOUND_MEMBER_MOD => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: COMPOUND_MEMBER_MOD on non-object".into()); + throw_err!(self, TypeError, "COMPOUND_MEMBER_MOD on non-object"); } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); @@ -2328,7 +2365,7 @@ impl Vm { OpCode::COMPOUND_MEMBER_EXP => { let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; if obj_ptr.is_null() { - return Err("TypeError: COMPOUND_MEMBER_EXP on non-object".into()); + throw_err!(self, TypeError, "COMPOUND_MEMBER_EXP on non-object"); } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); @@ -2382,7 +2419,7 @@ impl Vm { OpCode::FOR_IN_INIT => { let obj_val = self.regs[a]; if !obj_val.is_object() { - return Err("TypeError: for-in right-hand side is not an object".into()); + throw_err!(self, TypeError, "for-in right-hand side is not an object"); } let mut keys_vec = bumpalo::collections::Vec::new_in(self.epoch.bump()); diff --git a/crates/oxide_vm/tests/vm_tests.rs b/crates/oxide_vm/tests/vm_tests.rs index 8c183fc..e761e1b 100644 --- a/crates/oxide_vm/tests/vm_tests.rs +++ b/crates/oxide_vm/tests/vm_tests.rs @@ -75,3 +75,25 @@ fn test_try_catch_finally() { fn test_catch_param_binding() { assert_eq!(eval("try { throw 99; } catch(myErr) { myErr; }"), "99"); } + +#[test] +fn test_throw_err_type_error_catchable() { + let result = eval("try { null.prop = 1; } catch(e) { 42; }"); + assert_eq!(result, "42"); +} + +#[test] +fn test_throw_err_type_error_is_object() { + // IC_GET_PROP on null throws TypeError which is catchable; catch body runs + let result = eval("try { null.prop; } catch(e) { 99; }"); + assert_eq!(result, "99"); +} + +#[test] +fn test_uncaught_type_error_message() { + let result = eval("null.prop;"); + assert!( + result.contains("TypeError"), + "expected TypeError in message, got: {result}" + ); +}