From 2824213860b8d294c6789ab98a41676b1ce32977 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 26 Sep 2026 23:29:59 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20apply=20=E9=9D=9E=E5=AF=B9=E8=B1=A1=20ar?= =?UTF-8?q?gArray=20=E6=8A=9B=20TypeError=20=E5=B9=B6=E5=85=88=E6=9F=A5?= =?UTF-8?q?=E7=9B=AE=E6=A0=87=E5=8F=AF=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/function.rs | 25 ++++++++++++++++--- .../oxide_vm/tests/builtins_function_tests.rs | 21 ++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/crates/oxide_builtins/src/function.rs b/crates/oxide_builtins/src/function.rs index 0639022f..2144d9ae 100644 --- a/crates/oxide_builtins/src/function.rs +++ b/crates/oxide_builtins/src/function.rs @@ -193,10 +193,11 @@ pub fn function_call(vm: &mut H, args: &[u8]) -> NativeResult { } /// `Function.prototype.apply(thisArg, argsArray)`:以指定 this 和参数对象调用 -/// 目标函数。argsArray 为 null/undefined → 无实参;对象(数组 / Arguments / -/// 数组类对象)读 `length`(传播读,强转为长度)后逐下标传播读元素(空位取 -/// undefined);元素物化为实参切片经 TailCall 下发(帧参数区),不受寄存器 -/// 窗口限制。length/元素 getter 抛错时透传原异常值。 +/// 目标函数。目标非可调用 → TypeError(先查,先于 argArray 检查);argsArray +/// 为 null/undefined → 无实参;非对象(非 nullish)→ TypeError;对象(数组 / +/// Arguments / 数组类对象)读 `length`(传播读,强转为长度)后逐下标传播读 +/// 元素(空位取 undefined);元素物化为实参切片经 TailCall 下发(帧参数区), +/// 不受寄存器窗口限制。length/元素 getter 抛错时透传原异常值。 pub fn function_apply(vm: &mut H, args: &[u8]) -> NativeResult { if args.is_empty() { return NativeResult::Err(JsValue::undefined()); @@ -204,9 +205,25 @@ pub fn function_apply(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = vm.reg(args[0]); let this_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + // IsCallable 检查:目标非可调用先抛 TypeError,先于 argArray 检查。 + if !target_val.is_object() + || target_val.as_js_object_ptr().is_null() + || !unsafe { &*target_val.as_js_object_ptr() }.is_function() + { + return NativeResult::Err(crate::error::create_type_error(vm, "CALL target is not callable")); + } + let mut call_args: Vec = Vec::new(); if args.len() > 2 { let arr_val = vm.reg(args[2]); + // CreateListFromArrayLike:非对象 argArray(非 nullish)抛 TypeError; + // null/undefined 为无实参。 + if !arr_val.is_object() && !arr_val.is_nullish() { + return NativeResult::Err(crate::error::create_type_error( + vm, + "CreateListFromArrayLike called on non-object", + )); + } if arr_val.is_object() { let arr_ptr = arr_val.as_js_object_ptr(); if !arr_ptr.is_null() { diff --git a/crates/oxide_vm/tests/builtins_function_tests.rs b/crates/oxide_vm/tests/builtins_function_tests.rs index b0b70faf..b3cc0529 100644 --- a/crates/oxide_vm/tests/builtins_function_tests.rs +++ b/crates/oxide_vm/tests/builtins_function_tests.rs @@ -257,6 +257,27 @@ fn function_apply_non_callable_this_throws_catchable_type_error() { assert_eq!(vm.lookup_str(name).unwrap_or_default(), "TypeError"); } +#[test] +fn function_apply_non_object_argarray_throws_type_error() { + // CreateListFromArrayLike:非对象 argArray(非 nullish)抛可捕获 TypeError; + // 目标非可调用先抛(先于 argArray 检查);null/undefined argArray 为无实参不抛。 + let mut vm = Vm::new(); + for src in [ + "try { function f(){} f.apply(null, true) } catch (e) { e.name }", + "try { function f(){} f.apply(null, NaN) } catch (e) { e.name }", + "try { function f(){} f.apply(null, '1,2,3') } catch (e) { e.name }", + "try { function f(){} f.apply(null, Symbol()) } catch (e) { e.name }", + "try { Function.prototype.apply.call(undefined, {}, true) } catch (e) { e.name }", + ] { + let name = eval(&mut vm, src).unwrap(); + assert_eq!(vm.lookup_str(name).unwrap_or_default(), "TypeError", "for {}", src); + } + let result = eval(&mut vm, "function f(){ return arguments.length; } f.apply(null, null)").unwrap(); + assert_num(result, 0.0); + let result = eval(&mut vm, "function f(){ return arguments.length; } f.apply(null, undefined)").unwrap(); + assert_num(result, 0.0); +} + #[test] fn function_call_non_callable_target_keeps_type_error_kind() { // call 转发到非可调用目标(原始值 thisArg):内层递归无原值可恢复时,