From 04188201152d1c9c16aaff7ae44f58bccc6846d2 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 15 Sep 2026 06:42:12 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=9D=97=E7=BA=A7=E5=87=BD=E6=95=B0=20b?= =?UTF-8?q?uiltin=20=E5=90=8D=E5=AE=88=E5=8D=AB=E6=94=B6=E7=AA=84=E8=87=B3?= =?UTF-8?q?=E5=8F=AA=E8=AF=BB=E4=B8=89=E5=B8=B8=E9=87=8F=E9=9D=A2=EF=BC=8C?= =?UTF-8?q?=E5=8F=AF=E9=85=8D=E7=BD=AE=20builtin=20=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=E5=AE=9E=E4=BE=8B=E5=8C=96=E5=86=99=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_emit/src/emit.rs | 19 +++++--- .../src/stmt/declaration/function_decl.rs | 7 +-- .../oxide_vm/tests/block_fn_var_head_tests.rs | 45 ++++++++++++++++++- 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 22c1293b..e18b323b 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -751,6 +751,14 @@ impl CompileCtx { BUILTIN_GLOBALS.contains(&name) } + /// 规范不可声明的全局名:只读三常量(全局对象上数据属性 + /// {writable:false, configurable:false})——顶层块级函数名仅这一面不建 + /// 外层绑定、不求值写回(sloppy put 永不成功,跳过与执行等价);其余 + /// 已知 builtin(parseInt 等)属性可配置,可声明(求值期覆写全局属性)。 + pub(crate) fn is_non_writable_global_builtin(name: &str) -> bool { + NON_WRITABLE_GLOBAL_BUILTINS.contains(&name) + } + /// 进入 with 语句体:记录对象寄存器与当前作用域深度,供动态标识符解析。 pub(crate) fn push_with(&mut self, obj_reg: u32) { let depth = self.scopes.symbols.scopes.len(); @@ -1834,14 +1842,15 @@ impl Emitter { self.predeclare_lexical_declarations(&program.body, &mut ctx, global_lexical)?; // 顶层块级函数名 web-compat 外层绑定(sloppy、非 eval):实例化 var 绑定 - // (新建 var 槽;顶层 builtin 名不可声明不建)、并入顶层 var 名集(裸读/ - // 写路由全局对象属性)、GDI 序言建属性(define-if-absent,既有属性零动作)。 + // (新建 var 槽;顶层只读三常量名不可声明不建)、并入顶层 var 名集(裸 + // 读/写路由全局对象属性)、GDI 序言建属性(define-if-absent,既有属性 + // 零动作)。 let mut block_fn_names: Vec = Vec::new(); if !ctx.is_strict && !ctx.is_eval_script { ctx.block_fn_suppressed = self.collect_block_fn_suppressed_names(&program.body, &ctx.param_names); block_fn_names = self.collect_block_function_names(&program.body); for name in &block_fn_names { - if !ctx.block_fn_suppressed.contains(name) && !CompileCtx::is_known_builtin(name) { + if !ctx.block_fn_suppressed.contains(name) && !CompileCtx::is_non_writable_global_builtin(name) { self.predeclare_var_name(name, &mut ctx); } } @@ -1859,7 +1868,7 @@ impl Emitter { tier_names.extend(self.collect_top_level_function_names(&program.body)); // 块级函数泄漏名并入:求值期写回与头写同走全局对象属性(A 侧单一真值)。 for name in &block_fn_names { - if !ctx.block_fn_suppressed.contains(name) && !CompileCtx::is_known_builtin(name) { + if !ctx.block_fn_suppressed.contains(name) && !CompileCtx::is_non_writable_global_builtin(name) { tier_names.insert(name.clone()); } } @@ -1904,7 +1913,7 @@ impl Emitter { gdi_var_names.extend( block_fn_names .iter() - .filter(|n| !ctx.block_fn_suppressed.contains(*n) && !CompileCtx::is_known_builtin(n)) + .filter(|n| !ctx.block_fn_suppressed.contains(*n) && !CompileCtx::is_non_writable_global_builtin(n)) .cloned(), ); if !gdi_var_names.is_empty() { diff --git a/crates/oxide_emit/src/stmt/declaration/function_decl.rs b/crates/oxide_emit/src/stmt/declaration/function_decl.rs index 9c7696bf..e7971c72 100644 --- a/crates/oxide_emit/src/stmt/declaration/function_decl.rs +++ b/crates/oxide_emit/src/stmt/declaration/function_decl.rs @@ -42,12 +42,13 @@ impl Emitter { // 块内函数声明求值写回外层 var 绑定(sloppy web-compat):预声明期为该 // 名实例化的外层 var 绑定(函数作用域)在此写入块槽位里的函数对象, // 求值一次写回一次(循环体内每迭代覆写头绑定)。形参/词法声明同名 - // (抑制集)与顶层 builtin 名不写回;顶层 A 侧补全局对象属性写(与 - // for 头写同形)。 + // (抑制集)与顶层只读三常量名不写回(对不可写属性 put 在 sloppy 下 + // 永不成功,跳过与执行等价);顶层 A 侧补全局对象属性写(与 for 头写 + // 同形)。 if !ctx.is_strict && ctx.scopes.symbols.scopes.len() > 1 && !ctx.block_fn_suppressed.contains(&name) - && !(ctx.is_global_scope && CompileCtx::is_known_builtin(&name)) + && !(ctx.is_global_scope && CompileCtx::is_non_writable_global_builtin(&name)) { let var_target = ctx.scopes.symbols.find_var_target_scope(); if let Some(outer) = ctx.scopes.symbols.scopes[var_target].bindings.get(&name) { diff --git a/crates/oxide_vm/tests/block_fn_var_head_tests.rs b/crates/oxide_vm/tests/block_fn_var_head_tests.rs index e12f9888..1d959492 100644 --- a/crates/oxide_vm/tests/block_fn_var_head_tests.rs +++ b/crates/oxide_vm/tests/block_fn_var_head_tests.rs @@ -3,7 +3,8 @@ //! //! 覆盖:顶层/函数作用域 for-in 头形、嵌套块/switch case/for-of 变体、 //! 无碰撞泄漏名、while 块、arguments 名守卫、既有全局属性保留、形参/词法 -//! 碰撞守卫、strict 无写回对照。 +//! 碰撞守卫、strict 无写回对照、顶层可配置 builtin 名覆写形、函数作用域 +//! builtin 名局部影子对照、不可写三常量跳过对照。 use std::sync::Arc; @@ -125,3 +126,45 @@ fn strict_for_body_no_write_back() { "function f(){ \"use strict\"; var o={a:1,b:2}; for (var x in o){function x(){}} return typeof x } f() === 'string'", ); } + +#[test] +fn top_level_block_fn_named_parseint_overwrites_global_property() { + // 顶层可配置 builtin 名:实例化期建外层并入 var 名集,求值期写回覆写全局 + // 对象属性,块后调用见函数对象(空体返 undefined);写回被跳过则此钉读 + // builtin 值 42 红。 + let result = eval("{function parseInt(){}} parseInt('42')").unwrap(); + assert!(result.is_undefined(), "parseInt 形应为 undefined,得 {:?}", result); +} + +#[test] +fn top_level_block_fn_named_object_overwrites_global_property() { + // Object 名孪生形:构造器名是可配置全局属性,写回同样覆写;空体函数对象 + // 非 new 调用返 undefined(builtin 形会返字符串包装)。 + let result = eval("{function Object(){}} Object('x')").unwrap(); + assert!(result.is_undefined(), "Object 形应为 undefined,得 {:?}", result); +} + +#[test] +fn top_level_block_fn_named_parseint_kept_before_decl() { + // builtin 名全局属性运行期预存:GDI 序言 define-if-absent 零动作,声明点前 + // 读仍见 builtin 原值,声明点后读见覆写函数。 + truthy("var r = parseInt('77') === 77; {function parseInt(){}} r && parseInt('42') === undefined"); +} + +#[test] +fn function_scope_block_fn_named_parseint_local_shadow() { + // 函数作用域对照:写回守卫只门顶层不可写面,函数体内 builtin 名走局部 var + // 影子(全局属性不受影响),收窄误入函数作用域面在此红。 + truthy( + "function f(){ {function parseInt(){}} return parseInt('42') === undefined } f() && typeof globalThis.parseInt === 'function'", + ); +} + +#[test] +fn readonly_global_constants_skip_write_back() { + // 不可写三常量对照:写回守卫只豁免这一面(put 永不成功),块后读不变—— + // 收窄事故(三常量误入写回或可配置面误跳)在此红。 + truthy("{function undefined(){}} typeof undefined === 'undefined'"); + truthy("{function Infinity(){}} typeof Infinity === 'number'"); + truthy("{function NaN(){}} typeof NaN === 'number'"); +}