Files
OxideJS-New/crates/oxide_vm/src/vm/error.rs
T

309 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 错误抛出与有界强转:强转失败统一出口、调用错误恢复、错误构造与异常
//! 展开、有界数值强转三件套与抛出错误 kind 查询。
use oxide_runtime_api as coercion;
use oxide_types::error::JsError;
use oxide_types::object::JsObject;
use oxide_types::private_key::{encode_symbol_key, WELL_KNOWN_SYMBOL_TO_PRIMITIVE};
use oxide_types::value::{JsValue, PTR_MASK};
use super::{format_error_message, js_error_kind, js_error_kind_name, Vm};
use crate::vm_debug;
impl Vm {
/// ToPrimitive 有界版:按规范序把对象转为原始值,失败统一抛 `TypeError`。
///
/// 对象上(沿原型链读取)的 `Symbol.toPrimitive` 优先;不存在时按 `prefer_string` 定序尝试
/// `toString`/`valueOf`,等价于 OrdinaryToPrimitive。
///
/// # 步骤
/// 1. 非对象或空指针原样返回。
/// 2. 读 `@@toPrimitive`:存在则必须可调用(否则抛 `TypeError`),以
/// `"string"`/`"number"` hint 调用;返回对象再抛 `TypeError`。
/// 3. 回退方法族:按 hint 顺序试 `toString`/`valueOf`,不可调用者跳过,
/// 首个返回非对象的结果即采用。
/// 4. 两条路径都不产出原始值时抛 `TypeError`。
///
/// # 边界与前提
/// - `prefer_string` 为 `true` 时先试 `toString`,否则先试 `valueOf`。
/// - 方法调用经 `call_function_sync`;调用错误转 `raise_call_error` 恢复
/// 原始异常。
///
/// # 副作用
/// - 执行用户代码(getter / 方法调用),可能抛异常并触发 GC 安全点。
///
/// # 注意事项
/// - 主 dispatch 下就地展开异常;原生 builtin 内部只返回格式化 `Err`,
/// 由其调用边界恢复(见 `conversion_error`)。
pub(crate) fn coerce_primitive_bounded(&mut self, value: JsValue, prefer_string: bool) -> Result<JsValue, String> {
if !value.is_object() {
return Ok(value);
}
let obj_ptr = value.as_js_object_ptr();
if obj_ptr.is_null() {
return Ok(value);
}
// ECMA-262 §7.1.1 step 1:exotic 对象上(沿原型链读取)的 Symbol.toPrimitive 优先于
// OrdinaryToPrimitive;well-known symbol 键经 realm 感知编码,直接取保留槽。
let sym_si = encode_symbol_key(self.realm_id(), WELL_KNOWN_SYMBOL_TO_PRIMITIVE);
let exotic = {
let obj = unsafe { &*obj_ptr };
self.ordinary_get(obj, sym_si, value)?
};
if !exotic.is_undefined() && !exotic.is_null() {
let exotic_ptr = exotic.as_js_object_ptr();
if !exotic.is_object() || exotic_ptr.is_null() || !unsafe { &*exotic_ptr }.is_function() {
// 抛可捕获的 JS 异常(dispatch 层 try/catch 可捕获),见 conversion_error。
self.conversion_error("Symbol.toPrimitive is not a function")?;
return Ok(JsValue::undefined());
}
let hint_val = self.new_string(if prefer_string { "string" } else { "number" });
let result = match self.call_function_sync(exotic, value, &[hint_val]) {
Ok(r) => r,
Err(err) => return self.raise_call_error(&err),
};
if result.is_object() {
self.conversion_error("Cannot convert object to primitive value")?;
return Ok(JsValue::undefined());
}
return Ok(result);
}
let method_names = if prefer_string { ["toString", "valueOf"] } else { ["valueOf", "toString"] };
for method_name in method_names {
let method_si = self.kernel_core.perm_interner().intern(method_name).0;
let method = {
let obj = unsafe { &*obj_ptr };
self.ordinary_get(obj, method_si, value)?
};
if method.is_undefined() || method.is_null() {
continue;
}
// OrdinaryToPrimitive:valueOf/toString 不可调用时跳过(IsCallable == false
// 则 continue),不抛错;仅 @@toPrimitive 不可调用时抛 TypeError。
if !method.is_object() {
continue;
}
let method_ptr = method.as_js_object_ptr();
if method_ptr.is_null() || !unsafe { &*method_ptr }.is_function() {
continue;
}
let result = match self.call_function_sync(method, value, &[]) {
Ok(r) => r,
Err(err) => return self.raise_call_error(&err),
};
if !result.is_object() {
return Ok(result);
}
}
// 主 dispatch 抛可捕获异常(外围 JS try/catch 可捕获);原生 builtin 内部
// 只传播格式化 Err,由其调用边界恢复为异常对象(见 conversion_error)。
self.conversion_error("Cannot convert object to primitive value")?;
Ok(JsValue::undefined())
}
/// 对象转原始值失败时的统一出口:主 dispatch(native_call_depth == 0)下抛可捕获
/// 的 JS 异常并就地展开到外围 try/catch;原生 builtin 内部(depth > 0)不得就地
/// 展开(展开会消费 try 处理器,builtin 却继续执行并可能产生二次错误),改为返回
/// 格式化 Err,由原生调用边界(call_function_sync → dispatch_native_call)恢复为
/// 原始异常对象。
#[inline(always)]
fn conversion_error(&mut self, msg: &str) -> Result<(), String> {
if self.native_call_depth == 0 {
self.raise_error_kind("TypeError", msg)
} else {
Err(self.error_message_text("TypeError", msg))
}
}
/// 把 `call_function_sync` 返回的调用错误恢复为原始异常值并走异常展开,
/// 使外围 try/catch 可捕获(native 函数抛错时原值存于 `last_uncaught_value`)。
///
/// # 注意事项
/// 主 dispatch(`native_call_depth == 0`)就地展开——此时 try_stack 只含当前
/// 字节码的处理器,展开后 pc/regs[0] 不会被中途的原生调用栈覆盖。原生 builtin
/// 内部(depth > 0)传播错误,由其调用边界(dispatch_native_call)转换;异常值
/// 同步物化入异常通道(不取 uncaught 侧通道,builtin 边界 `from_engine_error`
/// 仍须从侧通道还原原值),供内联字节码体在状态还原前由体内 try 处理器捕获。
pub(crate) fn raise_call_error(&mut self, err: &str) -> Result<JsValue, String> {
if self.native_call_depth == 0 {
let exc = self
.last_uncaught_value
.take()
.unwrap_or_else(|| oxide_builtins::error::create_from_text(self, err));
let kind = self.thrown_error_kind(exc);
self.exception_value = Some(exc);
self.pending_error_kind = Some(kind);
self.unwind()?;
return Ok(JsValue::undefined());
}
let exc = self
.last_uncaught_value
.unwrap_or_else(|| oxide_builtins::error::create_from_text(self, err));
let kind = self.thrown_error_kind(exc);
self.exception_value = Some(exc);
self.pending_error_kind = Some(kind);
Err(err.to_string())
}
/// 恢复已捕获的原始异常值(强转失败载荷)并走异常展开,保原值 kind:
/// 深度 0 置原值入异常通道就地展开到外围 catch;深度 >0 返回 kind 前缀
/// 文本 `Err`,由原生调用边界恢复为异常对象,异常值同步物化入异常通道
/// (供内联字节码体在状态还原前由体内 try 处理器捕获)。
///
/// # 步骤
/// 1. 深度 0:原值与 kind 写入异常通道,就地 `unwind`。
/// 2. 深度 >0:原值与 kind 写入异常通道,以 `Kind: message` 文本 `Err` 返回。
///
/// # 边界与前提
/// - `exc` 为调用方已提取的原始异常值;本入口不重取 uncaught 槽。
///
/// # 副作用
/// - 写 `exception_value`/`pending_error_kind`;深度 0 时 pc 经展开改写。
pub(crate) fn raise_captured(&mut self, exc: JsValue) -> Result<(), String> {
let kind = self.thrown_error_kind(exc);
self.exception_value = Some(exc);
self.pending_error_kind = Some(kind);
if self.native_call_depth == 0 {
return self.unwind();
}
Err(oxide_builtins::typed_array::element_error_text(self, exc))
}
/// ToNumber 有界版:先按 number hint 做 ToPrimitive,再转 `f64`。
///
/// # 边界与前提
/// 原始值为 Symbol 或 BigInt 时抛 `TypeError`,不落入
/// [`coercion::to_number`] 的宽松快路径(后者对 Symbol 返回 `NaN`、
/// 对 BigInt 做 lossy 转换)。BigInt 入口语义:显式 lossy 站点
/// (`Number` 构造器)先解盒自行转换,不经本入口。
pub(crate) fn coerce_number_bounded(&mut self, value: JsValue) -> Result<f64, String> {
let primitive = self.coerce_primitive_bounded(value, false)?;
if primitive.is_symbol() {
self.conversion_error("Cannot convert a Symbol value to a number")?;
return Ok(f64::NAN);
}
if primitive.is_bigint() {
self.conversion_error("Cannot convert a BigInt value to a number")?;
return Ok(f64::NAN);
}
Ok(coercion::to_number(primitive))
}
/// ToInt32 有界版:整数直通;其余先 ToNumber,再取模 2^32 后按符号回卷。
pub(crate) fn coerce_int32_bounded(&mut self, value: JsValue) -> Result<i32, String> {
if value.is_int() {
return Ok(value.as_int());
}
let n = self.coerce_number_bounded(value)?;
if n == 0.0 || !n.is_finite() {
return Ok(0);
}
let int = n.trunc().rem_euclid(4_294_967_296.0) as u32;
if int > i32::MAX as u32 {
Ok((int as i64 - 4_294_967_296i64) as i32)
} else {
Ok(int as i32)
}
}
/// ToUint32 有界版:整数直通;其余先 ToNumber,再取模 2^32。
pub(crate) fn coerce_uint32_bounded(&mut self, value: JsValue) -> Result<u32, String> {
if value.is_int() {
return Ok(value.as_int() as u32);
}
let n = self.coerce_number_bounded(value)?;
if n == 0.0 || !n.is_finite() {
return Ok(0);
}
Ok(n.trunc().rem_euclid(4_294_967_296.0) as u32)
}
/// 校验 `JsValue` 为合法非空对象指针,成功时返回对象裸指针。
///
/// # 边界与前提
/// - 非对象、空指针、地址低于 `0x10000` 或未按 `JsObject` 对齐:抛
/// `TypeError`(消息由调用方给出)并返回 `Ok(None)`,调用方据此短路
/// (错误已进异常通道,后续由 dispatch 继续展开)。
/// - 本函数不做语义检查(可调用/可构造等能力由调用方判定)。
pub(crate) fn checked_object_ptr(
&mut self, val: JsValue, error_msg: &str,
) -> Result<Option<*mut JsObject>, String> {
if !val.is_object() {
self.raise_type_error(error_msg)?;
return Ok(None);
}
let ptr = (val.to_bits() & PTR_MASK) as *mut JsObject;
let addr = ptr as usize;
if ptr.is_null() || addr < 0x10000 || addr % std::mem::align_of::<JsObject>() != 0 {
self.raise_type_error(error_msg)?;
return Ok(None);
}
Ok(Some(ptr))
}
/// 按 kind 名构造 `JsError` 并转 `raise_js_error` 抛出。
pub(crate) fn raise_error_kind(&mut self, kind: &'static str, msg: &str) -> Result<(), String> {
self.raise_js_error(JsError::new(js_error_kind(kind), msg))
}
/// 构造错误对象、写入异常通道并展开到最近的 try 处理器。
///
/// - `exception_value` 存错误对象、`pending_error_kind` 存 kind 名,供外围
/// try/catch 与错误通道查询。
/// - 展开失败(无 handler)时 `unwind` 返回 `Err`,异常逃出 run 边界经
/// `last_uncaught_value` 上交调用方。
pub(crate) fn raise_js_error(&mut self, err: JsError) -> Result<(), String> {
let kind = js_error_kind_name(err.kind);
vm_debug!("raise_js_error: {} \"{}\"", kind, err.message);
let error = oxide_builtins::error::create_kind_error(self, kind, &err.message);
self.exception_value = Some(error);
self.pending_error_kind = Some(kind);
self.unwind()
}
/// 抛 `TypeError` 的便捷入口,等价于以 kind 名 `"TypeError"` 调
/// `raise_error_kind`。
pub(crate) fn raise_type_error(&mut self, msg: &str) -> Result<(), String> {
self.raise_error_kind("TypeError", msg)
}
/// 格式化错误文本,供原生 builtin 调用边界恢复异常时使用。
pub(crate) fn error_message_text(&self, kind: &str, msg: &str) -> String {
format_error_message(kind, msg)
}
/// 从异常对象读 `name` 属性并映射为错误 kind 名。
///
/// 非对象、无 `name`、name 非字符串或未知名称一律回退 `"Error"`。
pub(crate) fn thrown_error_kind(&self, val: JsValue) -> &'static str {
if !val.is_object() {
return "Error";
}
let name_si = self.kernel_core.perm_interner().intern("name").0;
let obj = unsafe { &*val.as_js_object_ptr() };
let Some(name_val) = self.resolve_property(obj, name_si) else {
return "Error";
};
let Some(name) = self.lookup_str(name_val) else {
return "Error";
};
match name.as_str() {
"TypeError" => "TypeError",
"ReferenceError" => "ReferenceError",
"RangeError" => "RangeError",
"SyntaxError" => "SyntaxError",
"URIError" => "URIError",
"EvalError" => "EvalError",
"Error" => "Error",
_ => "Error",
}
}
}