diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index 05ba61fa..0d8b11ce 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -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, diff --git a/crates/oxide_vm/tests/builtins_function_tests.rs b/crates/oxide_vm/tests/builtins_function_tests.rs index a383acd6..b0b70faf 100644 --- a/crates/oxide_vm/tests/builtins_function_tests.rs +++ b/crates/oxide_vm/tests/builtins_function_tests.rs @@ -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();