feat: 统一内部报错模型异常抛出风格

This commit is contained in:
爱特每羊队
2026-06-07 22:10:20 +08:00
parent bd7e4f78c2
commit f95841b3a1
4 changed files with 103 additions and 40 deletions
+1 -2
View File
@@ -462,8 +462,7 @@ impl Compiler {
ctx.label_map.insert(finally_label, finally_label_pc); ctx.label_map.insert(finally_label, finally_label_pc);
if let Some(fb_pos) = try_finally_begin_pos { if let Some(fb_pos) = try_finally_begin_pos {
let offset = finally_label_pc as isize - (fb_pos as isize); let offset = finally_label_pc as isize - (fb_pos as isize);
ctx.bytecode[fb_pos] = ctx.bytecode[fb_pos] = opcode::encode_try_finally_begin(offset as i16);
opcode::encode_try_finally_begin(offset as i16);
} }
if let Some(jmp_pos) = jmp_skip_pos { if let Some(jmp_pos) = jmp_skip_pos {
+5
View File
@@ -47,6 +47,11 @@ pub fn create_type_error(vm: &mut Vm, msg: &str) -> JsValue {
create_error_object(vm, proto_ptr, "TypeError", msg) 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 { 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; let proto_ptr = P::as_ptr(&vm.kernel().builtin_world().reference_error_proto) as *mut JsObject;
create_error_object(vm, proto_ptr, "ReferenceError", msg) create_error_object(vm, proto_ptr, "ReferenceError", msg)
+75 -38
View File
@@ -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 struct CallFrame {
pub return_addr: usize, pub return_addr: usize,
pub n_locals: u8, pub n_locals: u8,
@@ -965,11 +996,9 @@ impl Vm {
return Ok(()); return Ok(());
} }
} }
let msg = self let exc = self.exception_value.take().unwrap_or(JsValue::undefined());
.exception_value let kind_str = self.pending_error_kind.take().unwrap_or("Error");
.take() let msg = format!("uncaught {kind_str}: {exc}");
.map(|v| format!("uncaught {v}"))
.unwrap_or_else(|| "uncaught exception".to_string());
Err(msg) 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 => { OpCode::CALL_NATIVE => {
@@ -1282,15 +1311,19 @@ impl Vm {
let callee = self.regs[callee_reg]; let callee = self.regs[callee_reg];
if !callee.is_object() { 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(); let obj_ptr = callee.as_js_object_ptr();
if obj_ptr.is_null() { 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 }; let obj = unsafe { &*obj_ptr };
if !obj.is_function() || obj.native_fn().is_none() { 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]; let ext = self.bytecode[self.pc];
@@ -1320,18 +1353,22 @@ impl Vm {
let constructor = self.regs[constructor_reg]; let constructor = self.regs[constructor_reg];
if !constructor.is_object() { if !constructor.is_object() {
return Err( throw_err!(
"TypeError: NEW_EXPRESSION: constructor is not an object".into() self,
TypeError,
"NEW_EXPRESSION: constructor is not an object"
); );
} }
let ctor_ptr = constructor.as_js_object_ptr(); let ctor_ptr = constructor.as_js_object_ptr();
if ctor_ptr.is_null() { 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 }; let ctor_obj = unsafe { &*ctor_ptr };
if !ctor_obj.is_function() { if !ctor_obj.is_function() {
return Err( throw_err!(
"TypeError: NEW_EXPRESSION: constructor is not a function".into() self,
TypeError,
"NEW_EXPRESSION: constructor is not a function"
); );
} }
@@ -1436,7 +1473,7 @@ impl Vm {
continue; 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 obj = unsafe { &*obj_ptr };
let prop_name_si = self.regs[b].as_string_index(); let prop_name_si = self.regs[b].as_string_index();
@@ -1491,7 +1528,7 @@ impl Vm {
OpCode::IC_SET_PROP => { OpCode::IC_SET_PROP => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.regs[b].as_string_index(); let prop_name_si = self.regs[b].as_string_index();
@@ -1547,7 +1584,7 @@ impl Vm {
OpCode::GET_PROP => { OpCode::GET_PROP => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &*obj_ptr };
let prop_name_si = self.regs[b].as_string_index(); let prop_name_si = self.regs[b].as_string_index();
@@ -1558,7 +1595,7 @@ impl Vm {
OpCode::SET_PROP => { OpCode::SET_PROP => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.regs[b].as_string_index(); let prop_name_si = self.regs[b].as_string_index();
@@ -1582,12 +1619,12 @@ impl Vm {
OpCode::GET_PROP_DYNAMIC => { OpCode::GET_PROP_DYNAMIC => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &*obj_ptr };
let key_val = self.regs[a]; let key_val = self.regs[a];
if !key_val.is_string() { 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(); let prop_name_si = key_val.as_string_index();
self.regs[b] = self self.regs[b] = self
@@ -1598,12 +1635,12 @@ impl Vm {
OpCode::SET_PROP_DYNAMIC => { OpCode::SET_PROP_DYNAMIC => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &mut *obj_ptr };
let key_val = self.regs[a]; let key_val = self.regs[a];
if !key_val.is_string() { 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(); let prop_name_si = key_val.as_string_index();
if let Some(pos) = self if let Some(pos) = self
@@ -1649,7 +1686,7 @@ impl Vm {
OpCode::SET_ELEM => { OpCode::SET_ELEM => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 idx = self.regs[a].as_int() as usize;
let obj = unsafe { &mut *obj_ptr }; let obj = unsafe { &mut *obj_ptr };
@@ -1734,13 +1771,13 @@ impl Vm {
let key_val = self.regs[a]; let key_val = self.regs[a];
let obj_ptr = self.regs[b].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[b].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &*obj_ptr };
let prop_name_si = if key_val.is_string() { let prop_name_si = if key_val.is_string() {
key_val.as_string_index() key_val.as_string_index()
} else { } 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(); let found = self.resolve_property(obj, prop_name_si).is_some();
self.regs[rd] = JsValue::bool(found); self.regs[rd] = JsValue::bool(found);
@@ -1793,7 +1830,7 @@ impl Vm {
OpCode::MEMBER_INC => { OpCode::MEMBER_INC => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.regs[b].as_string_index(); let prop_name_si = self.regs[b].as_string_index();
@@ -1886,7 +1923,7 @@ impl Vm {
OpCode::MEMBER_DEC => { OpCode::MEMBER_DEC => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.regs[b].as_string_index(); let prop_name_si = self.regs[b].as_string_index();
@@ -1979,12 +2016,12 @@ impl Vm {
OpCode::DYN_MEMBER_INC => { OpCode::DYN_MEMBER_INC => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &mut *obj_ptr };
let key_val = self.regs[a]; let key_val = self.regs[a];
if !key_val.is_string() { 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_name_si = key_val.as_string_index();
let prop_val = self let prop_val = self
@@ -2014,12 +2051,12 @@ impl Vm {
OpCode::DYN_MEMBER_DEC => { OpCode::DYN_MEMBER_DEC => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &mut *obj_ptr };
let key_val = self.regs[a]; let key_val = self.regs[a];
if !key_val.is_string() { 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_name_si = key_val.as_string_index();
let prop_val = self let prop_val = self
@@ -2049,7 +2086,7 @@ impl Vm {
OpCode::COMPOUND_MEMBER_ADD => { OpCode::COMPOUND_MEMBER_ADD => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.regs[b].as_string_index(); let prop_name_si = self.regs[b].as_string_index();
@@ -2112,7 +2149,7 @@ impl Vm {
OpCode::COMPOUND_MEMBER_SUB => { OpCode::COMPOUND_MEMBER_SUB => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.regs[b].as_string_index(); let prop_name_si = self.regs[b].as_string_index();
@@ -2166,7 +2203,7 @@ impl Vm {
OpCode::COMPOUND_MEMBER_MUL => { OpCode::COMPOUND_MEMBER_MUL => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.regs[b].as_string_index(); let prop_name_si = self.regs[b].as_string_index();
@@ -2220,7 +2257,7 @@ impl Vm {
OpCode::COMPOUND_MEMBER_DIV => { OpCode::COMPOUND_MEMBER_DIV => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.regs[b].as_string_index(); let prop_name_si = self.regs[b].as_string_index();
@@ -2274,7 +2311,7 @@ impl Vm {
OpCode::COMPOUND_MEMBER_MOD => { OpCode::COMPOUND_MEMBER_MOD => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.regs[b].as_string_index(); let prop_name_si = self.regs[b].as_string_index();
@@ -2328,7 +2365,7 @@ impl Vm {
OpCode::COMPOUND_MEMBER_EXP => { OpCode::COMPOUND_MEMBER_EXP => {
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject; let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() { 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 obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.regs[b].as_string_index(); let prop_name_si = self.regs[b].as_string_index();
@@ -2382,7 +2419,7 @@ impl Vm {
OpCode::FOR_IN_INIT => { OpCode::FOR_IN_INIT => {
let obj_val = self.regs[a]; let obj_val = self.regs[a];
if !obj_val.is_object() { 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()); let mut keys_vec = bumpalo::collections::Vec::new_in(self.epoch.bump());
+22
View File
@@ -75,3 +75,25 @@ fn test_try_catch_finally() {
fn test_catch_param_binding() { fn test_catch_param_binding() {
assert_eq!(eval("try { throw 99; } catch(myErr) { myErr; }"), "99"); 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}"
);
}