mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 脚本顶层 var/function 声明落 globalThis(DEFINE_GLOBAL_PROP 非可配置数据属性)
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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<u8>,
|
||||
pub(crate) field_buffer: Option<FieldBuffer>,
|
||||
/// 类构造器模块中 `@@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<IRFunction, String> {
|
||||
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);
|
||||
|
||||
|
||||
@@ -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<u32>, key_str: &str, idx: usize, ctx: &mut CompileCtx,
|
||||
) {
|
||||
fn emit_push_excluded_key(&self, arr: u32, key_val: Option<u32>, 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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) => {}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}"#);
|
||||
}
|
||||
Reference in New Issue
Block a user