fix: 数组长度定义异常原值抛出并保留错误类型

This commit is contained in:
2026-09-18 02:42:11 +00:00
parent a6411a495d
commit b45f979db7
9 changed files with 144 additions and 33 deletions
+4
View File
@@ -222,6 +222,10 @@ pub struct Vm {
/// String 后,for-of 需要重新抛出原值。放在 VM 顶层字段(不在 InlineSyncState 中)
/// 以便跨内联调用的恢复过程存活。
pub(crate) last_uncaught_value: Option<JsValue>,
/// 数组 length define 强转期用户代码抛出的原始异常:与 `last_uncaught_value`
/// 分离,避免入口误取其它操作忽略调用残留的值。仅 `define_array_length` 写入,
/// 由 Object/Reflect define 入口消费。
pub(crate) pending_length_exception: Option<JsValue>,
pub(crate) pending_exception: Option<JsValue>,
pub(crate) pending_error_kind: Option<&'static str>,
/// 控制流完成(break/continue/return)暂存,finally 执行后由 TRY_FINALLY_END 恢复。
+3
View File
@@ -52,6 +52,9 @@ impl oxide_runtime_api::VmHost for Vm {
fn restore_uncaught_value(&mut self, value: Option<JsValue>) {
self.last_uncaught_value = value;
}
fn take_pending_length_exception(&mut self) -> Option<JsValue> {
self.pending_length_exception.take()
}
fn property_key_si(&mut self, val: JsValue) -> u32 {
// 对象键转换(to_string_full)失败时降级为空键:本 trait 路径供
// Reflect/Object 内置使用,这些内置按规范不传播键转换异常;计算属性
+28 -6
View File
@@ -1060,17 +1060,39 @@ impl Vm {
/// 可写位、bump 世代。
///
/// # 注意事项
/// - 非法长度以 `"RangeError: "` 前缀标记 kind,由 Object/Reflect 入口各自投影;
/// 其余失败投影为 TypeError。
/// - 非法长度以 `"RangeError: "` 前缀标记 kind,强转失败以 `"TypeError: "`
/// 前缀标记;用户代码抛出的原始异常值转存 VM 专用槽,由 Object/Reflect
/// 入口取出并原值重抛。
fn define_array_length(
&mut self, obj: &mut JsObject, val: JsValue, attributes: PropAttributes,
) -> Result<(), String> {
// BigInt 无 ToNumber 语义(ToNumber(BigInt) 抛 TypeError):在强转前拦截,
// 避免通用 to_number 的近似分支把 1n 当作 1.0 接受。
if val.is_bigint() {
return Err(self.error_message_text("TypeError", "Cannot convert a BigInt value to a number"));
}
// [[Value]] 两次强转都要实际执行(可触发用户代码),且须早于描述符校验;
// 两次强转之间用户代码可能把 length 收窄为不可写。
let new_len = self.coerce_uint32_bounded(val)?;
let number_len = self.coerce_number_bounded(val)?;
// 两次强转之间用户代码可能把 length 收窄为不可写。清空未捕获槽后执行,
// 槽内值必为本次强转产生;用户抛出时原值转存专用槽供入口原值重抛,
// 引擎转换失败(如 Symbol)留空由消息前缀重建 TypeError。
self.last_uncaught_value = None;
let new_len = match self.coerce_uint32_bounded(val) {
Ok(v) => v,
Err(msg) => {
self.pending_length_exception = self.last_uncaught_value.take();
return Err(msg);
}
};
let number_len = match self.coerce_number_bounded(val) {
Ok(v) => v,
Err(msg) => {
self.pending_length_exception = self.last_uncaught_value.take();
return Err(msg);
}
};
if new_len as f64 != number_len {
return Err("RangeError: Invalid array length".to_string());
return Err(self.error_message_text("RangeError", "Invalid array length"));
}
let old_logical = obj.logical_len();
+3
View File
@@ -87,6 +87,7 @@ impl Vm {
try_stack: Vec::new(),
exception_value: None,
last_uncaught_value: None,
pending_length_exception: None,
pending_exception: None,
pending_error_kind: None,
pending_completion: None,
@@ -216,6 +217,7 @@ impl Vm {
try_stack: Vec::new(),
exception_value: None,
last_uncaught_value: None,
pending_length_exception: None,
pending_exception: None,
pending_error_kind: None,
pending_completion: None,
@@ -513,6 +515,7 @@ impl Vm {
// 未捕获异常侧通道持原始 epoch 对象指针:执行期状态,跨 run/reset 不保留,
// 池回收后残留将悬垂。
self.last_uncaught_value = None;
self.pending_length_exception = None;
self.pending_exception = None;
self.pending_error_kind = None;
self.pending_completion = None;