From e03ec0f635dc753e521e59793e0cf45385a3d1db Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 10 Aug 2026 03:52:17 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=84=9A=E6=9C=AC=E9=A1=B6=E5=B1=82=20v?= =?UTF-8?q?ar/function=20=E5=A3=B0=E6=98=8E=E8=90=BD=20globalThis=EF=BC=88?= =?UTF-8?q?DEFINE=5FGLOBAL=5FPROP=20=E9=9D=9E=E5=8F=AF=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=B1=9E=E6=80=A7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_bytecode/src/opcode.rs | 5 +- crates/oxide_emit/src/emit.rs | 22 ++++ crates/oxide_emit/src/shared/binding.rs | 16 ++- .../src/stmt/declaration/function_decl.rs | 4 + crates/oxide_emit/src/stmt/declaration/var.rs | 4 + crates/oxide_ir/src/contract.rs | 12 +-- crates/oxide_ir/src/inst.rs | 6 ++ crates/oxide_test262/src/main.rs | 7 +- crates/oxide_vm/src/vm.rs | 4 + crates/oxide_vm/src/vm_dispatch_ctrl.rs | 20 ++++ .../tests/global_var_binding_tests.rs | 100 ++++++++++++++++++ 11 files changed, 187 insertions(+), 13 deletions(-) create mode 100644 crates/oxide_vm/tests/global_var_binding_tests.rs diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index 5af70f6e..f20ffe8f 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -175,8 +175,11 @@ define_opcodes! { // rest 参数数组:rd=目标寄存器,b=固定形参数(rest 之前的形参个数)。 CREATE_REST_ARRAY = 0x6E => "CREATE_REST_ARRAY", - // ── define 语义属性写入 (0x6D) ── + // ── define 语义属性写入 (0x6D, 0x6F) ── DEFINE_PROP = 0x6D => "DEFINE_PROP", + // 全局 var 绑定属性定义:rd=目标对象、a=值、b=键,数据属性可写/可枚举/不可配置 + // (脚本顶层 var/function 声明同步到 globalThis 用,属性描述符与规范一致)。 + DEFINE_GLOBAL_PROP = 0x6F => "DEFINE_GLOBAL_PROP", // ── 成员复合赋值:位/移位 (0x64-0x69) ── COMPOUND_MEMBER_BIT_AND = 0x64 => "COMPOUND_MEMBER_BIT_AND", diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 8bbffe5f..711f7685 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -166,6 +166,9 @@ pub struct CompileCtx { pub(crate) is_generator: bool, /// 本函数是否为异步函数体(`async function` / async 箭头),`assemble_ir` 回写到 IR。 pub(crate) is_async: bool, + /// 是否为脚本顶层模块(emit_program 的根上下文)。顶层 var/function 声明需 + /// 同步写全局对象属性(脚本环境记录的 var 可经 globalThis 反射);函数体为 false。 + pub(crate) is_global_scope: bool, pub(crate) static_block_this_reg: Option, pub(crate) field_buffer: Option, /// 类构造器模块中 `@@field_keys` upvalue 下标(实例字段 computed key 数组)。 @@ -274,6 +277,7 @@ impl CompileCtx { in_static_method: false, is_generator: false, is_async: false, + is_global_scope: false, static_block_this_reg: None, field_buffer: None, field_keys_uv: None, @@ -1245,12 +1249,30 @@ impl Emitter { } } + /// 把脚本顶层 var/function 绑定的当前值同步写入全局对象属性,使顶层声明 + /// 可经 `globalThis` 反射(脚本环境记录的 var 绑定全局对象属性)。 + /// + /// # 边界与前提 + /// - 仅顶层模块上下文调用:顶层 `this`(物理寄存器 254)恒为全局对象。 + /// - let/const/class 不落全局对象,不得调用本函数。 + /// + /// # 副作用 + /// - 定义全局对象数据属性(可写/可枚举/不可配置),属性缺失时新建。 + pub(crate) fn emit_global_prop_write(&self, name: &str, val_reg: u32, ctx: &mut CompileCtx) { + let idx = ctx.add_constant(Constant::String(name.to_string())); + let key_reg = ctx.alloc_reg(); + ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx)); + ctx.inst(Inst::define_global_prop(Operand::This, Operand::Reg(val_reg), Operand::Reg(key_reg))); + } + /// 把完整程序编译为顶层模块体的 IRFunction。 /// /// 调用方为 `oxide_compiler::Compiler::compile`:本函数完成 emit 半程, /// 随后由 `oxide_ir::lower::lower` 降为字节码。 pub fn emit_program(&self, program: &oxide_parser::Program) -> Result { let mut ctx = CompileCtx::new(); + // 脚本顶层:var/function 声明需落到全局对象,let/const/class 不进全局。 + ctx.is_global_scope = true; ctx.pre_register_builtins(); self.predeclare_function_declarations(&program.body, &mut ctx); diff --git a/crates/oxide_emit/src/shared/binding.rs b/crates/oxide_emit/src/shared/binding.rs index ce6a3498..384ac8d0 100644 --- a/crates/oxide_emit/src/shared/binding.rs +++ b/crates/oxide_emit/src/shared/binding.rs @@ -74,6 +74,7 @@ impl Emitter { )); } else if !ctx.with_stack.is_empty() && !ctx.is_with_internal_binding(name) { // with 内 var 初始化:对象有该属性则写对象,否则写提升槽(动态解析)。 + // ponytail: with 内顶层 var 不写全局对象属性,with 语句本身已是稀见用例。 self.emit_with_dynamic_write(name, src_reg, 0, ctx); } else { // const 声明路径 STORE_VAR 恒 b=0,不查运行时 guard。 @@ -88,6 +89,10 @@ impl Emitter { )); } ctx.init_var(name); + // 脚本顶层 var:同步写全局对象,使 globalThis.x 反射声明值。 + if ctx.is_global_scope && matches!(kind, VariableDeclarationKind::Var) { + self.emit_global_prop_write(name, src_reg, ctx); + } Ok(()) } @@ -240,9 +245,7 @@ impl Emitter { } /// 向 excluded 数组下标 `idx` 写入一个键:`key_val` 为运行时寄存器值,否则用 `key_str` 常量。 - fn emit_push_excluded_key( - &self, arr: u32, key_val: Option, key_str: &str, idx: usize, ctx: &mut CompileCtx, - ) { + fn emit_push_excluded_key(&self, arr: u32, key_val: Option, key_str: &str, idx: usize, ctx: &mut CompileCtx) { let val_reg = match key_val { Some(kv) => kv, None => { @@ -255,7 +258,12 @@ impl Emitter { let elem_idx = ctx.alloc_reg(); let cidx = ctx.add_constant(Constant::Int(idx as i32)); ctx.inst(Inst::load_const(Operand::Reg(elem_idx), cidx)); - ctx.inst(Inst::new(OpCode::SET_ELEM, Operand::Reg(arr), Operand::Reg(elem_idx), Operand::Reg(val_reg))); + ctx.inst(Inst::new( + OpCode::SET_ELEM, + Operand::Reg(arr), + Operand::Reg(elem_idx), + Operand::Reg(val_reg), + )); } fn emit_assignment_maybe_default( diff --git a/crates/oxide_emit/src/stmt/declaration/function_decl.rs b/crates/oxide_emit/src/stmt/declaration/function_decl.rs index 734b4db0..052ab244 100644 --- a/crates/oxide_emit/src/stmt/declaration/function_decl.rs +++ b/crates/oxide_emit/src/stmt/declaration/function_decl.rs @@ -60,6 +60,10 @@ impl Emitter { } else { ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(var_reg), Operand::None)); } + // 脚本顶层函数声明:同步写全局对象,使 globalThis 可反射函数名。 + if ctx.is_global_scope { + self.emit_global_prop_write(&name, var_reg, ctx); + } Ok(None) } } diff --git a/crates/oxide_emit/src/stmt/declaration/var.rs b/crates/oxide_emit/src/stmt/declaration/var.rs index 3b6a5c85..ffdb6bfb 100644 --- a/crates/oxide_emit/src/stmt/declaration/var.rs +++ b/crates/oxide_emit/src/stmt/declaration/var.rs @@ -61,6 +61,10 @@ impl Emitter { ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(target_reg), Operand::Reg(tmp), Operand::None)); } ctx.init_var(bi.name.as_str()); + // 脚本顶层 var 无初始化:仍须写全局对象属性(值为 undefined)。 + if ctx.is_global_scope && matches!(decl.kind, VariableDeclarationKind::Var) { + self.emit_global_prop_write(bi.name.as_str(), tmp, ctx); + } r = Some(var_reg); } } diff --git a/crates/oxide_ir/src/contract.rs b/crates/oxide_ir/src/contract.rs index 056e4e06..2b77a76d 100644 --- a/crates/oxide_ir/src/contract.rs +++ b/crates/oxide_ir/src/contract.rs @@ -66,6 +66,7 @@ impl Inst { | OpCode::DELETE_PROP_DYNAMIC | OpCode::DEFINE_ACCESSOR | OpCode::DEFINE_PROP + | OpCode::DEFINE_GLOBAL_PROP | OpCode::SET_HOME_OBJECT // SPILL 写 VM spill 栈而非寄存器(恢复由 UNSPILL 写 rd) | OpCode::SPILL @@ -231,11 +232,9 @@ impl Inst { // HALT:隐式读 reg 0(顶层返回值) OpCode::HALT => uses.push(0), // RETURN/THROW/YIELD/YIELD_STAR/AWAIT:读 rd(None→0) - OpCode::RETURN - | OpCode::THROW - | OpCode::YIELD - | OpCode::YIELD_STAR - | OpCode::AWAIT => push_operand(&mut uses, &self.rd), + OpCode::RETURN | OpCode::THROW | OpCode::YIELD | OpCode::YIELD_STAR | OpCode::AWAIT => { + push_operand(&mut uses, &self.rd) + } // 加载族:a 槽是 Const/Imm 立即数,无寄存器 use OpCode::LOAD_CONST | OpCode::CREATE_CLOSURE @@ -264,7 +263,8 @@ impl Inst { | OpCode::SET_ELEM | OpCode::INIT_PRIVATE | OpCode::DEFINE_ACCESSOR - | OpCode::DEFINE_PROP => { + | OpCode::DEFINE_PROP + | OpCode::DEFINE_GLOBAL_PROP => { push_operand(&mut uses, &self.rd); push_operand(&mut uses, &self.a); push_operand(&mut uses, &self.b); diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index 065bd6bf..a775df75 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -197,6 +197,12 @@ impl Inst { Self::new(OpCode::DEFINE_PROP, target, value, key) } + /// 定义全局 var 绑定数据属性:target 为全局对象,value/key 为寄存器。 + /// 属性可写/可枚举/不可配置(脚本顶层 var/function 声明的属性描述符)。 + pub fn define_global_prop(target: Operand, value: Operand, key: Operand) -> Self { + Self::new(OpCode::DEFINE_GLOBAL_PROP, target, value, key) + } + /// 静态删除属性:obj 同时放 rd/a 槽,const_idx 为属性名常量下标。 pub fn delete_prop_static(obj: Operand, const_idx: u32) -> Self { Self::with_ext(OpCode::DELETE_PROP_STATIC, obj, obj, Operand::None, &[const_idx]) diff --git a/crates/oxide_test262/src/main.rs b/crates/oxide_test262/src/main.rs index 38280aec..227ddca7 100644 --- a/crates/oxide_test262/src/main.rs +++ b/crates/oxide_test262/src/main.rs @@ -458,7 +458,7 @@ fn run_test_inner( if is_async { // 异步测试:注入 print 捕获 + $DONE(asyncTests 用 $DONE 报告结果)。 // 结果写入 globalThis 全局字符串,run() 结束后由 runner 读取并按 marker 判定。 - // (顶层 var 不落 global 对象,须显式写 globalThis。) + // print 为顶层函数声明,经全局对象反射落 globalThis;此处仅初始化累加器。 append_source_chunk( &mut code, "async capture", @@ -578,7 +578,10 @@ fn judge_async_result( return TestResult::fail( path.to_path_buf(), dur, - format!("expected runtime error ({}), got: async complete", meta.negative.as_ref().unwrap().error_type), + format!( + "expected runtime error ({}), got: async complete", + meta.negative.as_ref().unwrap().error_type + ), ); } return TestResult::pass(path.to_path_buf(), dur, "async ok"); diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index ed9ae0df..b90b4c42 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1318,6 +1318,10 @@ impl Vm { self.dispatch_define_prop(rd, a, b)?; } + OpCode::DEFINE_GLOBAL_PROP => { + self.dispatch_define_global_prop(rd, a, b)?; + } + OpCode::RETURN => match self.dispatch_return(rd) { Ok(Some(result)) => return Ok(result), Ok(None) => {} diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index 1830fb65..2175dfbd 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -178,6 +178,26 @@ impl Vm { } } + /// 定义全局 var 绑定数据属性:rd=全局对象,a=值,b=键。 + /// 属性可写/可枚举/不可配置(CreateGlobalVarBinding 的属性描述符)。 + /// + /// # 边界与前提 + /// - 已有同名不可写属性(NaN/undefined/Infinity 等内置)时静默跳过:规范中 + /// var 绑定仍建立(寄存器侧),全局对象属性保持原样,不抛错。 + /// - 已有同名可写数据属性(重复 var 声明)时按定义更新值。 + pub(crate) fn dispatch_define_global_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { + vm_trace!("DEFINE_GLOBAL_PROP rd={} value={} key={}", rd, a, b); + let obj_val = self.regs[rd]; + if !obj_val.is_object() { + return Ok(()); + } + let prop_name_si = self.property_key_si(self.regs[b]); + let value = self.regs[a]; + let obj = unsafe { &mut *obj_val.as_js_object_ptr() }; + let _ = self.define_data_property(obj, prop_name_si, value, PropAttributes::new(true, true, false)); + Ok(()) + } + /// break 完成:`crossed`(rd 槽)为 emit 词法算出的逃出 finally 域数。 /// 逐个穿越 finally 后跳转到目标;crossed 为 0 时直接跳转。 pub(crate) fn dispatch_break(&mut self, instr: u32) { diff --git a/crates/oxide_vm/tests/global_var_binding_tests.rs b/crates/oxide_vm/tests/global_var_binding_tests.rs new file mode 100644 index 00000000..8d822d34 --- /dev/null +++ b/crates/oxide_vm/tests/global_var_binding_tests.rs @@ -0,0 +1,100 @@ +//! 脚本顶层 var/function 声明落 globalThis 的运行时行为测试。 + +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval_truthy(source: &str) { + let allocator = Allocator::default(); + let program = oxide_parser::parse(&allocator, source).expect("parse"); + let module = Compiler::new().compile(&program).expect("compile"); + let mut vm = Vm::new(); + let result = vm.run(&module).expect("run"); + assert!(result.is_bool() && result.as_bool(), "expected true, got: {result:?}\nsource: {source}"); +} + +fn eval_string(source: &str) -> String { + let allocator = Allocator::default(); + let program = oxide_parser::parse(&allocator, source).expect("parse"); + let module = Compiler::new().compile(&program).expect("compile"); + let mut vm = Vm::new(); + let result = vm.run(&module).expect("run"); + vm.lookup_str(result).unwrap_or_default() +} + +#[test] +fn top_level_var_lands_on_global_this() { + eval_truthy("var x = 5; globalThis.x === 5"); + eval_truthy("var y = 10; typeof globalThis.y === 'number'"); +} + +#[test] +fn top_level_function_lands_on_global_this() { + eval_truthy("function foo(){ return 1; } globalThis.foo() === 1"); + eval_truthy("function foo(){ return 1; } typeof globalThis.foo === 'function'"); +} + +#[test] +fn let_const_class_do_not_land_on_global_this() { + eval_truthy("let z = 5; typeof globalThis.z === 'undefined'"); + eval_truthy("const c = 1; typeof globalThis.c === 'undefined'"); + eval_truthy("class C {} typeof globalThis.C === 'undefined'"); +} + +#[test] +fn function_local_var_does_not_land_on_global_this() { + eval_truthy("function f(){ var v = 1; } f(); typeof globalThis.v === 'undefined'"); +} + +#[test] +fn block_level_var_lands_on_global_this() { + eval_truthy("{ var b = 2; } globalThis.b === 2"); +} + +#[test] +fn redeclaration_updates_global_property() { + eval_truthy("var x = 1; var x = 2; globalThis.x === 2"); +} + +#[test] +fn var_global_property_is_non_configurable() { + eval_truthy("var x = 1; Object.getOwnPropertyDescriptor(globalThis, 'x').configurable === false"); + eval_truthy("var x = 1; Object.getOwnPropertyDescriptor(globalThis, 'x').enumerable === true"); + eval_truthy("var x = 1; delete globalThis.x === false"); + eval_truthy("var x = 1; delete globalThis.x; typeof globalThis.x === 'number'"); +} + +#[test] +fn var_name_shows_in_get_own_property_names() { + eval_truthy("var x = 1; Object.getOwnPropertyNames(globalThis).indexOf('x') >= 0"); + eval_truthy("function foo(){} Object.getOwnPropertyNames(globalThis).indexOf('foo') >= 0"); +} + +#[test] +fn function_declared_var_lands_after_user_assignment() { + eval_truthy("globalThis.x = 99; var x = 5; globalThis.x === 5"); +} + +#[test] +fn async_harness_done_pattern() { + // asyncHelpers.js 依赖顶层 function $DONE 落 globalThis。 + eval_truthy("function $DONE(){} Object.prototype.hasOwnProperty.call(globalThis, '$DONE')"); + eval_truthy("function $DONE(){} typeof globalThis.$DONE === 'function'"); +} + +#[test] +fn var_without_initializer_creates_undefined_property() { + eval_truthy("var x; typeof globalThis.x === 'undefined'"); + eval_truthy("var x; Object.prototype.hasOwnProperty.call(globalThis, 'x')"); +} + +#[test] +fn function_internal_reference_still_works() { + eval_truthy("function foo(){ return 1; } var r = foo(); r === 1"); +} + +#[test] +fn descriptor_attributes_match_spec() { + let d = eval_string("var x = 1; JSON.stringify(Object.getOwnPropertyDescriptor(globalThis, 'x'))"); + assert_eq!(d, r#"{"value":1,"writable":true,"enumerable":true,"configurable":false}"#); +}