From 920dc0e7f77ff36f95a3de70042aebc33a69ea56 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 9 Oct 2026 09:47:52 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20direct=20eval=20=E4=B8=B2=E6=8C=89?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E6=96=B9=E4=B8=A5=E6=A0=BC=E6=80=A7=E5=89=8D?= =?UTF-8?q?=E7=BD=AE=E4=B8=A5=E6=A0=BC=E6=8C=87=E4=BB=A4=EF=BC=8C=E5=8F=97?= =?UTF-8?q?=E9=99=90=E5=90=8D=20catch=20=E5=8F=82=E6=95=B0=E6=8A=A5=20Synt?= =?UTF-8?q?axError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 38 ++-- Cargo.toml | 2 +- crates/oxide_vm/src/vm/dynamic.rs | 23 ++- .../tests/catch_param_restricted_tests.rs | 186 ++++++++++++++++++ .../tests/delete_eval_var_env_tests.rs | 11 +- .../tests/global_var_binding_tests.rs | 9 +- 6 files changed, 240 insertions(+), 29 deletions(-) create mode 100644 crates/oxide_vm/tests/catch_param_restricted_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 3c78798a..81c29167 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.96" +version = "0.8.97" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.96" +version = "0.8.97" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.96" +version = "0.8.97" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.96" +version = "0.8.97" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.96" +version = "0.8.97" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.96" +version = "0.8.97" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.96" +version = "0.8.97" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.96" +version = "0.8.97" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.8.96" +version = "0.8.97" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.8.96" +version = "0.8.97" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.96" +version = "0.8.97" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.96" +version = "0.8.97" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.96" +version = "0.8.97" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.96" +version = "0.8.97" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.96" +version = "0.8.97" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.96" +version = "0.8.97" dependencies = [ "num-bigint", "num-traits", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.96" +version = "0.8.97" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.96" +version = "0.8.97" dependencies = [ "bumpalo", "dashmap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.96" +version = "0.8.97" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 24446070..dc18c2e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.8.96" +version = "0.8.97" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_vm/src/vm/dynamic.rs b/crates/oxide_vm/src/vm/dynamic.rs index 77512de0..d1b5ae22 100644 --- a/crates/oxide_vm/src/vm/dynamic.rs +++ b/crates/oxide_vm/src/vm/dynamic.rs @@ -114,13 +114,18 @@ impl Vm { /// (属性 configurable:true,区别于普通脚本顶层的 false)。 /// /// # 步骤 - /// 1. parse(脚本模式)→ compile(emit_program 置 is_global_scope=true, + /// 1. 严格性判定:调用方上下文严格(`current_strict()` 三级回退)时源串前置 + /// `"use strict";` 指令,使整串按严格编译(direct eval 字符串的严格性随 + /// 外层脚本与直接调用函数传播)。 + /// 2. parse(脚本模式)→ compile(emit_program 置 is_global_scope=true, /// Compiler 置 is_eval_script=true)。 - /// 2. 整棵模块树(根 flat_id=0 + 嵌套函数)追加进平表:`rehome_subtree(&module, base+1)`, + /// 3. 整棵模块树(根 flat_id=0 + 嵌套函数)追加进平表:`rehome_subtree(&module, base+1)`, /// 使根落 base、子函数 old→base+old,CREATE_CLOSURE imm16 同步重写。 - /// 3. 扩容当前代际的常量缓存,建函数对象(sub_module_index = base)返回。 + /// 4. 扩容当前代际的常量缓存,建函数对象(sub_module_index = base)返回。 /// /// # 边界与前提 + /// - 严格上下文下整串按严格编译:`this`/`with`/`delete`/赋值语义随严格模式翻转, + /// 受限名 catch 参数(eval/arguments)报 SyntaxError。 /// - 顶层 return 不报 SyntaxError(emit 无此检查,与 CLI 脚本路径一致)——已知偏差。 /// - 动态模块在函数对象存活期间跨 run 有效(同 create_dynamic_function)。 /// - 返回函数对象仅供内部同步调用,不设 name/length(用户不可见)。 @@ -130,7 +135,17 @@ impl Vm { pub fn create_dynamic_script(&mut self, code: &str) -> Result { // eval 脚本:顶层 var/function 声明落全局属性 configurable:true。 // 动态路径源契约同 `create_dynamic_function`(源码域转义形态传源)。 - let module = self.compiler.compile_eval_script(code)?; + + // direct eval 字符串的严格性随外层脚本与直接调用函数传播(与共识引擎口径一致): + // oxc 的 SourceType 无 strict 入口,调用方严格性只能由引擎前置严格指令承载, + // 使 oxc 既有 strict 绑定检查(catch 参数受限名 eval/arguments 等)报 SyntaxError。 + let source = if self.current_strict() { + format!("\"use strict\";\n{code}") + } else { + code.to_string() + }; + + let module = self.compiler.compile_eval_script(&source)?; // 根模块 flat_id=0 传 base+1,重编号后落 base(避开 sub_module_index()==0 // 守卫)。make_mut 彼时平表 Arc 强引用唯一持有者是本表,原地扩展不分叉。 let table = self.current_table_mut(); diff --git a/crates/oxide_vm/tests/catch_param_restricted_tests.rs b/crates/oxide_vm/tests/catch_param_restricted_tests.rs new file mode 100644 index 00000000..2151b6e8 --- /dev/null +++ b/crates/oxide_vm/tests/catch_param_restricted_tests.rs @@ -0,0 +1,186 @@ +//! direct eval 字符串的严格性随调用方上下文传播:strict 上下文(顶层脚本 / 继承 +//! 严格函数 / 自带指令函数)下,eval 串整体按严格编译,受限名 catch 参数 +//! (eval/arguments,含解构模式绑定名)报 SyntaxError;sloppy 上下文不受影响。 +//! +//! 完成值一律收敛为布尔或数值(JsValue 的 Display 只暴露 number/bool,不暴露 +//! 字符串内容),字符串比较在引擎内完成。 + +use std::sync::Arc; + +use oxide_compiler::compiler::Compiler; +use oxide_compiler::DefaultCompilerService; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> String { + let allocator = Allocator::default(); + let program = match oxide_parser::parse(&allocator, source) { + Ok(p) => p, + Err(e) => return format!("parse error: {}", e[0].message), + }; + let module = match Compiler::new().compile(&program) { + Ok(m) => m, + Err(e) => return format!("compile error: {e}"), + }; + let mut vm = Vm::new(); + vm.set_compiler_service(Arc::new(DefaultCompilerService)); + match vm.run(&Arc::new(module)) { + Ok(result) => format!("{result}"), + Err(e) => format!("vm error: {e}"), + } +} + +/// 断言 strict 上下文 direct eval 抛可捕获 SyntaxError(收敛为布尔 true)。 +fn assert_syntax_error(source: &str, msg: &str) { + let res = eval(source); + assert_eq!(res, "true", "{msg}: expected a catchable SyntaxError, got: {res}"); +} + +// ── strict 上下文 direct eval 受限名 catch 参数报 SyntaxError ── + +#[test] +fn strict_script_inheriting_fn_eval_catch_arguments_throws() { + // strict 脚本 + 无自带指令函数(继承严格)direct eval:受限名 catch 参数报 SyntaxError。 + assert_syntax_error( + r#""use strict"; + try { (function() { eval("try {} catch (arguments) { }"); })(); false } + catch (e) { e instanceof SyntaxError }"#, + "strict script + inheriting function direct eval restricted catch name must throw", + ); +} + +#[test] +fn strict_top_level_eval_catch_arguments_throws() { + // strict 顶层 direct eval:受限名 catch 参数报 SyntaxError(语料 -throws 同形)。 + assert_syntax_error( + r#""use strict"; + try { eval("try {} catch (arguments) { }"); false } + catch (e) { e instanceof SyntaxError }"#, + "strict top-level direct eval restricted catch name must throw", + ); +} + +#[test] +fn sloppy_script_strict_fn_eval_catch_arguments_throws() { + // sloppy 脚本 + 自带指令严格函数 direct eval:调用方严格性为真,受限名报 SyntaxError。 + assert_syntax_error( + r#"function f() { "use strict"; eval("try {} catch (arguments) { }"); } + try { f(); false } catch (e) { e instanceof SyntaxError }"#, + "sloppy script + strict function direct eval restricted catch name must throw", + ); +} + +#[test] +fn strict_script_nested_fn_eval_catch_arguments_throws() { + // strict 脚本 + 嵌套函数 direct eval:嵌套函数继承严格,受限名报 SyntaxError。 + assert_syntax_error( + r#""use strict"; + function outer() { function inner() { eval("try {} catch (arguments) { }"); } inner(); } + try { outer(); false } catch (e) { e instanceof SyntaxError }"#, + "strict script + nested function direct eval restricted catch name must throw", + ); +} + +#[test] +fn strict_ctx_eval_catch_destructure_array_eval_throws() { + // strict 上下文 direct eval 解构数组模式绑定名 eval:受限名报 SyntaxError。 + assert_syntax_error( + r#""use strict"; + try { eval("try {} catch ([eval]) { }"); false } + catch (e) { e instanceof SyntaxError }"#, + "strict context direct eval array-destructured catch name eval must throw", + ); +} + +#[test] +fn strict_ctx_eval_catch_object_shorthand_eval_throws() { + // strict 上下文 direct eval 对象简写模式绑定名 eval:受限名报 SyntaxError。 + assert_syntax_error( + r#""use strict"; + try { eval("try {} catch ({eval}) { }"); false } + catch (e) { e instanceof SyntaxError }"#, + "strict context direct eval object-shorthand catch name eval must throw", + ); +} + +// ── 守卫:sloppy 上下文 / 改名 / 完成值不受影响 ── + +#[test] +fn sloppy_script_sloppy_fn_eval_legal_completion_preserved() { + // sloppy 脚本 + sloppy 函数 direct eval:合法,eval 完成值保留。 + assert_eq!( + eval("function f() { return eval(\"1 + 2\"); } f()"), + "3", + "sloppy context direct eval stays legal and preserves the completion value" + ); +} + +#[test] +fn strict_ctx_eval_catch_object_renamed_legal() { + // 守卫:受限名检查只认绑定名,对象简写改名(eval: e)后合法。 + assert_eq!( + eval(r#""use strict"; try { eval("try {} catch ({eval: e}) { }"); true } catch (e) { false }"#), + "true", + "renamed object property binding is not a restricted name" + ); +} + +#[test] +fn eval_catch_default_value_throws() { + // 守卫:catch 参数默认值恒为 SyntaxError(既有 parse 臂保真,与严格性无关)。 + assert_syntax_error( + r#"try { eval("try {} catch (e = 2) { }"); false } catch (e) { e instanceof SyntaxError }"#, + "catch parameter default value must stay a SyntaxError", + ); +} + +#[test] +fn function_ctor_catch_arguments_legal() { + // 守卫:Function 构造器不继承调用方严格性,受限名 catch 参数合法。 + assert_eq!( + eval("new Function(\"try{}catch(arguments){}\")()"), + "undefined", + "Function constructor does not inherit caller strictness" + ); +} + +#[test] +fn eval_own_strict_directive_catch_eval_throws() { + // 守卫:eval 串自带 'use strict' 指令时,sloppy 上下文亦按严格编译,受限名报 SyntaxError。 + assert_syntax_error( + r#"try { eval("\'use strict\'; try {} catch (eval) { }"); false } catch (e) { e instanceof SyntaxError }"#, + "eval string with its own strict directive must reject restricted catch name", + ); +} + +#[test] +fn strict_ctx_indirect_eval_catch_arguments_throws() { + // 现状钉:引擎不区分 direct/indirect eval,strict 上下文间接 eval 受限名串亦拒 + // (node 接受,偏差归 caller 环境通道面,本钉仅记录现状)。 + assert_syntax_error( + r#""use strict"; + try { (0,eval)("try {} catch (arguments) { }"); false } + catch (e) { e instanceof SyntaxError }"#, + "indirect eval in strict context currently rejects restricted catch name", + ); +} + +#[test] +fn sloppy_eval_completion_value_unaffected() { + // 守卫:sloppy 上下文 eval 完成值不受严格性判定影响。 + assert_eq!( + eval("eval(\"1+2\")"), + "3", + "sloppy context eval completion value is unaffected" + ); +} + +#[test] +fn static_strict_catch_eval_parse_rejected() { + // 守卫:静态 strict 脚本 catch (eval) 在 parse 期即被 oxc strict 绑定检查拒绝。 + let res = eval("\"use strict\"; try {} catch (eval) {}"); + assert!( + res.starts_with("parse error"), + "static strict catch (eval) must be rejected at parse time, got: {res}" + ); +} 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 f625389b..b2089caf 100644 --- a/crates/oxide_vm/tests/delete_eval_var_env_tests.rs +++ b/crates/oxide_vm/tests/delete_eval_var_env_tests.rs @@ -75,10 +75,15 @@ fn delete_top_var_from_function_scope_returns_false() { eval_truthy("var p = 1; (function() { return delete p; })() === false && p === 1"); } -// ── 绿基线:strict 调用方 + indirect eval(eval 代码不继承调用方严格性) ── +// ── 现状钉:strict 调用方 + indirect eval(引擎不区分 direct/indirect, +// eval 串继承调用方严格性):delete 未限定名在严格模式报 SyntaxError ── #[test] -fn strict_caller_indirect_eval_delete_undeclared_returns_true() { - eval_truthy("(function() { \"use strict\"; return (0, eval)(\"delete zqq_probe\"); })() === true"); +fn strict_caller_indirect_eval_delete_undeclared_throws_syntax_error() { + eval_truthy( + "(function() { \"use strict\"; return (function() { \ + try { (0, eval)(\"delete zqq_probe\"); return false; } \ + catch (e) { return e instanceof SyntaxError; } })(); })() === true", + ); } // ── 可删全局内置镜像槽臂优先:探针臂不吞 builtin 槽(真删 + 清槽) ── diff --git a/crates/oxide_vm/tests/global_var_binding_tests.rs b/crates/oxide_vm/tests/global_var_binding_tests.rs index 9dca3374..88b76686 100644 --- a/crates/oxide_vm/tests/global_var_binding_tests.rs +++ b/crates/oxide_vm/tests/global_var_binding_tests.rs @@ -392,8 +392,13 @@ fn direct_eval_var_and_write_visible_to_outer() { eval_truthy("eval('var y = 5'); y === 5 && globalThis.y === 5"); eval_truthy("var q = 1; eval('q = 55'); q === 55 && globalThis.q === 55"); eval_truthy("eval('var ev = 1'); Object.getOwnPropertyDescriptor(globalThis, 'ev').configurable === true"); - // 嵌套函数内直接 eval:写点经 session 解析全局对象(不依赖 this)。 - eval_truthy("var z = 1; (function(){ \"use strict\"; eval('z = 77'); })(); z === 77 && globalThis.z === 77"); + // 嵌套函数内直接 eval:strict 上下文 eval 串继承调用方严格性,写未声明名抛 + // ReferenceError(引擎独立编译 eval 程序,不共享调用方作用域,严格模式禁隐式 + // 全局写入;sloppy 下该形为隐式全局写入,见上三行)。 + eval_truthy( + "var z = 1; (function(){ \"use strict\"; return (function() { \ + try { eval('z = 77'); return false; } catch (e) { return e instanceof ReferenceError; } })(); })() === true", + ); } /// 既有属性与顶层 var 声明交互:序言 define-if-absent 零动作,声明值写