mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 11:11:11 +08:00
fix: 脚本顶层 var/function 声明落 globalThis(DEFINE_GLOBAL_PROP 非可配置数据属性)
This commit is contained in:
@@ -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