diff --git a/crates/oxide_emit/src/compile_ctx.rs b/crates/oxide_emit/src/compile_ctx.rs index b7f693b4..2ce17141 100644 --- a/crates/oxide_emit/src/compile_ctx.rs +++ b/crates/oxide_emit/src/compile_ctx.rs @@ -55,6 +55,11 @@ pub struct CompileCtx { /// 是否为 eval 脚本:脚本顶层 var/function 声明落全局对象时属性 /// configurable:true(普通脚本顶层为 false)。仅动态脚本入口设置。 pub(crate) is_eval_script: bool, + /// 编译树是否源自 eval 脚本:eval 代码自身顶层 var/function 名物化 c:true + /// 全局属性、可删,本标志随嵌套函数继承,供 delete 分类判定。不直接继承 + /// `is_eval_script`——该标志只标识当前顶层程序,且还控制顶层全局写点与块级 + /// 函数门控,整体传播语义过宽。 + pub(crate) is_eval_origin: bool, /// 源码是否为 `source_escape` 产物(动态编译入口):正则字面量源文本切片 /// 的池键形态据此选择(见 [`Emitter::source_encoded`])。 pub(crate) source_encoded: bool, @@ -183,6 +188,7 @@ impl CompileCtx { is_global_scope: false, repl_persist: false, is_eval_script: false, + is_eval_origin: false, source_encoded: false, static_block_this_reg: None, field_buffer: None, diff --git a/crates/oxide_emit/src/expr/identifier.rs b/crates/oxide_emit/src/expr/identifier.rs index 4086a7a4..e1793c4b 100644 --- a/crates/oxide_emit/src/expr/identifier.rs +++ b/crates/oxide_emit/src/expr/identifier.rs @@ -50,7 +50,10 @@ impl Emitter { // 文档),缺失抛 ReferenceError(unresolvable)。tier 名属性由 // GlobalDeclarationInstantiation(脚本顶层声明实例化)序言创建(仅删除后缺失); // 未声明名属性缺失即 unresolvable。已声明 var 不再落引擎侧镜像副本。 - if self.is_global_tier_name(ctx, name) || ctx.implicit_global_reads.contains(&var_reg) { + // 隐式全局槽(读侧登记与写侧登记同属一个槽)同样读全局对象属性:属性是 + // 隐式全局值的唯一真源,嵌套函数内 delete 真删后外层裸读据此可见,引擎侧 + // 镜像槽不反映删除。 + if self.is_global_tier_name(ctx, name) || ctx.is_implicit_global_reg(var_reg) { let key_idx = ctx.add_constant(Constant::String(name.to_string())); ctx.inst(Inst::new(OpCode::LOAD_GLOBAL, Operand::Reg(r), Operand::Const(key_idx), Operand::None)); } else { diff --git a/crates/oxide_emit/src/expr/operator/mod.rs b/crates/oxide_emit/src/expr/operator/mod.rs index 33f10411..76cb4f5c 100644 --- a/crates/oxide_emit/src/expr/operator/mod.rs +++ b/crates/oxide_emit/src/expr/operator/mod.rs @@ -215,9 +215,10 @@ impl Emitter { // var 名集 ∪ 顶层函数声明名,物化 c:true 全局属性) // 可删;eval 顶层 let/const 不在名集内(自身 lexical // 环境不可删),与非 eval 脚本自身顶层 var/函数名 - // (c:false)同保留 false。 + // (c:false)同保留 false。eval 起源随嵌套函数继承, + // 子 ctx 内 delete 同一名同样判可删。 ctx.is_implicit_global_reg(binding.reg) - || (ctx.is_eval_script && ctx.global_tier_names.contains(name)) + || (ctx.is_eval_origin && ctx.global_tier_names.contains(name)) } else { // 函数/块作用域局部绑定:非属性引用,恒 false。 false diff --git a/crates/oxide_emit/src/function_body.rs b/crates/oxide_emit/src/function_body.rs index 5d02b411..0a684b85 100644 --- a/crates/oxide_emit/src/function_body.rs +++ b/crates/oxide_emit/src/function_body.rs @@ -368,6 +368,9 @@ impl Emitter { // 顶层已声明 var 名集随作用域继承:嵌套函数裸读/写顶层 var 直连全局对象 // (经继承的 scope-0 绑定 + 作用域索引判定,不被局部同名遮蔽误判)。 ctx.global_tier_names = parent_ctx.global_tier_names.clone(); + // eval 起源随作用域继承:eval 顶层 var/函数名在嵌套函数内同样可删(物化 + // c:true 全局属性),delete 分类判定读此标志。 + ctx.is_eval_origin = parent_ctx.is_eval_origin; for (name, reg) in extra_bindings { ctx.scopes.symbols.scopes[0].bindings.insert( (*name).to_string(), diff --git a/crates/oxide_emit/src/program.rs b/crates/oxide_emit/src/program.rs index 49facc64..9497f8a3 100644 --- a/crates/oxide_emit/src/program.rs +++ b/crates/oxide_emit/src/program.rs @@ -265,6 +265,9 @@ impl Emitter { ctx.is_global_scope = true; ctx.repl_persist = repl_persist; ctx.is_eval_script = is_eval_script; + // eval 起源随嵌套函数继承,供 delete 分类判定 eval 顶层可删名;与 + // `is_eval_script` 区分——后者只标识当前顶层程序,传播会误改全局写点。 + ctx.is_eval_origin = is_eval_script; ctx.source_encoded = self.source_encoded; // 脚本顶层严格模式由源码 "use strict" directive 决定(嵌套函数经父 ctx 继承)。 ctx.is_strict = program.has_use_strict_directive(); diff --git a/crates/oxide_vm/tests/delete_eval_var_env_tests.rs b/crates/oxide_vm/tests/delete_eval_var_env_tests.rs index 8f8d388b..28b107e8 100644 --- a/crates/oxide_vm/tests/delete_eval_var_env_tests.rs +++ b/crates/oxide_vm/tests/delete_eval_var_env_tests.rs @@ -6,8 +6,11 @@ //! 环境,declarative 环境不可删)保留 false 常数。覆盖:direct eval 对调用方 //! 脚本 var、eval 自身 var 真删、跨 eval 真删、函数内隐式全局真删、读侧真删 //! 可见(删后裸读抛 ReferenceError)、typeof 对删后缺失名不抛、删后重写同步、 -//! 绿基线(脚本 var/顶层函数名/未声明缺失/builtin 槽优先级/strict 调用方 -//! indirect eval)、eval 自身顶层 let/const false 且无全局属性副作用。 +//! 跨嵌套函数 delete 后外层裸读抛 ReferenceError、catch 参数错误类型、eval +//! 起源随嵌套函数继承(顶层 var 可删/顶层 let 保留)、strict 未声明写抛错、 +//! 嵌套局部变量遮蔽隐式全局名、绿基线(脚本 var/顶层函数名/未声明缺失/builtin +//! 槽优先级/strict 调用方 indirect eval)、eval 自身顶层 let/const false 且无 +//! 全局属性副作用。 use std::sync::Arc; @@ -126,3 +129,65 @@ fn eval_delete_own_const_returns_false_without_global_property() { fn eval_delete_own_let_shadowing_caller_var_returns_false_and_kept() { eval_truthy("var l = 5; eval('let l = 1; delete l') === false && l === 5 && globalThis.l === 5"); } + +// ── 跨嵌套函数 delete:外层函数随后裸读该隐式全局名抛 ReferenceError ── +// 写与读同在外层函数,delete 在嵌套函数;删除登记须对外层读可见。 +#[test] +fn nested_delete_then_outer_read_throws_reference_error() { + eval_truthy( + "(function() { q194a = 2; var d = (function() { return delete q194a; })(); \ + try { void q194a; return false; } catch (e) { return d === true && e instanceof ReferenceError; } })()", + ); +} + +// ── 跨嵌套 delete 后 catch 参数:错误类型为 ReferenceError 且 name 正确 ── +#[test] +fn nested_delete_catch_param_is_reference_error_with_name() { + eval_truthy( + "(function() { q194b = 1; var d = (function() { return delete q194b; })(); \ + try { void q194b; return false; } catch (e) { \ + return d === true && e.name === 'ReferenceError' && e instanceof ReferenceError; } })()", + ); +} + +// ── 嵌套函数内 delete 未存在名:探针缺失臂 true ── +#[test] +fn nested_delete_missing_name_returns_true() { + eval_truthy("(function() { return (function() { return delete noexist194; })() === true; })()"); +} + +// ── eval 起源随嵌套函数继承:eval 顶层 var 在嵌套函数内删可删(c:true) ── +#[test] +fn eval_origin_delete_own_top_var_from_nested_function_returns_true() { + eval_truthy("eval('var v194 = 1; function f194() { return delete v194; }') === undefined && f194() === true"); +} + +// ── eval 起源守卫:eval 顶层 let 是 lexical 绑定,嵌套函数内 delete 仍 false ── +#[test] +fn eval_origin_delete_own_top_let_from_nested_function_returns_false() { + eval_truthy("eval('let l194 = 1; function g194() { return delete l194; }') === undefined && g194() === false"); +} + +// ── strict 守卫:未声明写仍抛 ReferenceError,不受读路由改动影响 ── +#[test] +fn strict_undeclared_write_still_throws_reference_error() { + eval_truthy( + "(function() { \"use strict\"; try { undeclared194 = 1; return false; } \ + catch (e) { return e instanceof ReferenceError; } })()", + ); +} + +// ── 跨嵌套 delete 后重写:镜像槽与全局对象属性重新同步 ── +#[test] +fn reassign_after_nested_delete_resyncs_both_sides() { + eval_truthy( + "(function() { q194c = 1; (function() { return delete q194c; })(); q194c = 9; \ + return q194c === 9 && globalThis.q194c === 9; })()", + ); +} + +// ── 嵌套函数内局部同名 var 遮蔽隐式全局名:局部绑定不受全局读路由影响 ── +#[test] +fn nested_local_var_shadowing_implicit_global_name_unaffected() { + eval_truthy("(function() { q194d = 1; return (function() { var q194d = 2; return q194d === 2; })(); })()"); +}