From c7b238e51805d08aeb27e4dfd8e0c4560a7cc311 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 16 Aug 2026 22:25:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20eval=20=E5=85=A8=E5=B1=80=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E4=B8=8E=E6=9C=80=E5=B0=8F=E6=89=A7=E8=A1=8C=EF=BC=88?= =?UTF-8?q?=E9=97=B4=E6=8E=A5=20eval=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/eval.rs | 44 ++++++ crates/oxide_builtins/src/lib.rs | 2 + crates/oxide_emit/src/emit.rs | 1 + crates/oxide_test262/src/main.rs | 53 ++++++- crates/oxide_vm/src/bindings/bind_global.rs | 1 + crates/oxide_vm/src/bindings/mod.rs | 1 + crates/oxide_vm/tests/eval_tests.rs | 152 ++++++++++++++++++++ 7 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 crates/oxide_builtins/src/eval.rs create mode 100644 crates/oxide_vm/tests/eval_tests.rs diff --git a/crates/oxide_builtins/src/eval.rs b/crates/oxide_builtins/src/eval.rs new file mode 100644 index 00000000..fc5e1299 --- /dev/null +++ b/crates/oxide_builtins/src/eval.rs @@ -0,0 +1,44 @@ +//! eval 全局函数:非字符串原样返回;字符串按脚本/表达式动态编译执行。 +//! +//! # 契约 +//! - 非字符串实参**不** ToString(`eval(new String("1+1"))` 返回对象本身)。 +//! - 字符串经动态编译以 global 为 this 同步调用;编译失败 → SyntaxError; +//! 运行异常 → 优先重抛原始异常值(`take_uncaught_value`,`eval("throw 1")` 捕获到 `1`)。 +//! - 档 1 函数模式:eval 内 var 不落全局;档 2 切 `create_dynamic_script` 后 var 落全局。 + +use oxide_runtime_api::{NativeResult, VmHost}; +use oxide_types::object::JsObject; +use oxide_types::value::JsValue; + +use crate::error; + +/// `eval(x)`:非字符串实参原样返回;字符串按脚本编译执行(档 1 走函数模式)。 +/// +/// # 步骤 +/// 1. 实参非字符串 → 原样返回(规范不 ToString)。 +/// 2. 字符串 → 动态编译为匿名函数(`create_dynamic_function`),以 global 为 this 同步调用。 +/// 3. 编译失败 → SyntaxError;运行异常 → 优先重抛原始异常值(`take_uncaught_value`)。 +/// +/// # 边界与前提 +/// - 档 1 函数模式:eval 内 var 不落全局;档 2 切 `create_dynamic_script` 后 var 落全局。 +/// - 无实参等价于 `eval(undefined)` → undefined。 +pub fn eval(vm: &mut H, args: &[u8]) -> NativeResult { + let arg = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() }; + if !arg.is_string() { + return NativeResult::Ok(arg); + } + let code = vm.string_ref(arg).to_string(); + // 档 2 起把此分支换成 `vm.create_dynamic_script(&code)`。 + match vm.create_dynamic_function(&[], &code) { + Ok(func) => { + let global = JsValue::from_js_object(vm.session().global_object().as_ptr() as *mut JsObject); + match vm.call_function_sync(func, global, &[]) { + Ok(val) => NativeResult::Ok(val), + Err(e) => { + NativeResult::Err(vm.take_uncaught_value().unwrap_or_else(|| error::create_from_text(vm, &e))) + } + } + } + Err(e) => NativeResult::Err(error::create_syntax_error(vm, &e)), + } +} diff --git a/crates/oxide_builtins/src/lib.rs b/crates/oxide_builtins/src/lib.rs index 75d197c7..3968ab53 100644 --- a/crates/oxide_builtins/src/lib.rs +++ b/crates/oxide_builtins/src/lib.rs @@ -25,6 +25,8 @@ pub mod date; pub mod disposable_stack; /// Error 内置对象实现(各类 Error 构造函数与 stack/toString)。 pub mod error; +/// eval 全局函数实现(动态编译执行脚本/表达式)。 +pub mod eval; /// Function 内置对象实现(call/apply/bind/toString)。 pub mod function; /// 全局函数模块(URI 编解码与 Annex B 的 escape/unescape)。 diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index c9c78daa..8842dc7a 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -83,6 +83,7 @@ const BUILTIN_GLOBALS: &[&str] = &[ "SyntaxError", "URIError", "EvalError", + "eval", "SuppressedError", "DisposableStack", "AsyncDisposableStack", diff --git a/crates/oxide_test262/src/main.rs b/crates/oxide_test262/src/main.rs index 3eaac516..264e805f 100644 --- a/crates/oxide_test262/src/main.rs +++ b/crates/oxide_test262/src/main.rs @@ -468,6 +468,54 @@ fn is_skipped(meta: &TestMeta) -> Option { None } +/// eval 相关子族精确排除(档 1+2 后): +/// built-ins/eval 与 eval-code 的完成值/解析失败/非字符串/this-value-global/间接环境族放行; +/// 仅排除确定失败的 arguments/super/strict/块声明 等子族。 +fn eval_family_excluded(path: &str) -> Option<&'static str> { + if !path.contains("/eval-code/") { + return None; + } + let is_direct = path.contains("eval-code/direct/"); + let common = [ + "declare-arguments", // 直接 eval 的 arguments 语义族(档 3) + "non-definable", // 与既有不可配置全局属性冲突(DEFINE_GLOBAL_PROP 静默跳过,不抛) + "this-value-func", // 调用者 this 传递(档 3) + "new.target", // new.target 语义(档 3) + "strict-caller", // 严格调用者传播(档 3) + "strict-source", + "strictness-override", // 直接 eval 严格性覆盖 + "onlystrict", // onlyStrict 块声明族 + "always-non-strict", // 依赖隐式全局写同步(既有债务) + "block-decl", // 块级函数声明(Annex B 严格变体) + "switch-case-decl", + "switch-dflt-decl", + ]; + if common.iter().any(|s| path.contains(s)) { + return Some("eval 子族未实现(档 1-2 边界)"); + } + if is_direct { + // 直接 eval:调用者作用域交互族(函数上下文 var/let + super 方法上下文),档 3 前失败 + if ["var-env-", "lex-env-", "super-prop", "super-call-arrow", "super-call-method"] + .iter() + .any(|s| path.contains(s)) + { + return Some("直接 eval 作用域族未实现(档 3)"); + } + } else if [ + "super-", + "var-env-func-strict", + "var-env-var-strict", + "var-env-global-lex", + "var-env-lower-lex", + ] + .iter() + .any(|s| path.contains(s)) + { + return Some("间接 eval 严格/词法冲突族未实现"); + } + None +} + /// 在 catch_unwind 保护下运行单个测试,把引擎 panic 记为失败。 #[expect(clippy::too_many_arguments)] fn run_test( @@ -1262,9 +1310,12 @@ fn process_path( if !no_skip { let path_str = path.to_string_lossy().replace('\\', "/"); - if path_str.contains("/eval/") || path_str.contains("/function-ctor/") || path_str.contains("/realm/") { + if path_str.contains("/function-ctor/") || path_str.contains("/realm/") { return TestResult::skip(path.to_path_buf(), "unsupported class/eval feature excluded".into()); } + if let Some(reason) = eval_family_excluded(&path_str) { + return TestResult::skip(path.to_path_buf(), reason.into()); + } if let Some(reason) = is_skipped(&meta) { return TestResult::skip(path.to_path_buf(), reason); } diff --git a/crates/oxide_vm/src/bindings/bind_global.rs b/crates/oxide_vm/src/bindings/bind_global.rs index 7d34e714..39597388 100644 --- a/crates/oxide_vm/src/bindings/bind_global.rs +++ b/crates/oxide_vm/src/bindings/bind_global.rs @@ -39,6 +39,7 @@ pub fn bind_global(core: &Arc, session: &KernelSession, global: &mut ("__moduleSeal", oxide_builtins::module::module_seal:: as *const (), 1), ("__moduleEval", oxide_builtins::module::module_eval:: as *const (), 1), ("__moduleData", oxide_builtins::module::module_data:: as *const (), 2), + ("eval", oxide_builtins::eval::eval:: as *const (), 1), ], ); } diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 57b0bd43..45d6ccb4 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -642,6 +642,7 @@ fn bind_global_functions(core: &Arc, session: &KernelSession, global oxide_builtins::global::decode_uri_component:: as *const (), 1, ), + ("eval", oxide_builtins::eval::eval:: as *const (), 1), ], ); } diff --git a/crates/oxide_vm/tests/eval_tests.rs b/crates/oxide_vm/tests/eval_tests.rs new file mode 100644 index 00000000..f779bc6a --- /dev/null +++ b/crates/oxide_vm/tests/eval_tests.rs @@ -0,0 +1,152 @@ +use oxide_compiler::compiler::Compiler; +use oxide_types::object::JsObject; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(vm: &mut Vm, source: &str) -> Result { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?; + let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?; + vm.run(&module) +} + +/// 数值断言:整数运算保 int,容忍 int/double 两种表示。 +fn assert_num(result: JsValue, expected: f64) { + let actual = if result.is_int() { result.as_int() as f64 } else { result.as_double() }; + assert!((actual - expected).abs() < 0.0001, "expected {expected}, got {actual}"); +} + +#[test] +fn eval_string_expression_completion_value() { + let mut vm = Vm::new(); + // 档 1 函数模式:body 包装为语句体匿名函数,隐式返回 undefined,完成值不保留。 + // 档 2 脚本模式(create_dynamic_script)后 `eval('1+2')` 返回 3。 + let result = eval(&mut vm, "eval('1+2')").unwrap(); + assert_eq!(result, JsValue::undefined()); +} + +#[test] +fn eval_string_number_completion_value() { + let mut vm = Vm::new(); + // 同档 1 函数模式上限:完成值不保留。 + let result = eval(&mut vm, "eval('42')").unwrap(); + assert_eq!(result, JsValue::undefined()); +} + +#[test] +fn eval_string_var_declaration_inside() { + let mut vm = Vm::new(); + // 档 1:eval 内 var 声明只存在于匿名函数作用域,完成值不保留、不泄漏外层。 + let result = eval(&mut vm, "eval('var y = 1; y')").unwrap(); + assert_eq!(result, JsValue::undefined()); +} + +#[test] +fn eval_non_string_returns_as_is() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "eval(123)").unwrap(); + assert_num(result, 123.0); +} + +#[test] +fn eval_object_identity() { + let mut vm = Vm::new(); + // 非字符串实参原样返回:同一对象引用。 + let result = eval(&mut vm, "var o = {}; eval(o) === o").unwrap(); + assert_eq!(result, JsValue::bool(true)); +} + +#[test] +fn eval_new_string_not_tostring() { + let mut vm = Vm::new(); + // new String 是非字符串对象:不 ToString,原样返回同一对象(非原始值)。 + let result = eval(&mut vm, "var s = new String('1+1'); eval(s) === s").unwrap(); + assert_eq!(result, JsValue::bool(true)); +} + +#[test] +fn eval_throw_primitive_rethrown() { + let mut vm = Vm::new(); + // eval 内 throw 1 重抛原始值 1,可被外层 catch 捕获(非 Error 包装)。 + let result = eval(&mut vm, "try { eval('throw 1') } catch(e) { e }").unwrap(); + assert_num(result, 1.0); +} + +#[test] +fn eval_syntax_error_throws() { + let mut vm = Vm::new(); + // 换行分隔的 `x` 与 `++` 为非法语法,eval 抛 SyntaxError。 + let result = eval(&mut vm, "try { eval('x\\u000A++') } catch(e) { e.name }").unwrap(); + let rendered = vm.lookup_str(result).unwrap_or_default(); + assert_eq!(rendered, "SyntaxError"); +} + +#[test] +fn eval_length_is_one() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "eval.length").unwrap(); + assert_num(result, 1.0); +} + +#[test] +fn eval_name_is_eval() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "eval.name").unwrap(); + let rendered = vm.lookup_str(result).unwrap_or_default(); + assert_eq!(rendered, "eval"); +} + +#[test] +fn eval_global_descriptor() { + let mut vm = Vm::new(); + // 全局 eval 属性描述符:{writable:true, enumerable:false, configurable:true}。 + let result = eval( + &mut vm, + "var d = Object.getOwnPropertyDescriptor(this, 'eval'); \ + d.writable && !d.enumerable && d.configurable", + ) + .unwrap(); + assert_eq!(result, JsValue::bool(true)); +} + +#[test] +fn eval_new_throws_type_error() { + let mut vm = Vm::new(); + // eval 不是构造器:new eval() 抛 TypeError。 + let result = eval(&mut vm, "try { new eval() } catch(e) { e.name }").unwrap(); + let rendered = vm.lookup_str(result).unwrap_or_default(); + assert_eq!(rendered, "TypeError"); +} + +#[test] +fn eval_no_arg_and_undefined_are_undefined() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "eval() === undefined && eval(undefined) === undefined").unwrap(); + assert_eq!(result, JsValue::bool(true)); +} + +#[test] +fn eval_typeof_is_function() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "typeof eval").unwrap(); + let rendered = vm.lookup_str(result).unwrap_or_default(); + assert_eq!(rendered, "function"); +} + +#[test] +fn eval_survives_full_reset_rebuild() { + let mut vm = Vm::new(); + // 仅 global 脏:full_reset 走 bind_global_functions 重建路径,eval 须保留。 + let g_ptr = vm.session().global_object().as_ptr() as *mut JsObject; + unsafe { (&mut *g_ptr).bump_generation() }; + vm.full_reset(); + + let result = eval(&mut vm, "typeof eval").unwrap(); + let rendered = vm.lookup_str(result).unwrap_or_default(); + assert_eq!(rendered, "function"); + let result = eval(&mut vm, "eval.length === 1").unwrap(); + assert_eq!(result, JsValue::bool(true)); + let result = eval(&mut vm, "eval(123)").unwrap(); + assert_num(result, 123.0); + assert!(!vm.session().is_dirty_since_snapshot()); +}