feat: eval 全局注册与最小执行(间接 eval)

This commit is contained in:
2026-08-16 22:25:33 +08:00
parent 04067a4fe5
commit c7b238e518
7 changed files with 253 additions and 1 deletions
+44
View File
@@ -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<H: VmHost>(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)),
}
}
+2
View File
@@ -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)。
+1
View File
@@ -83,6 +83,7 @@ const BUILTIN_GLOBALS: &[&str] = &[
"SyntaxError",
"URIError",
"EvalError",
"eval",
"SuppressedError",
"DisposableStack",
"AsyncDisposableStack",
+52 -1
View File
@@ -468,6 +468,54 @@ fn is_skipped(meta: &TestMeta) -> Option<String> {
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);
}
@@ -39,6 +39,7 @@ pub fn bind_global(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
("__moduleSeal", oxide_builtins::module::module_seal::<crate::vm::Vm> as *const (), 1),
("__moduleEval", oxide_builtins::module::module_eval::<crate::vm::Vm> as *const (), 1),
("__moduleData", oxide_builtins::module::module_data::<crate::vm::Vm> as *const (), 2),
("eval", oxide_builtins::eval::eval::<crate::vm::Vm> as *const (), 1),
],
);
}
+1
View File
@@ -642,6 +642,7 @@ fn bind_global_functions(core: &Arc<KernelCore>, session: &KernelSession, global
oxide_builtins::global::decode_uri_component::<crate::vm::Vm> as *const (),
1,
),
("eval", oxide_builtins::eval::eval::<crate::vm::Vm> as *const (), 1),
],
);
}
+152
View File
@@ -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<JsValue, String> {
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());
}