From bfcf132d73b3edb9af13885874f5df576b1bc2de Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 8 Aug 2026 00:01:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20arguments=20=E5=AF=B9=E8=B1=A1=E7=BB=91?= =?UTF-8?q?=E5=AE=9A=EF=BC=88CREATE=5FARGUMENTS=20+=20=E5=B8=A7=E5=AE=9E?= =?UTF-8?q?=E5=8F=82=20spill=20=E5=8C=BA=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_bytecode/src/opcode.rs | 7 +- crates/oxide_emit/src/emit.rs | 27 ++++++ crates/oxide_ir/src/contract.rs | 13 ++- crates/oxide_ir/src/inst.rs | 13 +++ crates/oxide_vm/src/dispatch/call.rs | 12 ++- crates/oxide_vm/src/dispatch/misc.rs | 38 +++++++- crates/oxide_vm/src/session_gc.rs | 2 + crates/oxide_vm/src/vm.rs | 28 +++++- crates/oxide_vm/src/vm_dispatch_misc.rs | 10 ++ crates/oxide_vm/src/vm_runtime.rs | 8 ++ crates/oxide_vm/src/vm_support.rs | 4 + crates/oxide_vm/tests/arguments_tests.rs | 112 +++++++++++++++++++++++ 12 files changed, 267 insertions(+), 7 deletions(-) create mode 100644 crates/oxide_vm/tests/arguments_tests.rs diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index 6b141c9d..008d3085 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -164,8 +164,10 @@ define_opcodes! { COMPOUND_MEMBER_MOD = 0x61 => "COMPOUND_MEMBER_MOD", COMPOUND_MEMBER_EXP = 0x62 => "COMPOUND_MEMBER_EXP", - // ── Profiling — 占位符 (0x63-0x6F) ── - PROFILE_TYPE = 0x63 => "PROFILE_TYPE", + // ── 对象 (0x63) ── + CREATE_ARGUMENTS = 0x63 => "CREATE_ARGUMENTS", + + // ── Profiling — 占位符 (0x64-0x6F) ── PROFILE_SHAPE = 0x64 => "PROFILE_SHAPE", PROFILE_BRANCH = 0x65 => "PROFILE_BRANCH", PROFILE_CALL = 0x66 => "PROFILE_CALL", @@ -326,6 +328,7 @@ mod tests { assert_eq!(OpCode::MOV as u8, 0x0C); assert_eq!(OpCode::SPILL as u8, 0x0D); assert_eq!(OpCode::UNSPILL as u8, 0x0E); + assert_eq!(OpCode::CREATE_ARGUMENTS as u8, 0x63); assert_eq!(OpCode::ADD.to_string(), "ADD"); assert_eq!(OpCode::COMPOUND_MEMBER_EXP.to_string(), "COMPOUND_MEMBER_EXP"); assert_eq!(OpCode::MOV.to_string(), "MOV"); diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index a4c54327..3fe16379 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -803,6 +803,18 @@ impl Emitter { } } ctx.own_bindings = self.collect_own_binding_names(¶m_names, body_stmts); + + // 自动声明 arguments 绑定(非箭头函数,且用户未显式声明同名标识符)。 + // 先登记符号并纳入 own_bindings,使嵌套箭头引用 arguments 被识别为本函数 + // 绑定(否则被当自由变量 → 子模块 upvalue 解析错位)。 + let mut arguments_reg = None; + if !matches!(body_context, FunctionBodyContext::Arrow) && !ctx.own_bindings.contains("arguments") { + let reg = ctx.alloc_reg(); + ctx.declare_initialized("arguments", reg, VariableDeclarationKind::Var, false)?; + ctx.own_bindings.insert("arguments".to_string()); + arguments_reg = Some(reg); + } + ctx.captured_bindings = self.collect_captured_bindings(body_stmts, ¶m_defaults, &ctx.own_bindings); // 自由变量分析:收集 upvalue 捕获(类方法也是普通函数,可捕获外层变量)。 if matches!( @@ -818,6 +830,11 @@ impl Emitter { ); } + // 创建 arguments 对象:指令须在默认参数求值前发出(默认参数可引用 arguments)。 + if let Some(reg) = arguments_reg { + ctx.inst(Inst::create_arguments(Operand::Reg(reg))); + } + for spec in param_specs { match spec { ParamSpec::Pattern { synthetic_name, pattern, initializer } => { @@ -856,6 +873,16 @@ impl Emitter { } } + // 被捕获的 arguments 绑定同样建 cell(与参数一致,须在默认值之后)。 + if let (Some(reg), Some(&cell_idx)) = (arguments_reg, ctx.captured_bindings.get("arguments")) { + ctx.inst(Inst::new( + OpCode::MAKE_CELL, + Operand::Reg(reg), + Operand::Imm(cell_idx as u16), + Operand::None, + )); + } + Ok(param_base) } diff --git a/crates/oxide_ir/src/contract.rs b/crates/oxide_ir/src/contract.rs index 7ba7887e..f2ce3360 100644 --- a/crates/oxide_ir/src/contract.rs +++ b/crates/oxide_ir/src/contract.rs @@ -199,6 +199,7 @@ impl Inst { | OpCode::VOID | OpCode::NEW_OBJECT | OpCode::NEW_ARRAY + | OpCode::CREATE_ARGUMENTS | OpCode::NOP => {} // 变量读写:LOAD_VAR/STORE_VAR/CELL_GET 读 a(None→0) OpCode::LOAD_VAR | OpCode::STORE_VAR | OpCode::CELL_GET => { @@ -256,7 +257,6 @@ impl Inst { } // 占位 opcode(emit 不产):按无 use 保守处理,不影响合法产物 OpCode::SWITCH_TABLE - | OpCode::PROFILE_TYPE | OpCode::PROFILE_SHAPE | OpCode::PROFILE_BRANCH | OpCode::PROFILE_CALL @@ -530,6 +530,17 @@ mod tests { assert_eq!(inst.use_regs().as_slice(), &[1]); } + #[test] + fn create_arguments_defs_rd_no_register_use_impure() { + // CREATE_ARGUMENTS 写 rd;实参从 spill 栈读取,无寄存器 use; + // 创建对象有观察副作用(分配的 arguments 可被外部观察到),不可删除。 + let inst = Inst::create_arguments(Operand::Reg(6)); + assert_eq!(inst.def_reg(), Some(6)); + assert!(inst.use_regs().is_empty()); + let f = IRFunction::new(); + assert!(!inst.is_pure(&f)); + } + #[test] fn for_in_next_defs_rd_no_use() { let inst = Inst::new(OpCode::FOR_IN_NEXT, Operand::Reg(0), Operand::None, Operand::None); diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index 738e6c7e..f2e7feb4 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -178,6 +178,12 @@ impl Inst { Self::new(OpCode::CREATE_CLOSURE, dst, Operand::Imm(sub_idx), Operand::None) } + /// 创建 arguments 对象:`dst = 当前帧的实参列表`。运行时从当前帧 + /// (CallFrame 或 inline 同步调用)的实参区构建,无寄存器 use。 + pub fn create_arguments(dst: Operand) -> Self { + Self::new(OpCode::CREATE_ARGUMENTS, dst, Operand::None, Operand::None) + } + // ── 跳转族:label 放 b 槽,offset 计算是 lowering 职责 ── /// 无条件跳转。label 放 b 槽,offset 由 lowering 回填。 @@ -311,6 +317,13 @@ mod tests { assert_eq!(cc.a, Operand::Imm(5)); assert_eq!(cc.b, Operand::None); assert!(cc.ext.is_empty()); + + let ca = Inst::create_arguments(Operand::Reg(6)); + assert_eq!(ca.op, OpCode::CREATE_ARGUMENTS); + assert_eq!(ca.rd, Operand::Reg(6)); + assert_eq!(ca.a, Operand::None); + assert_eq!(ca.b, Operand::None); + assert!(ca.ext.is_empty()); } #[test] diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index 1d5a3064..3e4939fe 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -238,7 +238,7 @@ impl Vm { /// 当前执行函数的闭包对象:普通路径取 frames 栈顶帧,inline(sync 回调)路径 /// frames 被隔离为空,取 `inline_callee`。 - fn current_callee(&self) -> Option { + pub(crate) fn current_callee(&self) -> Option { self.frames.last().map(|f| f.callee).or(self.inline_callee) } @@ -465,6 +465,14 @@ impl Vm { let saved_this = self.regs[254]; let saved_new_target = self.regs[255]; + // 完整实参写入 spill 栈实参区(在帧的 spill 区之前),供 CREATE_ARGUMENTS 使用。 + let args_base = self.spill_stack.len() as u32; + for i in 0..arg_count { + let src_reg = first_arg_reg.wrapping_add(i as u8) as usize; + self.spill_stack.push(self.regs[src_reg]); + } + let args_count = arg_count.min(u16::MAX as usize) as u16; + for i in 0..sub_n_args { let src_reg = first_arg_reg.wrapping_add(i as u8) as usize; self.regs[sub_param_base + i] = self.regs[src_reg]; @@ -487,6 +495,8 @@ impl Vm { caller_reg_limit, saved_reg_offset, spill_offset: self.spill_stack.len() as u32, + arguments_base: args_base, + arguments_count: args_count, saved_this, saved_new_target, callee: super_ctor, diff --git a/crates/oxide_vm/src/dispatch/misc.rs b/crates/oxide_vm/src/dispatch/misc.rs index e9f0cbbd..b48bd169 100644 --- a/crates/oxide_vm/src/dispatch/misc.rs +++ b/crates/oxide_vm/src/dispatch/misc.rs @@ -2,7 +2,7 @@ use crate::vm::Vm; use crate::{vm_error, vm_trace}; use oxide_bytecode::opcode; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; -use oxide_types::object::JsObject; +use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::value::JsValue; impl Vm { @@ -132,6 +132,42 @@ impl Vm { self.regs[rd] = JsValue::object(obj as *mut u8); } + /// 创建 arguments 对象:索引属性取当前帧(或 inline 同步调用)的完整实参, + /// 附 length / callee 属性。第一版为 unmapped(非严格)语义,索引与形参不同步。 + /// + /// # 边界与前提 + /// - 实参区由 `push_bytecode_frame` / `dispatch_super_call` / `dispatch_new_expression` + /// 在推帧时写入 spill 栈;frames 为空(inline 同步调用)时读 `inline_args_*`。 + /// - 索引属性用 shape 槽存储(普通对象),length/callee 为不可枚举数据属性。 + pub(crate) fn dispatch_create_arguments(&mut self, rd: usize) -> Result<(), String> { + let (base, count) = match self.frames.last() { + Some(frame) => (frame.arguments_base, frame.arguments_count), + None => (self.inline_args_base, self.inline_args_count), + }; + let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject; + let obj_ptr = self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr))); + let obj = unsafe { &mut *obj_ptr }; + + // 索引属性:按实参下标写入 shape 槽,属性描述符为默认(可写/可枚举/可配置)。 + for i in 0..count as usize { + let si = self.kernel_core.perm_interner().intern(&i.to_string()).0; + let val = self.spill_stack.get(base as usize + i).copied().unwrap_or(JsValue::undefined()); + self.set_or_create_prop_value(obj, si, val); + } + + // length:实参个数,可写、不可枚举、可配置。 + let length_si = self.kernel_core.perm_interner().intern("length").0; + self.define_data_property(obj, length_si, JsValue::int(count as i32), PropAttributes::new(true, false, true))?; + + // callee:当前执行函数,可写、不可枚举、可配置(严格模式应抛 TypeError,未支持)。 + let callee_si = self.kernel_core.perm_interner().intern("callee").0; + let callee = self.current_callee().unwrap_or(JsValue::undefined()); + self.define_data_property(obj, callee_si, callee, PropAttributes::new(true, false, true))?; + + self.regs[rd] = JsValue::from_js_object(obj_ptr); + Ok(()) + } + #[inline(always)] pub(crate) fn dispatch_new_array(&mut self, rd: usize, instr: u32) { let n = opcode::imm16(instr) as usize; diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index dee5159d..54c5e887 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -589,6 +589,8 @@ mod tests { caller_reg_limit: 1, saved_reg_offset: 0, spill_offset: 0, + arguments_base: 0, + arguments_count: 0, saved_this: JsValue::from_js_object(this_session), saved_new_target: JsValue::from_js_object(child_session), callee: JsValue::from_js_object(child_session), diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 05a5b2a4..d1838a6b 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -112,6 +112,10 @@ pub struct CallFrame { pub saved_reg_offset: u32, /// 记录本帧 spill 栈起始长度,作为 SPILL/UNSPILL 的帧边界基址(push 时快照)。 pub spill_offset: u32, + /// 本帧完整实参在 spill 栈的起始下标(帧恢复时随 spill 区一起截断丢弃)。 + pub arguments_base: u32, + /// 实际传入的实参个数(可大于形参个数)。 + pub arguments_count: u16, pub saved_this: JsValue, pub saved_new_target: JsValue, pub callee: JsValue, @@ -164,6 +168,8 @@ pub(crate) struct InlineSyncState { pub(crate) spill_stack: Vec, pub(crate) cell_stack: Vec>, pub(crate) inline_callee: Option, + pub(crate) inline_args_base: u32, + pub(crate) inline_args_count: u16, } /// 基于寄存器的 JS 虚拟机:持有执行状态、寄存器文件、调用栈与 session 内存。 @@ -211,6 +217,10 @@ pub struct Vm { pub(crate) root_reg_limit: u8, pub(crate) active_reg_limit: u8, pub(crate) native_call_depth: usize, + /// inline 同步调用(`call_bytecode_function_inline`,frames 为空)的实参区位置。 + /// frames 非空时 CREATE_ARGUMENTS 优先读当前帧的实参区;此字段只服务内联路径。 + pub(crate) inline_args_base: u32, + pub(crate) inline_args_count: u16, /// `ordinary_get` 压入字节码 accessor 帧时设为 Some(target_reg)。 /// 调度循环检查该标志,跳过用调用结果写 `regs[target_reg]` —— 值改由 RETURN /// 处理器交付。 @@ -822,12 +832,20 @@ impl Vm { .map(|name| self.kernel_core.perm_interner().intern(name).0) .unwrap_or(0); + // 完整实参写入 spill 栈实参区(在帧的 spill 区之前):CREATE_ARGUMENTS 据此 + // 构建 arguments 对象,帧恢复时随 spill 区截断一起丢弃。 + let args_base = self.spill_stack.len() as u32; + self.spill_stack.extend_from_slice(args); + let args_count = args.len().min(u16::MAX as usize) as u16; + self.frames.push(CallFrame { return_addr: self.pc, function_name, caller_reg_limit, saved_reg_offset, spill_offset: self.spill_stack.len() as u32, + arguments_base: args_base, + arguments_count: args_count, saved_this, saved_new_target, callee, @@ -1144,6 +1162,10 @@ impl Vm { self.dispatch_new_object(rd); } + OpCode::CREATE_ARGUMENTS => { + self.dispatch_create_arguments(rd)?; + } + OpCode::NEW_ARRAY => { self.dispatch_new_array(rd, instr); } @@ -1553,6 +1575,8 @@ mod tests { caller_reg_limit: 2, saved_reg_offset: 0, spill_offset: 0, + arguments_base: 0, + arguments_count: 0, saved_this: JsValue::undefined(), saved_new_target: JsValue::undefined(), callee: JsValue::undefined(), @@ -1653,7 +1677,7 @@ mod tests { fn unimplemented_profile_opcode_fails_explicitly() { let module = CompiledModule { bytecode: vec![ - opcode::encode(opcode::OpCode::PROFILE_TYPE, 0, 0, 0), + opcode::encode(opcode::OpCode::PROFILE_SHAPE, 0, 0, 0), opcode::encode(opcode::OpCode::HALT, 0, 0, 0), ], n_registers: 1, @@ -1661,7 +1685,7 @@ mod tests { }; let mut vm = Vm::new(); let err = vm.run(&module).expect_err("unimplemented opcode should fail explicitly"); - assert_eq!(err, "opcode PROFILE_TYPE not yet implemented"); + assert_eq!(err, "opcode PROFILE_SHAPE not yet implemented"); } #[test] diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index c9f43571..dbd5c2d0 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -113,6 +113,14 @@ impl Vm { let saved_this = self.regs[254]; let saved_new_target = self.regs[255]; + // 完整实参写入 spill 栈实参区(在帧的 spill 区之前),供 CREATE_ARGUMENTS 使用。 + let args_base = self.spill_stack.len() as u32; + for i in 0..arg_count { + let src_reg = first_arg_reg.wrapping_add(i as u8) as usize; + self.spill_stack.push(self.regs[src_reg]); + } + let args_count = arg_count.min(u16::MAX as usize) as u16; + for i in 0..sub_n_args { let src_reg = first_arg_reg.wrapping_add(i as u8) as usize; self.regs[sub_param_base + i] = self.regs[src_reg]; @@ -137,6 +145,8 @@ impl Vm { caller_reg_limit, saved_reg_offset, spill_offset: self.spill_stack.len() as u32, + arguments_base: args_base, + arguments_count: args_count, saved_this, saved_new_target, callee: constructor, diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 8e5d7511..e0776622 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -81,6 +81,8 @@ impl Vm { spill_stack: std::mem::take(&mut self.spill_stack), cell_stack: std::mem::take(&mut self.cell_stack), inline_callee: self.inline_callee, + inline_args_base: self.inline_args_base, + inline_args_count: self.inline_args_count, }); self.regs = [JsValue::undefined(); 256]; @@ -91,6 +93,10 @@ impl Vm { self.root_reg_limit = self.active_reg_limit; self.cell_stack.push(Vec::with_capacity(sub.cells_needed as usize)); self.inline_callee = Some(callee); + // inline 同步调用无 CallFrame:完整实参写入 spill 栈实参区,供 CREATE_ARGUMENTS 读取。 + self.inline_args_base = self.spill_stack.len() as u32; + self.spill_stack.extend_from_slice(args); + self.inline_args_count = args.len().min(u16::MAX as usize) as u16; for i in 0..sub.n_args as usize { self.regs[sub.param_base as usize + i] = args.get(i).copied().unwrap_or(JsValue::undefined()); } @@ -136,6 +142,8 @@ impl Vm { self.spill_stack = saved.spill_stack; self.cell_stack = saved.cell_stack; self.inline_callee = saved.inline_callee; + self.inline_args_base = saved.inline_args_base; + self.inline_args_count = saved.inline_args_count; result } diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index f90c1e05..2695187d 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -46,6 +46,8 @@ impl Vm { root_reg_limit: 0, active_reg_limit: 0, native_call_depth: 0, + inline_args_base: 0, + inline_args_count: 0, accessor_frame_target_reg: None, inline_callee: None, gc_state: GcState { @@ -109,6 +111,8 @@ impl Vm { root_reg_limit: 0, active_reg_limit: 0, native_call_depth: 0, + inline_args_base: 0, + inline_args_count: 0, accessor_frame_target_reg: None, inline_callee: None, gc_state: GcState { diff --git a/crates/oxide_vm/tests/arguments_tests.rs b/crates/oxide_vm/tests/arguments_tests.rs new file mode 100644 index 00000000..bfbc893b --- /dev/null +++ b/crates/oxide_vm/tests/arguments_tests.rs @@ -0,0 +1,112 @@ +//! arguments 对象执行语义测试:实参个数/索引读取、箭头函数词法继承、 +//! 默认参数引用、方法调用、闭包捕获、显式声明屏蔽、嵌套函数调用帧边界。 + +use oxide_compiler::compiler::Compiler; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(vm: &mut Vm, source: &str) -> Result { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?; + let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?; + vm.run(&module) +} + +#[test] +fn arguments_length_counts_actual_args() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "function f(){return arguments.length}; f(1,2,3)").unwrap(); + assert_eq!(result.as_int(), 3); +} + +#[test] +fn arguments_index_reads_first_arg() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "function f(a){return arguments[0]}; f(42)").unwrap(); + assert_eq!(result.as_int(), 42); +} + +#[test] +fn arguments_index_reads_extra_arg() { + // 实参多于形参:多余实参仍可从 arguments 读取。 + let mut vm = Vm::new(); + let result = eval(&mut vm, "function f(a){return arguments[1]}; f(1, 99)").unwrap(); + assert_eq!(result.as_int(), 99); +} + +#[test] +fn arrow_inherits_outer_arguments() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "function f(){return (()=>arguments.length)()}; f(1,2)").unwrap(); + assert_eq!(result.as_int(), 2); +} + +#[test] +fn default_param_can_read_arguments() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "function f(a=arguments.length){return a}; f()").unwrap(); + assert_eq!(result.as_int(), 0); +} + +#[test] +fn method_call_arguments() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "var o={m:function(){return arguments.length}}; o.m(5,6)").unwrap(); + assert_eq!(result.as_int(), 2); +} + +#[test] +fn closure_captures_arguments() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "function f(){var g=()=>arguments[0]; return g()}; f('x')").unwrap(); + assert_eq!(vm.lookup_str(result).as_deref(), Some("x")); +} + +#[test] +fn arguments_not_created_when_param_named_arguments() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "function f(arguments){return arguments}; f(7)").unwrap(); + assert_eq!(result.as_int(), 7); +} + +#[test] +fn arguments_not_created_when_var_shadowed() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "function f(){var arguments=9; return arguments}; f(1)").unwrap(); + assert_eq!(result.as_int(), 9); +} + +#[test] +fn arguments_object_has_length_and_callee() { + let mut vm = Vm::new(); + let result = + eval(&mut vm, "function f(){return arguments.callee === f && arguments.length === 2}; f(1,2)").unwrap(); + assert_eq!(result, JsValue::bool(true)); +} + +#[test] +fn arguments_frame_boundary_across_nested_calls() { + // 子调用返回后父函数的 arguments 实参区仍完好(帧恢复截断不越界)。 + let mut vm = Vm::new(); + let result = eval(&mut vm, "function g(){return 1} function f(){return arguments.length + g()}; f(4,5,6)").unwrap(); + assert!((result.as_double() - 4.0).abs() < 0.0001); +} + +#[test] +fn nested_plain_function_has_own_arguments() { + // 非箭头嵌套函数创建自己的 arguments(空实参),不继承外层。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "function f(){function g(){return arguments.length} return g() === 0 && arguments.length === 1}; f(8)", + ) + .unwrap(); + assert_eq!(result, JsValue::bool(true)); +} + +#[test] +fn new_expression_target_gets_arguments() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "function Ctor(){this.n=arguments.length} var c=new Ctor(1,2,3); c.n").unwrap(); + assert_eq!(result.as_int(), 3); +}