fix: bind 的 length/name 改自身门与传播读,getter 抛错原值上抛

This commit is contained in:
2026-09-27 00:05:31 +00:00
parent ed3b7619fa
commit 1c13815762
2 changed files with 69 additions and 9 deletions
+27 -9
View File
@@ -254,6 +254,10 @@ pub fn function_apply<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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));
}
@@ -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");
}