mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 03:01:12 +08:00
fix: 全局三常量描述符翻可配置,var 重复声明以 builtin 镜像值保留现存值
This commit is contained in:
@@ -275,12 +275,16 @@ impl Vm {
|
||||
}
|
||||
|
||||
/// 定义全局 var 绑定数据属性:rd=全局对象,a=值,b=键。
|
||||
/// 属性可写/可枚举/不可配置(CreateGlobalVarBinding 的属性描述符)。
|
||||
///
|
||||
/// 描述符语义(CreateGlobalVarBinding):
|
||||
/// - 已有可配置属性:保持原 enumerable/configurable、置 writable、更新值——
|
||||
/// 重复声明不漂移既有描述符(内置属性 enumerable 不泄漏、值语义不变)。
|
||||
/// - 已有不可配置属性:走定义不变量检查(末尾统一路径)——描述符冲突或
|
||||
/// 不可写属性值变更时静默 no-op,否则仅更新值、描述符不变。
|
||||
/// - 属性缺失:新建可写/可枚举/不可配置数据属性。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 已有同名不可写属性(NaN/undefined/Infinity 等内置)时静默跳过:规范中
|
||||
/// var 绑定仍建立(寄存器侧),全局对象属性保持原样,不抛错。
|
||||
/// - 已有同名可写数据属性(重复 var 声明)时按定义更新值。
|
||||
/// - rd 非对象(非对象 this)时按 no-op 处理,不抛错。
|
||||
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];
|
||||
@@ -290,6 +294,20 @@ impl Vm {
|
||||
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() };
|
||||
// 已有可配置属性:var 绑定描述符不漂移——保 enumerable/configurable,
|
||||
// 置 writable,更新值(三常量与内置对象声明撞名时值语义按规范落槽)。
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
if let Some(current) = obj.prop_meta_at(pos) {
|
||||
if current.attributes.configurable() {
|
||||
let attrs =
|
||||
PropAttributes::new(true, current.attributes.enumerable(), current.attributes.configurable());
|
||||
let _ = self.define_data_property(obj, prop_name_si, value, attrs);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 属性缺失或不可配置既有属性:统一走定义,不可配置时由定义侧不变量
|
||||
// 检查决定更新值或静默 no-op。
|
||||
let _ = self.define_data_property(obj, prop_name_si, value, PropAttributes::new(true, true, false));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
//! 全局三常量(undefined/NaN/Infinity)描述符与 var 声明交互的回归测试。
|
||||
//!
|
||||
//! 覆盖规范全局对象属性描述符 { writable:false, enumerable:false, configurable:true }:
|
||||
//! 描述符读、delete 真删、delete 后镜像裸读、var 无初始化声明的描述符翻转与值
|
||||
//! 保持(NaN/Infinity 不被抹成 undefined)、var 初始化声明的值落槽、枚举面不
|
||||
//! 泄漏、任务只读拦截与 strict TypeError、eval 路径、for-in 枚举面。
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn compile(source: &str) -> oxide_bytecode::module::CompiledModule {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse");
|
||||
Compiler::new().compile(&program).expect("compile")
|
||||
}
|
||||
|
||||
fn run_truthy(source: &str) {
|
||||
let mut vm = Vm::new();
|
||||
let result = vm.run(&compile(source)).expect("run");
|
||||
assert!(result.is_bool() && result.as_bool(), "expected true, got: {result:?}\nsource: {source}");
|
||||
}
|
||||
|
||||
fn run_string(source: &str) -> String {
|
||||
let mut vm = Vm::new();
|
||||
let result = vm.run(&compile(source)).expect("run");
|
||||
if result.is_undefined() {
|
||||
return "undefined".to_string();
|
||||
}
|
||||
vm.lookup_str(result).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// 求值并返回完成值字符串;运行期异常返回错误文本(供 TypeError 断言)。
|
||||
fn eval(source: &str) -> String {
|
||||
let mut vm = Vm::new();
|
||||
match vm.run(&compile(source)) {
|
||||
Ok(result) => vm.lookup_str(result).unwrap_or_default(),
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
const CONSTANTS: [&str; 3] = ["undefined", "NaN", "Infinity"];
|
||||
|
||||
#[test]
|
||||
fn three_constants_descriptor_matches_spec() {
|
||||
// #1:描述符 { writable:false, enumerable:false, configurable:true } ×3 常量。
|
||||
for name in CONSTANTS {
|
||||
let src = format!(
|
||||
"var d = Object.getOwnPropertyDescriptor(globalThis, '{name}'); \
|
||||
d.writable === false && d.enumerable === false && d.configurable === true"
|
||||
);
|
||||
run_truthy(&src);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_three_constants_removes_property() {
|
||||
// #2:configurable → delete 返 true 且属性真删(描述符读为 undefined)×3 常量。
|
||||
for name in CONSTANTS {
|
||||
let src = format!(
|
||||
"delete globalThis.{name} === true \
|
||||
&& Object.getOwnPropertyDescriptor(globalThis, '{name}') === undefined"
|
||||
);
|
||||
run_truthy(&src);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_then_bare_read_via_mirror() {
|
||||
// #3:删后裸读走镜像预载(入口值),无 ReferenceError。
|
||||
assert_eq!(run_string("delete globalThis.undefined; typeof undefined"), "undefined");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_no_init_flips_writable_keeps_value() {
|
||||
// #4:var 无初始化声明——CreateGlobalVarBinding 翻 writable、保 enumerable/
|
||||
// configurable,值保持(三常量现值 undefined,幂等)。
|
||||
run_truthy(
|
||||
"var undefined; var d = Object.getOwnPropertyDescriptor(globalThis, 'undefined'); \
|
||||
d.value === undefined && d.writable === true && d.enumerable === false \
|
||||
&& d.configurable === true",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_no_init_preserves_builtin_values() {
|
||||
// #5:var 声明不得把现存值抹成 undefined(clobber 回归点)×NaN/Infinity。
|
||||
run_truthy("var NaN; Number.isNaN(globalThis.NaN) && globalThis.NaN !== undefined");
|
||||
run_truthy("var Infinity; globalThis.Infinity === Infinity && globalThis.Infinity !== undefined");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_no_init_builtin_not_enumerable() {
|
||||
// #6:描述符保 enumerable:false——不泄漏进 Object.keys(回归点)。
|
||||
run_truthy("var NaN; Object.keys(globalThis).includes('NaN') === false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_with_init_readonly_intercept_unchanged() {
|
||||
// #7:var undefined = 3——只读拦截不变(sloppy 静默 no-op,读仍 undefined)。
|
||||
assert_eq!(run_string("var undefined = 3; undefined"), "undefined");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_var_readonly_builtin_type_error() {
|
||||
// #8:strict 下 var undefined = 3 抛 TypeError(拦截路径不受描述符翻动影响)。
|
||||
let result = eval("'use strict'; var undefined = 3;");
|
||||
assert!(result.contains("TypeError"), "expected TypeError, got: {result}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_builtin_object_descriptor_kept() {
|
||||
// #9:var Math 无初始化——值与描述符均不漂移(Math 对象保留,e/c 不变)。
|
||||
run_truthy(
|
||||
"var Math; var d = Object.getOwnPropertyDescriptor(globalThis, 'Math'); \
|
||||
d.value === Math && d.writable === true && d.enumerable === false \
|
||||
&& d.configurable === true",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_builtin_object_with_init() {
|
||||
// #10:var Math = 6——值落槽、描述符保 e:false/c:true、不泄漏进枚举。
|
||||
run_truthy(
|
||||
"var Math = 6; var d = Object.getOwnPropertyDescriptor(globalThis, 'Math'); \
|
||||
d.value === 6 && d.writable === true && d.enumerable === false \
|
||||
&& d.configurable === true && Object.keys(globalThis).includes('Math') === false",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_var_readonly_builtin() {
|
||||
// #11:eval var 路径(0x98 判 writable 而非 configurable)——sloppy no-op、
|
||||
// strict(eval 脚本内)TypeError,描述符不变。
|
||||
assert_eq!(run_string("eval('var undefined = 3'); undefined"), "undefined");
|
||||
let strict = eval("eval('\"use strict\"; var undefined = 3')");
|
||||
assert!(strict.contains("TypeError"), "expected TypeError, got: {strict}");
|
||||
run_truthy(
|
||||
"eval('var undefined = 3'); \
|
||||
var d = Object.getOwnPropertyDescriptor(globalThis, 'undefined'); \
|
||||
d.writable === false && d.configurable === true",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_new_name_still_configurable() {
|
||||
// #12:eval var 新名属性 configurable:true(0x98 新建分支不变)。
|
||||
run_truthy(
|
||||
"eval('var __g78x = 1'); \
|
||||
Object.getOwnPropertyDescriptor(globalThis, '__g78x').configurable === true",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_does_not_enumerate_three_constants() {
|
||||
// #13:for-in 仅 enumerable 面——三常量 e:false 不泄漏(不变)。
|
||||
run_truthy(
|
||||
"var ks = []; for (var k in globalThis) ks.push(k); \
|
||||
ks.includes('undefined') || ks.includes('NaN') || ks.includes('Infinity') === false",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn control_shapes_unchanged() {
|
||||
// #14:对照形——delete Math(基线即 c:true)与 typeof NaN 不变。
|
||||
run_truthy("delete globalThis.Math === true");
|
||||
assert_eq!(run_string("typeof NaN"), "number");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_declaration_builtin_name() {
|
||||
// 边界:function Math(){}——顶层函数声明取函数值(规范 var 绑定语义),
|
||||
// 描述符保 e:false/c:true、值可调用。
|
||||
run_truthy(
|
||||
"function Math(){ return 1; } \
|
||||
var d = Object.getOwnPropertyDescriptor(globalThis, 'Math'); \
|
||||
Math() === 1 && d.value === Math && d.writable === true \
|
||||
&& d.enumerable === false && d.configurable === true",
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
//! 内置全局属性描述符测试:规范要求全局构造器/命名空间对象属性
|
||||
//! enumerable=false(不泄漏进 Object.keys(globalThis) / for-in),
|
||||
//! 全局 NaN/undefined/Infinity 三常量不可写不可枚举不可配置。
|
||||
//! 全局 NaN/undefined/Infinity 三常量不可写不可枚举可配置。
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
@@ -61,11 +61,11 @@ fn for_in_global_this_excludes_builtins() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nan_undefined_infinity_are_non_writable_non_configurable() {
|
||||
fn nan_undefined_infinity_are_non_writable_configurable() {
|
||||
for name in ["NaN", "undefined", "Infinity"] {
|
||||
let src = format!(
|
||||
"var d = Object.getOwnPropertyDescriptor(globalThis, '{name}'); \
|
||||
d.writable === false && d.enumerable === false && d.configurable === false"
|
||||
d.writable === false && d.enumerable === false && d.configurable === true"
|
||||
);
|
||||
eval_truthy(&src);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user