diff --git a/crates/oxide_emit/src/prepass/declare.rs b/crates/oxide_emit/src/prepass/declare.rs index 00b8aac8..e5b0736a 100644 --- a/crates/oxide_emit/src/prepass/declare.rs +++ b/crates/oxide_emit/src/prepass/declare.rs @@ -1,8 +1,6 @@ //! emit 前置 pass,声明预登记:函数/var 声明提升预声明、块级函数声明按 ES2015 -//! 块绑定预声明、let/const/class 建 TDZ 占位;顶层 lexical 声明撞受限全局名 -//! 在声明实例化期拒绝(global_lexical 门控,门控源居 emit_program 调用点)。 +//! 块绑定预声明、let/const/class 建 TDZ 占位。 -use super::check_restricted_global_lexical; use crate::{CompileCtx, Emitter}; use oxide_parser::{BindingPattern, Declaration, ExportDefaultDeclarationKind, Statement, VariableDeclarationKind}; @@ -294,10 +292,9 @@ impl Emitter { /// 属 Declaration、非 Statement 子产生式,parser 按语法错误直接拒绝,这些 /// 形状不会进入 emit)。 /// - 跳过 for 头声明(循环作用域由 for 分支内联 declare)。 - /// - `global_lexical` 为 true 时(仅脚本顶层调用点传 `!is_eval_script`): - /// lexical 声明撞受限全局名报 SyntaxError(脚本声明实例化对全局对象受限 - /// 自有属性名做检查,eval 代码声明实例化无此检查);函数体/块/switch case/ - /// try/模块调用点传 false,lexical 声明是局部绑定不查。 + /// - `global_lexical` 仅脚本顶层调用点传 `!is_eval_script`,其余调用点传 + /// false;当前只透传不消费(死参数,待后续清理),顶层与局部的 lexical + /// 声明均按合法遮蔽处理。 pub(crate) fn predeclare_lexical_declarations( &self, statements: &[Statement], ctx: &mut CompileCtx, global_lexical: bool, ) -> Result<(), String> { @@ -314,7 +311,6 @@ impl Emitter { } Statement::ClassDeclaration(cd) => { if let Some(id) = &cd.id { - check_restricted_global_lexical(id.name.as_str(), global_lexical)?; let reg = ctx.alloc_reg(); let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Let, false); } @@ -333,7 +329,6 @@ impl Emitter { } Declaration::ClassDeclaration(cd) => { if let Some(id) = &cd.id { - check_restricted_global_lexical(id.name.as_str(), global_lexical)?; let reg = ctx.alloc_reg(); let _ = ctx.declare_predeclared( id.name.as_str(), @@ -352,7 +347,6 @@ impl Emitter { // 此处只为具名 default 类建 lexical Let TDZ 占位。 if let ExportDefaultDeclarationKind::ClassDeclaration(cd) = &exp.declaration { if let Some(id) = &cd.id { - check_restricted_global_lexical(id.name.as_str(), global_lexical)?; let reg = ctx.alloc_reg(); let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Let, false); } @@ -365,12 +359,14 @@ impl Emitter { } /// 递归预声明绑定 pattern 内的全部标识符(含数组/对象/默认值解构)。 + /// `global_lexical` 当前只透传不消费(死参数,待后续清理),递归调用 + /// 必须原样下传,故仅递归使用。 + #[allow(clippy::only_used_in_recursion)] fn predeclare_lexical_pattern( &self, pattern: &BindingPattern, is_const: bool, ctx: &mut CompileCtx, global_lexical: bool, ) -> Result<(), String> { match pattern { BindingPattern::BindingIdentifier(bi) => { - check_restricted_global_lexical(bi.name.as_str(), global_lexical)?; let reg = ctx.alloc_reg(); let _ = ctx.declare_predeclared( bi.name.as_str(), diff --git a/crates/oxide_emit/src/prepass/mod.rs b/crates/oxide_emit/src/prepass/mod.rs index 75bb95f9..ea1ae52c 100644 --- a/crates/oxide_emit/src/prepass/mod.rs +++ b/crates/oxide_emit/src/prepass/mod.rs @@ -7,23 +7,3 @@ mod block_fn; mod builtin_scan; mod declare; - -/// 脚本顶层词法声明(`let`/`const`/`class`)禁止使用的受限全局名:`undefined`、 -/// `NaN`、`Infinity` 三名静态集。 -/// -/// 判定标准是全局对象上 `configurable:false` 的自有属性名:此类属性无法由声明 -/// 实例化建立遮蔽绑定,现行规范下恰为三常量;`eval` 与其余内置属性皆可配置, -/// 可合法遮蔽。运行时无法查询全局对象描述符,名单必须编译期确定。 -/// -/// 与 `BUILTIN_GLOBALS` 语义不同:后者是写入拦截与双写名单,两份名单互不派生, -/// 交叠名由 `restricted_lexical_names_within_builtin_globals` 单测断言恒同步。 -pub(crate) const RESTRICTED_GLOBAL_LEXICAL_NAMES: &[&str] = &["undefined", "NaN", "Infinity"]; - -/// 脚本顶层 lexical 声明撞受限全局名 → SyntaxError(声明实例化期拒绝,整程序 -/// 编译失败)。错误消息与既有重复声明错同形;非顶层或名不在受限集 → Ok。 -fn check_restricted_global_lexical(name: &str, global_lexical: bool) -> Result<(), String> { - if global_lexical && RESTRICTED_GLOBAL_LEXICAL_NAMES.contains(&name) { - return Err(format!("Identifier '{name}' has already been declared")); - } - Ok(()) -} diff --git a/crates/oxide_emit/src/program.rs b/crates/oxide_emit/src/program.rs index 7d963ee5..44129efb 100644 --- a/crates/oxide_emit/src/program.rs +++ b/crates/oxide_emit/src/program.rs @@ -489,16 +489,6 @@ impl Emitter { #[cfg(test)] mod tests { use super::{BUILTIN_GLOBALS, NON_WRITABLE_GLOBAL_BUILTINS}; - use crate::prepass::RESTRICTED_GLOBAL_LEXICAL_NAMES; - - /// 漂移守卫:受限全局名集与 put 写拦截名单交叠名恒同步(两名单语义独立、 - /// 不互相派生,靠本断言防止改名/删名时单边漂移)。 - #[test] - fn restricted_lexical_names_within_builtin_globals() { - for name in RESTRICTED_GLOBAL_LEXICAL_NAMES { - assert!(BUILTIN_GLOBALS.contains(name), "受限全局名缺失于 builtin 名单:{name}"); - } - } /// 漂移守卫:只读名单是 builtin 母集子集,且只读/可写两谓词对母集构成 /// 划分(不重不漏)——防三常量集或母集改名/删名时拦截面与双写面单边漂移。 diff --git a/crates/oxide_test262/src/discovery.rs b/crates/oxide_test262/src/discovery.rs index 5234117b..b582c2bd 100644 --- a/crates/oxide_test262/src/discovery.rs +++ b/crates/oxide_test262/src/discovery.rs @@ -110,6 +110,10 @@ pub(crate) fn pre_existing_excluded(path: &str) -> Option<&'static str> { "built-ins/Error/prototype/stack/instance-not-enumerable.js", "快照断言 JSON.stringify 空对象,与 Error.prototype.toJSON 规范序列化(name/message)冲突,子模块更新后自然通过", ), + ( + "language/global-code/decl-lex-restricted-global.js", + "语料负面 oracle 相对 V8 对齐裁定过时:引擎已合法化三常量名顶层词法声明(ES2025 规范 GDI lexNames 臂仍保留受限全局属性检查,属有意偏离;若规范日后变更并更新语料,移除本条目即重新对齐)", + ), ]; LIST.iter() .find(|(suffix, _)| path.ends_with(suffix)) diff --git a/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs b/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs index e6e515d1..2a744f41 100644 --- a/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs +++ b/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs @@ -1,10 +1,13 @@ -//! 脚本顶层 lexical 声明撞受限全局名的编译期拒绝:let/const 声明名撞 -//! 受限集(undefined/NaN/Infinity——三常量,全局对象 {configurable:false} -//! 自有属性名)→ SyntaxError(整程序编译失败)。局部作用域(函数体/块/try)与 -//! eval 代码为合法遮蔽不拒;三常数值使用不受影响。 +//! 脚本顶层 lexical 声明合法遮蔽三常量全局名(undefined/NaN/Infinity): +//! let/const/class 声明名撞三常量合法(对齐 V8/node 现行行为;ES2025 规范 +//! GDI lexNames 臂仍保留受限全局属性检查,属有意偏离,语料负面 oracle 已 +//! 登记跳过集)。裸读读绑定槽、裸写/delete 不落全局对象,全局属性描述符 +//! 不变;局部作用域(函数体/块/try)与 eval 代码为合法遮蔽不拒;三常数值 +//! 使用不受影响。 //! -//! class 名撞 eval 不在本集(解析器 early-error 族独立拒绝,类体恒严格模式, -//! 与受限集检查无关,见 top_level_class_eval_name_rejected)。 +//! class 名撞 undefined/eval 不在本集(解析器 early-error 族独立拒绝,类体 +//! 恒严格模式,与本集机制无关,见 top_level_class_undefined_rejected / +//! top_level_class_eval_name_rejected)。 use std::sync::Arc; @@ -20,74 +23,86 @@ fn eval(source: &str) -> Result { vm.run(&Arc::new(module)) } -/// 断言整程序编译失败且错消息为声明实例化期的重复声明错形(SyntaxError 族, -/// 消息实现自定,只断结构)。 -fn assert_compile_err(source: &str) { - let err = eval(source).unwrap_err(); - assert!(err.contains("already been declared"), "got: {err}"); -} - -/// 断言编译 + 运行均成功。 +/// 断言编译 + 运行均成功,返回完成值。 fn assert_ok(source: &str) -> JsValue { eval(source).unwrap_or_else(|e| panic!("expected ok, got: {e}\nsource: {source}")) } -// ── 脚本顶层 let/const/class 撞三常量:编译期拒绝 ── +/// 断言编译 + 运行均成功且完成值为字符串,返回字符串内容。 +fn assert_ok_str(source: &str) -> String { + let value = assert_ok(source); + if value.is_string() { + // SAFETY: 值已确认字符串类别,指针指向 VM 存活期内的 JsString。 + unsafe { &*value.as_string_ptr() }.as_str().to_string() + } else { + panic!("expected string, got {value:?}\nsource: {source}") + } +} + +// ── 脚本顶层 let/const/class 撞三常量:合法遮蔽 ── #[test] -fn top_level_let_undefined_rejected() { - // 独立形(脚本内无裸 undefined 引用,无镜像槽):规范 SyntaxError。 - assert_compile_err("let undefined;"); +fn top_level_let_undefined_allowed() { + // 独立形(脚本内无裸 undefined 引用,无镜像槽):合法,完成值 undefined。 + let r = assert_ok("let undefined;"); + assert!(r.is_undefined(), "let undefined 完成值应为 undefined,实际 {r:?}"); } #[test] -fn top_level_let_nan_rejected() { - assert_compile_err("let NaN;"); +fn top_level_let_nan_allowed() { + let r = assert_ok("let NaN;"); + assert!(r.is_undefined(), "let NaN 完成值应为 undefined,实际 {r:?}"); } #[test] -fn top_level_let_infinity_rejected() { - assert_compile_err("let Infinity;"); +fn top_level_let_infinity_allowed() { + let r = assert_ok("let Infinity;"); + assert!(r.is_undefined(), "let Infinity 完成值应为 undefined,实际 {r:?}"); } #[test] -fn top_level_const_nan_rejected() { - assert_compile_err("const NaN = 1;"); +fn top_level_const_nan_allowed() { + let r = assert_ok("const NaN = 1; NaN"); + assert_eq!(r.as_int(), 1); } #[test] -fn top_level_const_undefined_rejected() { - assert_compile_err("const undefined = 1;"); +fn top_level_const_undefined_allowed() { + let r = assert_ok("const undefined = 1; undefined"); + assert_eq!(r.as_int(), 1); } #[test] fn top_level_class_undefined_rejected() { - // class 名同为 lexical 绑定(受限检查同形);undefined 类名由解析器更早拒绝 - // (SyntaxError 族,整程序拒绝可观察行为同形),断言接受任一拒绝相位。 + // class 名撞 undefined 由解析器 early-error 族更早拒绝(SyntaxError 族, + // 整程序拒绝可观察行为同形),与本集机制无关,断言接受任一拒绝相位。 assert!(eval("class undefined {}").is_err(), "class undefined 类名应被拒绝"); } #[test] -fn top_level_class_infinity_rejected() { - assert_compile_err("class Infinity {}"); +fn top_level_class_infinity_allowed() { + // class 名同为 lexical 绑定:合法遮蔽,typeof 读绑定槽(类函数)。 + assert_eq!(assert_ok_str("class Infinity {} typeof Infinity"), "function"); } #[test] -fn top_level_let_undefined_with_bare_use_rejected() { - // 镜像形(脚本另有裸 undefined 引用):拒绝行为不变,错误相位提前到 - // 声明预登记期(消息同形)。 - assert_compile_err("let undefined; undefined"); +fn top_level_let_undefined_with_bare_use_allowed() { + // 镜像形(脚本另有裸 undefined 引用):声明点之后的裸读是词法绑定读, + // 合法,完成值为绑定值 undefined。 + let r = assert_ok("let undefined; undefined"); + assert!(r.is_undefined(), "镜像形完成值应为 undefined,实际 {r:?}"); } #[test] -fn top_level_let_destructured_nan_rejected() { - // 解构叶子同属声明名,受限检查覆盖。 - assert_compile_err("let [NaN] = [1];"); +fn top_level_let_destructured_nan_allowed() { + // 解构叶子同属声明名,合法遮蔽。 + let r = assert_ok("let [NaN] = [1]; NaN"); + assert_eq!(r.as_int(), 1); } #[test] fn top_level_switch_case_let_nan_allowed() { - // switch case 有独立 CaseBlock 作用域:case 内 lexical 是块局部绑定,不受限检查。 + // switch case 有独立 CaseBlock 作用域:case 内 lexical 是块局部绑定。 let r = assert_ok("switch (0) { case 0: let NaN; } 1"); assert_eq!(r.as_int(), 1); } @@ -96,12 +111,68 @@ fn top_level_switch_case_let_nan_allowed() { #[test] fn eval_script_let_nan_allowed() { - // eval 代码在独立 lexical 环境实例化,不受限检查约束;外层裸读仍是三常量值 + // eval 代码在独立 lexical 环境实例化;外层裸读仍是三常量值 //(NaN 自反性用 isNaN 断言,=== 对 NaN 恒 false)。 let r = assert_ok("eval('let NaN;'); isNaN(NaN)"); assert!(r.is_bool() && r.as_bool(), "eval 内 let NaN 应为合法遮蔽,实际 {r:?}"); } +// ── 值隔离与全局对象不变面:写/delete 落绑定槽,全局属性原值与描述符保留 ── + +#[test] +fn top_level_let_undefined_value_isolated() { + // 声明点后的裸读读绑定槽:得声明值,非全局属性值。 + let r = assert_ok("let undefined=5; undefined"); + assert_eq!(r.as_int(), 5); +} + +#[test] +fn top_level_let_undefined_global_this_reflection() { + // 全局对象属性原值保留:globalThis.undefined 仍是真 undefined(typeof 串形)。 + assert_eq!(assert_ok_str("let undefined=5; typeof globalThis.undefined"), "undefined"); +} + +#[test] +fn top_level_let_undefined_delete_not_global() { + // 词法绑定 delete 返 false(DeleteBinding 非属性引用),全局属性原值保留。 + let r = assert_ok("let undefined=5; delete undefined"); + assert!(r.is_bool() && !r.as_bool(), "实际 {r:?}"); + let r = assert_ok("let undefined=5; delete undefined; undefined"); + assert_eq!(r.as_int(), 5); +} + +#[test] +fn top_level_let_undefined_typeof() { + // typeof 读绑定槽:声明值 5 的 typeof 为 number。 + assert_eq!(assert_ok_str("let undefined=5; typeof undefined"), "number"); +} + +#[test] +fn top_level_let_undefined_nested_capture() { + // 嵌套函数读顶层词法绑定经 upvalue 捕获:得声明值。 + let r = assert_ok("let undefined=1; function f(){return undefined} f()"); + assert_eq!(r.as_int(), 1); +} + +#[test] +fn top_level_let_undefined_tdz_read_throws() { + // 声明点前裸读是 TDZ 运行期 ReferenceError(错消息含 before initialization)。 + let err = eval("undefined; let undefined;").unwrap_err(); + assert!(err.contains("before initialization"), "got: {err}"); +} + +#[test] +fn top_level_let_undefined_descriptor_unchanged() { + // 全局对象属性描述符不变:writable/configurable/enumerable 恒 false。 + let r = assert_ok( + "let undefined=5; Object.getOwnPropertyDescriptor(globalThis,'undefined') + .writable === false && Object.getOwnPropertyDescriptor(globalThis,'undefined') + .configurable === false && Object.getOwnPropertyDescriptor(globalThis,'undefined') + .enumerable === false", + ); + assert!(r.is_bool() && r.as_bool(), "描述符应为只读不可配置不可枚举,实际 {r:?}"); +} + // ── 假阳性守卫:合法面不得误拒 ── #[test] @@ -113,8 +184,7 @@ fn top_level_let_uses_nan_value_allowed() { #[test] fn top_level_let_eval_name_allowed() { - // eval 是全局对象可配置自有属性(受限判定走自有属性臂,eval 不命中): - // 合法遮蔽,不在受限集。 + // eval 是全局对象可配置自有属性:合法遮蔽,不在受限集。 let r = assert_ok("let eval; 1"); assert_eq!(r.as_int(), 1); } @@ -129,8 +199,8 @@ fn top_level_const_eval_name_allowed() { fn top_level_class_eval_name_rejected() { // eval 不在受限集(let/const 合法遮蔽),但 class 名撞 eval 由解析器 // early-error 族拒绝:类体恒严格模式,类名绑定检查落在类作用域 strict - // 判定内(V8 同形:sloppy `class eval {}` 亦 SyntaxError)。与本受限集 - // 检查无关,整程序拒绝可观察行为同形,断言任意拒绝相位。 + // 判定内(V8 同形:sloppy `class eval {}` 亦 SyntaxError)。与本集 + // 机制无关,整程序拒绝可观察行为同形,断言任意拒绝相位。 assert!(eval("class eval {}").is_err(), "class eval 类名应被拒绝"); } diff --git a/crates/oxide_vm/tests/global_lexical_shadow_builtin_tests.rs b/crates/oxide_vm/tests/global_lexical_shadow_builtin_tests.rs index 6a9e5843..b53f88e8 100644 --- a/crates/oxide_vm/tests/global_lexical_shadow_builtin_tests.rs +++ b/crates/oxide_vm/tests/global_lexical_shadow_builtin_tests.rs @@ -3,9 +3,9 @@ //! 不为其登记,声明合法建立遮蔽绑定——裸读读绑定槽、裸写/delete 不落全局 //! 对象(全局属性原值保留),声明点前裸读按 TDZ 运行期抛 ReferenceError。 //! -//! 回归约束:受限三常量(87 面)声明仍编译期拒绝;let+var / function+let -//! 撞 builtin 名的重复声明仍编译期拒绝(var 懒登记镜像与函数先登记撞点保留); -//! 未遮蔽 builtin 读、var 内置名写全局、函数内遮蔽读不回退。 +//! 回归约束:let+var / function+let 撞 builtin 名的重复声明仍编译期拒绝 +//! (var 懒登记镜像与函数先登记撞点保留);未遮蔽 builtin 读、var 内置名 +//! 写全局、函数内遮蔽读不回退。 use std::sync::Arc; @@ -153,13 +153,15 @@ fn top_level_let_shadow_tdz_second_read_throws() { assert!(err.contains("before initialization"), "got: {err}"); } -// ── 回归约束:受限名与重复声明拒面不翻 ── +// ── 回归约束:受限名已合法化(对齐 V8),重复声明拒面不翻 ── #[test] -fn top_level_let_restricted_names_still_rejected() { - // 受限三常量(87 面):非受限名修面不动此面,声明仍编译期拒绝。 - assert_compile_err("let undefined;"); - assert_compile_err("let NaN=1; NaN"); +fn top_level_let_restricted_names_legal_shadow() { + // 三常量名(undefined/NaN/Infinity)声明合法化:裸读读绑定槽。 + let r = assert_ok("let undefined;"); + assert!(r.is_undefined(), "let undefined 完成值应为 undefined,实际 {r:?}"); + let r = assert_ok("let NaN=1; NaN"); + assert_eq!(r.as_int(), 1); } #[test]