mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
309 lines
14 KiB
Rust
309 lines
14 KiB
Rust
//! 错误抛出与有界强转:强转失败统一出口、调用错误恢复、错误构造与异常
|
||
//! 展开、有界数值强转三件套与抛出错误 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",
|
||
}
|
||
}
|
||
}
|