From c8740bfb49e40ec7d893ae77aea2aed65219bd31 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 9 Aug 2026 15:38:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=87=BD=E6=95=B0=20length=20=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=EF=BC=88=E5=AD=97=E8=8A=82=E7=A0=81=E5=87=BD=E6=95=B0?= =?UTF-8?q?/bind=20=E5=8C=85=E8=A3=85=E5=99=A8=EF=BC=89+=20getter/setter?= =?UTF-8?q?=20name=20=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/function.rs | 93 +++++++++++-- crates/oxide_bytecode/src/module.rs | 4 + crates/oxide_emit/src/class/method.rs | 8 +- crates/oxide_emit/src/emit.rs | 15 +++ .../src/expr/object/object_literal.rs | 8 +- crates/oxide_ir/src/lib.rs | 3 + crates/oxide_ir/src/lower.rs | 1 + crates/oxide_vm/src/dispatch/call.rs | 10 +- crates/oxide_vm/src/vm_props.rs | 14 +- .../oxide_vm/tests/function_length_tests.rs | 126 ++++++++++++++++++ 10 files changed, 263 insertions(+), 19 deletions(-) create mode 100644 crates/oxide_vm/tests/function_length_tests.rs diff --git a/crates/oxide_builtins/src/function.rs b/crates/oxide_builtins/src/function.rs index da6ad763..fa8a002e 100644 --- a/crates/oxide_builtins/src/function.rs +++ b/crates/oxide_builtins/src/function.rs @@ -1,8 +1,8 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; -use oxide_types::object::{JsObject, NativeFnPtr}; +use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; use oxide_types::value::JsValue; -use oxide_runtime_api::{to_string_full, NativeResult, VmHost}; +use oxide_runtime_api::{to_integer_or_infinity, to_string_full, NativeResult, VmHost}; fn invoke_target(vm: &mut H, target_val: JsValue, this_val: JsValue, arg_regs: &[u8]) -> NativeResult { let args: Vec = arg_regs.iter().map(|&r| vm.reg(r)).collect(); @@ -57,13 +57,16 @@ fn to_string_error_value(vm: &mut H, msg: &str) -> JsValue { fn bind_dispatcher(vm: &mut H, args: &[u8]) -> NativeResult { let wrapper_val = vm.reg(254); let wrapper = unsafe { &*wrapper_val.as_js_object_ptr() }; + // dense 布局:[length, name, caller, arguments, target, thisArg, ...boundArgs]—— + // 前 4 槽是 shape 属性(length/name 数据 + caller/arguments 访问器占位), + // 绑定状态从槽位 4 起(shape 槽位与 dense 下标对齐)。 let props = wrapper.hash_props_vec().cloned().unwrap_or_default(); - let bound_target = props.first().copied().unwrap_or(JsValue::undefined()); - let bound_this = props.get(1).copied().unwrap_or(JsValue::undefined()); + let bound_target = props.get(4).copied().unwrap_or(JsValue::undefined()); + let bound_this = props.get(5).copied().unwrap_or(JsValue::undefined()); - // 拼接调用实参:绑定实参(hash_props[2..])在前,本次调用实参(跳过 args[0] + // 拼接调用实参:绑定实参(props[6..])在前,本次调用实参(跳过 args[0] // 即绑定包装器的 receiver)在后,与规范的"绑定实参先于调用实参"一致。 - let mut call_args: Vec = props.iter().skip(2).copied().collect(); + let mut call_args: Vec = props.iter().skip(6).copied().collect(); for &r in args.iter().skip(1) { call_args.push(vm.reg(r)); } @@ -148,18 +151,78 @@ pub fn function_bind(vm: &mut H, args: &[u8]) -> NativeResult { )); } let bound_this = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let bound_arg_count = args.len().saturating_sub(2); - let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + // bound 函数 [[Prototype]] 为 Function.prototype(BoundFunctionCreate 语义)。 + let fn_proto_val = JsValue::from_js_object(vm.session().builtin_world().function_proto.as_ptr() as *mut JsObject); + let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); unsafe { (*wrapper).set_function(true); (*wrapper).set_native_fn(Some(NativeFnPtr::from_raw(bind_dispatcher:: as *const ()))); - // hash_props 布局:[target, thisArg, ...boundArgs];绑定实参个数记入 - // native_arg_count,与 Function.length 语义一致。 - (*wrapper).set_native_arg_count(args.len().saturating_sub(2) as u8); + // 绑定实参个数记入 native_arg_count,与 Function.length 语义一致。 + (*wrapper).set_native_arg_count(bound_arg_count as u8); + } + + // length/name 先定义占 dense 槽位 0/1(shape 属性),绑定状态 [target, thisArg, + // ...boundArgs] 随后从槽位 2 起存放,保证 shape 槽位与 dense 下标对齐。 + let length_si = vm.kernel_core().perm_interner().intern("length").0; + let name_si = vm.kernel_core().perm_interner().intern("name").0; + let caller_si = vm.kernel_core().perm_interner().intern("caller").0; + let arguments_si = vm.kernel_core().perm_interner().intern("arguments").0; + let attrs = PropAttributes::new(false, false, true); + + // bound 函数的 caller/arguments 是受限访问器:读写一律抛 TypeError(poisoned)。 + let thrower = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); + unsafe { + (*thrower).set_function(true); + (*thrower).set_native_fn(Some(NativeFnPtr::from_raw(bound_restricted_thrower:: as *const ()))); + } + let thrower_val = JsValue::from_js_object(thrower); + + // 全部 shape 属性(length/name/caller/arguments)须先于状态数据定义完成, + // 保证 shape 槽位与 dense 下标对齐,再 push [target, thisArg, ...boundArgs]。 + unsafe { + let wrapper_ref = &mut *wrapper; + 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); + // length = max(0, target.length - boundArgs);target.length 为 +∞ 时保持 +∞。 + let length = if target_length == f64::INFINITY { + f64::INFINITY + } else { + (target_length - bound_arg_count as f64).max(0.0) + }; + // 规范 length 无上界:i32 装得下用 int,否则(如 2^31、MAX_SAFE_INTEGER)用 float。 + let length_val = if length <= i32::MAX as f64 { + JsValue::int(length as i32) + } else { + JsValue::float(length) + }; + 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(); + let name_val = vm.new_string(&format!("bound {target_name}")); + 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)); + } + if let Err(e) = vm.define_accessor_property(wrapper_ref, caller_si, thrower_val, thrower_val, attrs) { + return NativeResult::Err(crate::error::create_type_error(vm, &e)); + } + if let Err(e) = vm.define_accessor_property(wrapper_ref, arguments_si, thrower_val, thrower_val, attrs) { + return NativeResult::Err(crate::error::create_type_error(vm, &e)); + } + } + unsafe { let props = (*wrapper).ensure_hash_props(); props.push(target_val); props.push(bound_this); - for &r in &args[2..] { + for &r in args.iter().skip(2) { props.push(vm.reg(r)); } } @@ -167,6 +230,14 @@ pub fn function_bind(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(wrapper)) } +/// bound 函数 caller/arguments 的受限访问器:任何访问(get/set)都抛 TypeError。 +fn bound_restricted_thrower(vm: &mut H, _args: &[u8]) -> NativeResult { + NativeResult::Err(crate::error::create_type_error( + vm, + "'caller' and 'arguments' are restricted on bound functions", + )) +} + /// `Function.prototype.toString`:返回 `function name() { [native code] }` /// 或 `[bytecode]` 形式;函数名取自 `name` 属性或字节码子模块名。 pub fn function_to_string(vm: &mut H, args: &[u8]) -> NativeResult { diff --git a/crates/oxide_bytecode/src/module.rs b/crates/oxide_bytecode/src/module.rs index b830678d..5a0aaac1 100644 --- a/crates/oxide_bytecode/src/module.rs +++ b/crates/oxide_bytecode/src/module.rs @@ -60,6 +60,8 @@ pub struct CompiledModule { pub captured_this_const_idx: u16, /// 由赋值上下文推断的函数名,在变量声明 / 对象属性赋值点设置。 pub function_name: Option, + /// 函数 `length` 属性值:第一个带默认值/解构默认的形参之前的形参数(rest 不计)。 + pub function_length: u32, /// 是否为类构造函数(普通 CALL 必须拒绝它,仅 NEW_EXPRESSION 可经它构造)。 pub is_class_constructor: bool, /// 类构造函数是否有 `extends` 子句(`this` 在 SUPER_CALL 完成前保持未初始化)。 @@ -87,6 +89,7 @@ impl CompiledModule { is_arrow: false, captured_this_const_idx: 0, function_name: None, + function_length: 0, is_class_constructor: false, is_derived_constructor: false, needs_home_object: false, @@ -116,6 +119,7 @@ impl Clone for CompiledModule { is_arrow: self.is_arrow, captured_this_const_idx: self.captured_this_const_idx, function_name: self.function_name.clone(), + function_length: self.function_length, is_class_constructor: self.is_class_constructor, is_derived_constructor: self.is_derived_constructor, needs_home_object: self.needs_home_object, diff --git a/crates/oxide_emit/src/class/method.rs b/crates/oxide_emit/src/class/method.rs index af4f74b7..bf60f7fb 100644 --- a/crates/oxide_emit/src/class/method.rs +++ b/crates/oxide_emit/src/class/method.rs @@ -112,7 +112,13 @@ impl Emitter { )?; ctx.in_instance_method = saved_instance; ctx.in_static_method = saved_static; - method_module.function_name = Some(method_name.to_string()); + // 访问器函数名带 "get "/"set " 前缀(SetFunctionName 语义),普通方法裸属性名。 + let display_name = match method.kind { + MethodDefinitionKind::Get => format!("get {method_name}"), + MethodDefinitionKind::Set => format!("set {method_name}"), + _ => method_name.to_string(), + }; + method_module.function_name = Some(display_name); method_module.needs_home_object = true; ctx.nested.push(method_module); let method_reg = ctx.alloc_reg(); diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 52b7512d..92828fe6 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -175,6 +175,9 @@ pub struct CompileCtx { /// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射, /// 消除符号表时序依赖与 cell 索引错位。 pub(crate) captured_bindings: BTreeMap, + /// 函数 `length` 属性值:首个带默认值形参之前的形参数(rest 不计)。 + /// emit_params_prologue 前由编译入口从 param_specs 计算。 + pub(crate) function_length: u32, pub(crate) const_overflow: bool, /// with 语句作用域栈:元素为 (with 对象寄存器, 打开时的作用域深度)。 /// 非空时 with 体内的自由标识符需动态解析(先查对象属性,回退外层)。 @@ -266,6 +269,7 @@ impl CompileCtx { current_upvalue_captures: Vec::new(), own_bindings: HashSet::new(), captured_bindings: BTreeMap::new(), + function_length: 0, const_overflow: false, with_stack: Vec::new(), open_try_handlers: Vec::new(), @@ -601,6 +605,7 @@ impl CompileCtx { needs_home_object: false, captured_this_const_idx: 0, function_name: None, + function_length: self.function_length, is_top_level: parent_ctx.is_none(), const_overflow: self.const_overflow, nested: std::mem::take(&mut self.nested), @@ -748,6 +753,16 @@ impl Emitter { { let mut ctx = CompileCtx::new(); + // length = 第一个带默认值形参之前的形参数(解构默认与标识符默认同规则); + // rest 参数不在 param_specs 中(独立字段),天然不计入。 + ctx.function_length = param_specs + .iter() + .take_while(|spec| match spec { + ParamSpec::Identifier { initializer, .. } => initializer.is_none(), + ParamSpec::Pattern { initializer, .. } => initializer.is_none(), + }) + .count() as u32; + // 继承父内置寄存器映射:子模块寄存器文件中,内置标识符(Math、Object 等) // 解析到父预先分配的槽位。 ctx.scopes.builtin_reg_map = parent_ctx.scopes.builtin_reg_map.clone(); diff --git a/crates/oxide_emit/src/expr/object/object_literal.rs b/crates/oxide_emit/src/expr/object/object_literal.rs index 13e70317..2eb9b701 100644 --- a/crates/oxide_emit/src/expr/object/object_literal.rs +++ b/crates/oxide_emit/src/expr/object/object_literal.rs @@ -45,7 +45,13 @@ impl Emitter { } let accessor_reg = self.emit_expression(&p.value, ctx)?; if let Some(sub_mod) = ctx.nested.last_mut() { - sub_mod.function_name = Some(prop_name.to_string()); + // 访问器函数名带 "get "/"set " 前缀(SetFunctionName 语义)。 + let fn_name = match p.kind { + PropertyKind::Get => format!("get {prop_name}"), + PropertyKind::Set => format!("set {prop_name}"), + _ => prop_name.clone(), + }; + sub_mod.function_name = Some(fn_name); } let undef_reg = self.emit_undefined(ctx); let (get_reg, set_reg) = if p.kind == PropertyKind::Get { diff --git a/crates/oxide_ir/src/lib.rs b/crates/oxide_ir/src/lib.rs index 598cba98..c0c9b2e1 100644 --- a/crates/oxide_ir/src/lib.rs +++ b/crates/oxide_ir/src/lib.rs @@ -52,6 +52,8 @@ pub struct IRFunction { pub needs_home_object: bool, pub captured_this_const_idx: u16, pub function_name: Option, + /// 函数 `length` 属性值:第一个带默认值/解构默认的形参之前的形参数(rest 不计)。 + pub function_length: u32, /// 本函数是否为顶层脚本函数:顶层 STORE_VAR 写全局可观察状态,不可被精确 DCE /// 删除;函数内局部 STORE_VAR 才可删。emit 在 assemble_ir 处由 /// `parent_ctx.is_none()` 填充;nested/手工构造默认 false。 @@ -80,6 +82,7 @@ impl IRFunction { needs_home_object: false, captured_this_const_idx: 0, function_name: None, + function_length: 0, is_top_level: false, const_overflow: false, nested: Vec::new(), diff --git a/crates/oxide_ir/src/lower.rs b/crates/oxide_ir/src/lower.rs index 1e9c2fea..583145fb 100644 --- a/crates/oxide_ir/src/lower.rs +++ b/crates/oxide_ir/src/lower.rs @@ -85,6 +85,7 @@ pub fn lower(f: &IRFunction) -> Result { is_arrow: f.is_arrow, captured_this_const_idx: f.captured_this_const_idx, function_name: f.function_name.clone(), + function_length: f.function_length, is_class_constructor: f.is_class_constructor, is_derived_constructor: f.is_derived_constructor, needs_home_object: f.needs_home_object, diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index 18aaa69f..670a16ea 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -121,6 +121,7 @@ impl Vm { let needs_home_object = sub.needs_home_object; let upvalue_captures = sub.upvalue_captures.clone(); let function_name = sub.function_name.clone(); + let function_length = sub.function_length; let result = self.create_function_object( sub_idx, is_arrow, @@ -130,13 +131,18 @@ impl Vm { ); // 函数名推断:emit 端在变量声明/对象属性赋值点设置 function_name。 let func_obj = unsafe { &mut *result.as_js_object_ptr() }; + let length_si = self.kernel_core.perm_interner().intern("length").0; let name_si = self.kernel_core.perm_interner().intern("name").0; + // length/name 描述符均为不可写、不可枚举、可配置(SetFunctionLength / + // SetFunctionName 语义);length 先于 name 定义保证属性序 [prototype, length, name]。 + let attrs = PropAttributes::new(false, false, true); + let length_val = JsValue::int(function_length as i32); + self.define_data_property(func_obj, length_si, length_val, attrs)?; let name_val = function_name .as_deref() .map(|n| self.new_string(n)) .unwrap_or_else(|| self.new_string("")); - // name 属性描述符:不可写、不可枚举、可配置(SetFunctionName 语义)。 - self.define_data_property(func_obj, name_si, name_val, PropAttributes::new(false, false, true))?; + self.define_data_property(func_obj, name_si, name_val, attrs)?; if !upvalue_captures.is_empty() { // 链式捕获(parent_uv_idx):从父闭包(创建者)的 upvalues 取 cell。 let parent_upvalues: Vec<*mut Cell> = match self.current_callee() { diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index ed1b87ef..6ac9a424 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -237,9 +237,16 @@ impl Vm { &mut self, setter: JsValue, receiver: JsValue, val: JsValue, use_frame_push: bool, ) -> Result<(), String> { vm_trace!("call_or_push_setter: frame_push={}", use_frame_push); + // native setter 抛错经 call_function_sync 以 String 返回(未 unwind)—— + // 在此经 raise_call_error 恢复为可捕获的 JS 异常(与 getter 路径对称)。 + let call_native = |vm: &mut Self| -> Result<(), String> { + if let Err(err) = vm.call_function_sync(setter, receiver, &[val]) { + vm.raise_call_error(&err)?; + } + Ok(()) + }; if !use_frame_push { - self.call_function_sync(setter, receiver, &[val])?; - return Ok(()); + return call_native(self); } if !setter.is_object() { return self.raise_type_error("setter is not callable"); @@ -249,8 +256,7 @@ impl Vm { return self.raise_type_error("setter is not callable"); } if setter_obj.native_fn().is_some() { - self.call_function_sync(setter, receiver, &[val])?; - return Ok(()); + return call_native(self); } self.push_bytecode_frame( setter, diff --git a/crates/oxide_vm/tests/function_length_tests.rs b/crates/oxide_vm/tests/function_length_tests.rs new file mode 100644 index 00000000..848e0eea --- /dev/null +++ b/crates/oxide_vm/tests/function_length_tests.rs @@ -0,0 +1,126 @@ +//! 函数 length/name 属性集成测试:普通字节码函数、箭头函数、bind 包装器、 +//! getter/setter 前缀。每个用例独立编译执行,断言顶层表达式结果。 + +use oxide_bytecode::module::CompiledModule; +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> JsValue { + let allocator = Allocator::default(); + let program = oxide_parser::parse(&allocator, source).expect("parse failed"); + let module = Compiler::new().compile(&program).expect("compile failed"); + run(&module) +} + +fn run(module: &CompiledModule) -> JsValue { + let mut vm = Vm::new(); + vm.run(module).expect("vm run failed") +} + +fn eval_str(source: &str) -> String { + let v = eval(source); + if v.is_string() { + unsafe { oxide_runtime_api::string_data(v) }.to_string() + } else { + format!("{v}") + } +} + +#[test] +fn bytecode_function_length_counts_formals_before_first_default() { + assert_eq!(eval("function f(a,b,c){}; f.length"), JsValue::int(3)); + assert_eq!(eval("function f(a,b=1,c){}; f.length"), JsValue::int(1)); + assert_eq!(eval("function f(...r){}; f.length"), JsValue::int(0)); + assert_eq!(eval("var f = function named(a,b,c){}; f.length"), JsValue::int(3)); +} + +#[test] +fn arrow_function_length_matches_formals() { + assert_eq!(eval("var f=(a,b)=>a+b; f.length"), JsValue::int(2)); + assert_eq!(eval("var f=(a,b,c)=>a+b+c; f.length"), JsValue::int(3)); +} + +#[test] +fn class_method_and_accessor_names() { + assert_eq!(eval("class C { m(a,b){} }; C.prototype.m.length"), JsValue::int(2)); + assert_eq!( + eval_str("class C { get x(){} set x(v){} }; Object.getOwnPropertyDescriptor(C.prototype,'x').get.name"), + "get x" + ); + assert_eq!( + eval_str("class C { get x(){} set x(v){} }; Object.getOwnPropertyDescriptor(C.prototype,'x').set.name"), + "set x" + ); +} + +#[test] +fn object_literal_accessor_names() { + assert_eq!( + eval_str("var o={ get x(){}, set x(v){} }; Object.getOwnPropertyDescriptor(o,'x').get.name"), + "get x" + ); + assert_eq!( + eval_str("var o={ get x(){}, set x(v){} }; Object.getOwnPropertyDescriptor(o,'x').set.name"), + "set x" + ); +} + +#[test] +fn bind_wrapper_length_is_target_length_minus_bound_args() { + assert_eq!(eval("function f(a,b){}; f.bind(null,1).length"), JsValue::int(1)); + assert_eq!(eval("function f(){}; f.bind(null,1,2,3).length"), JsValue::int(0)); + assert_eq!(eval("function f(a,b,c){}; f.bind(null).length"), JsValue::int(3)); +} + +#[test] +fn bind_wrapper_name_has_bound_prefix() { + assert_eq!(eval_str("function f(a){}; f.bind(null).name"), "bound f"); + assert_eq!(eval_str("(function(){}).bind(null).name"), "bound "); +} + +#[test] +fn bind_wrapper_length_supports_infinity_and_large_values() { + assert_eq!( + eval("function f(){}; Object.defineProperty(f,'length',{value:Infinity}); f.bind().length === Infinity"), + JsValue::bool(true) + ); + assert_eq!( + eval("function f(){}; Object.defineProperty(f,'length',{value:2147483648}); f.bind().length"), + JsValue::float(2147483648.0) + ); +} + +#[test] +fn bind_wrapper_caller_and_arguments_are_poisoned() { + assert_eq!( + eval_str("var b=(function(){}).bind({}); try { b.caller; 'no' } catch(e) { e instanceof TypeError ? 'throws' : 'wrong' }"), + "throws" + ); + assert_eq!( + eval_str("var b=(function(){}).bind({}); try { b.caller = 1; 'no' } catch(e) { e instanceof TypeError ? 'throws' : 'wrong' }"), + "throws" + ); +} + +#[test] +fn length_and_name_descriptors_are_non_writable_non_enumerable_configurable() { + assert_eq!( + eval_str("function f(){}; var d=Object.getOwnPropertyDescriptor(f,'length'); d.writable===false && d.enumerable===false && d.configurable===true"), + "true" + ); +} + +#[test] +fn length_comes_before_name_in_property_order() { + assert_eq!( + eval_str("function f(a,b,c){}; var p=Object.getOwnPropertyNames(f); p[p.indexOf('length')] < p[p.indexOf('name')]"), + "true" + ); +} + +#[test] +fn anonymous_function_name_is_empty_string() { + assert_eq!(eval_str("(function(){}).name"), ""); +}