mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: TailCall 转发不可调用目标改抛可捕获 TypeError
This commit is contained in:
@@ -94,20 +94,28 @@ impl Vm {
|
||||
NativeResult::TailCall { callee, this, args } => {
|
||||
if callee.is_object() {
|
||||
let obj = unsafe { &*callee.as_js_object_ptr() };
|
||||
// 不可调用目标(apply/call/bound 转发的 thisArg 不可调用等)抛
|
||||
// 可捕获 TypeError;落入 push_bytecode_frame 会产生引擎级错误,
|
||||
// 绕过 JS try/catch。
|
||||
if !obj.is_function() || (obj.native_fn().is_none() && obj.sub_module_index() == 0) {
|
||||
return self.raise_type_error("CALL target is not callable");
|
||||
}
|
||||
if obj.native_fn().is_some() {
|
||||
match self.call_function_sync(callee, this, &args) {
|
||||
Ok(val) => {
|
||||
self.regs[0] = val;
|
||||
return Ok(());
|
||||
}
|
||||
Err(_) => {
|
||||
Err(e) => {
|
||||
// call_function_sync 已把 native 错误展平为 String,
|
||||
// 原始 JsValue 暂存在 last_uncaught_value——恢复为 JS 异常,
|
||||
// 使外围 try/catch 能捕获(而非作为引擎错误上抛)。
|
||||
// 使外围 try/catch 能捕获(而非作为引擎错误上抛);
|
||||
// 无原值时从展平文本恢复 kind,泛型回退会把种类
|
||||
// 降级为普通 Error。
|
||||
let exc = self
|
||||
.last_uncaught_value
|
||||
.take()
|
||||
.unwrap_or_else(|| oxide_builtins::error::create_error(self, "call failed"));
|
||||
.unwrap_or_else(|| oxide_builtins::error::create_from_text(self, &e));
|
||||
let kind = self.thrown_error_kind(exc);
|
||||
self.exception_value = Some(exc);
|
||||
self.pending_error_kind = Some(kind);
|
||||
@@ -116,6 +124,8 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return self.raise_type_error("CALL target is not callable");
|
||||
}
|
||||
self.push_bytecode_frame(
|
||||
callee,
|
||||
|
||||
@@ -244,6 +244,45 @@ fn bound_function_construct_semantics() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_apply_non_callable_this_throws_catchable_type_error() {
|
||||
// 不可调用 this(实例原型链含函数)经 apply 转发:抛可捕获 TypeError,
|
||||
// 不得绕过 try/catch 成为引擎级错误。
|
||||
let mut vm = Vm::new();
|
||||
let name = eval(
|
||||
&mut vm,
|
||||
"function FACTORY(){} FACTORY.prototype = Function(); var o = new FACTORY(); try { o.apply(); } catch (e) { e.name }",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(vm.lookup_str(name).unwrap_or_default(), "TypeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_call_non_callable_target_keeps_type_error_kind() {
|
||||
// call 转发到非可调用目标(原始值 thisArg):内层递归无原值可恢复时,
|
||||
// 错误种类仍须保持 TypeError。
|
||||
let mut vm = Vm::new();
|
||||
for src in [
|
||||
"try { Function.prototype.call.call(undefined, {}) } catch (e) { e.name }",
|
||||
"try { Function.prototype.call.call(null, {}) } catch (e) { e.name }",
|
||||
"try { Function.prototype.call.call({}, {}) } catch (e) { e.name }",
|
||||
"try { Function.prototype.call.call(undefined) } catch (e) { e.name }",
|
||||
] {
|
||||
let name = eval(&mut vm, src).unwrap();
|
||||
assert_eq!(vm.lookup_str(name).unwrap_or_default(), "TypeError", "for {}", src);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_call_callable_native_target_still_works() {
|
||||
// 守卫不得误伤合法 native 目标:经 call/apply 转发 native 函数照常执行。
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "Math.max.call(null, 3, 9)").unwrap();
|
||||
assert_num(result, 9.0);
|
||||
let result = eval(&mut vm, "Math.max.apply(null, [3, 9])").unwrap();
|
||||
assert_num(result, 9.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bound_function_construct_errors() {
|
||||
let mut vm = Vm::new();
|
||||
|
||||
Reference in New Issue
Block a user