From 1c138157620029d8f15e949f79149a8106be293b Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 27 Sep 2026 00:05:31 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20bind=20=E7=9A=84=20length/name=20?= =?UTF-8?q?=E6=94=B9=E8=87=AA=E8=BA=AB=E9=97=A8=E4=B8=8E=E4=BC=A0=E6=92=AD?= =?UTF-8?q?=E8=AF=BB=EF=BC=8Cgetter=20=E6=8A=9B=E9=94=99=E5=8E=9F=E5=80=BC?= =?UTF-8?q?=E4=B8=8A=E6=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/function.rs | 36 ++++++++++++---- .../oxide_vm/tests/builtins_function_tests.rs | 42 +++++++++++++++++++ 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/crates/oxide_builtins/src/function.rs b/crates/oxide_builtins/src/function.rs index 2144d9ae..4fbbd53a 100644 --- a/crates/oxide_builtins/src/function.rs +++ b/crates/oxide_builtins/src/function.rs @@ -254,6 +254,10 @@ pub fn function_apply(vm: &mut H, args: &[u8]) -> NativeResult { /// `Function.prototype.bind(thisArg, ...args)`:返回绑定 this 与前置实参的新包装函数, /// 调用时通过 `bind_dispatcher` 把绑定实参拼到调用实参前转发到原目标。非函数目标抛 TypeError。 +/// +/// length = max(0, target.length - 绑定实参数):仅当 target 自有 length 且为 Number +/// 时读(传播读,getter 抛错原值传播),否则 0。name = "bound " + target.name +/// (传播读,非字符串 → 空串)。 pub fn function_bind(vm: &mut H, args: &[u8]) -> NativeResult { if args.is_empty() { return NativeResult::Err(crate::error::create_type_error( @@ -298,12 +302,20 @@ pub fn function_bind(vm: &mut H, args: &[u8]) -> NativeResult { // 全部 shape 属性(length/name)须先于状态数据定义完成,保证 shape 槽位 // 与 dense 下标对齐,再 push [target, thisArg, ...boundArgs]。 unsafe { - let wrapper_ref = &mut *wrapper; + // length:HasOwnProperty 门(非自身 → 0)+ 传播 Get(getter 抛错原值 + // 传播)+ Number 类型门(非 Number → 0)。 + // SAFETY: target_val 上方已校验为非空函数对象指针;读取即时消费,不跨 GC/reset。 let target_obj = &*target_val.as_js_object_ptr(); - let target_length = vm - .resolve_property(target_obj, length_si) - .map(to_integer_or_infinity) - .unwrap_or(0.0); + let mut target_length = 0.0; + if vm.get_own_property_slot(target_obj, length_si).is_some() { + let own_length = match vm.ordinary_get(target_obj, length_si, target_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(to_string_error_value(vm, &e)), + }; + if own_length.js_type().is_number() { + target_length = to_integer_or_infinity(own_length); + } + } // length = max(0, target.length - boundArgs);target.length 为 +∞ 时保持 +∞。 let length = if target_length == f64::INFINITY { f64::INFINITY @@ -316,14 +328,20 @@ pub fn function_bind(vm: &mut H, args: &[u8]) -> NativeResult { } else { JsValue::float(length) }; + // 传播读后重取包装器指针。 + let wrapper_ref = &mut *wrapper; if let Err(e) = vm.define_data_property(wrapper_ref, length_si, length_val, attrs) { return NativeResult::Err(crate::error::create_type_error(vm, &e)); } - let target_name = vm - .resolve_property(target_obj, name_si) - .and_then(|v| vm.lookup_str(v)) - .unwrap_or_default(); + // name:传播 Get(getter 抛错原值传播),非字符串 → 空串。 + let target_obj = &*target_val.as_js_object_ptr(); + let target_name = match vm.ordinary_get(target_obj, name_si, target_val) { + Ok(v) => vm.lookup_str(v).unwrap_or_default(), + Err(e) => return NativeResult::Err(to_string_error_value(vm, &e)), + }; let name_val = vm.new_string(&format!("bound {target_name}")); + // 传播读后重取包装器指针。 + let wrapper_ref = &mut *wrapper; if let Err(e) = vm.define_data_property(wrapper_ref, name_si, name_val, attrs) { return NativeResult::Err(crate::error::create_type_error(vm, &e)); } diff --git a/crates/oxide_vm/tests/builtins_function_tests.rs b/crates/oxide_vm/tests/builtins_function_tests.rs index 8e03dfd2..3489d85d 100644 --- a/crates/oxide_vm/tests/builtins_function_tests.rs +++ b/crates/oxide_vm/tests/builtins_function_tests.rs @@ -342,3 +342,45 @@ fn bound_function_newtarget_pins() { .unwrap(); assert!(result.as_bool()); } + +#[test] +fn bound_function_length_name_pins() { + let mut vm = Vm::new(); + // length:非自身 length(原型链 42 泄漏形态)→ 0;自身非 Number → 0; + // 自身 Number 经 ToIntegerOrInfinity 减绑定实参数。 + let result = eval( + &mut vm, + "function bar() {} Object.setPrototypeOf(bar, {length: 42}); delete bar.length; Function.prototype.bind.call(bar, null, 1).length", + ) + .unwrap(); + assert_num(result, 0.0); + let result = eval( + &mut vm, + "function t() {} Object.defineProperty(t, 'length', {value: '5'}); t.bind(null, 1).length", + ) + .unwrap(); + assert_num(result, 0.0); + let result = eval( + &mut vm, + "function t() {} Object.defineProperty(t, 'length', {value: 3.66}); t.bind().length", + ) + .unwrap(); + assert_num(result, 3.0); + let result = eval( + &mut vm, + "function t() {} Object.defineProperty(t, 'length', {value: Infinity}); t.bind().length", + ) + .unwrap(); + assert!(result.as_double() == f64::INFINITY); + // name:getter 抛错须原值传播;chained 读 bound 包装自身 name。 + let result = eval( + &mut vm, + "var threw = false; try { Object.defineProperty(function(){}, 'name', {get: function() { throw new Error('x'); }}).bind(); } catch (e) { threw = e.message === 'x'; } threw", + ) + .unwrap(); + assert!(result.as_bool()); + let result = eval(&mut vm, "function target() {} target.bind().name").unwrap(); + assert_eq!(vm.lookup_str(result).unwrap_or_default(), "bound target"); + let result = eval(&mut vm, "function target() {} target.bind().bind().name").unwrap(); + assert_eq!(vm.lookup_str(result).unwrap_or_default(), "bound bound target"); +}