feat(vm): arguments 对象绑定(CREATE_ARGUMENTS + 帧实参 spill 区)
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -238,7 +238,7 @@ impl Vm {
|
||||
|
||||
/// 当前执行函数的闭包对象:普通路径取 frames 栈顶帧,inline(sync 回调)路径
|
||||
/// frames 被隔离为空,取 `inline_callee`。
|
||||
fn current_callee(&self) -> Option<JsValue> {
|
||||
pub(crate) fn current_callee(&self) -> Option<JsValue> {
|
||||
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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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<JsValue>,
|
||||
pub(crate) cell_stack: Vec<Vec<*mut Cell>>,
|
||||
pub(crate) inline_callee: Option<JsValue>,
|
||||
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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<JsValue, String> {
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user