feat: eval 全局注册与最小执行(间接 eval)
This commit is contained in:
@@ -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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,8 @@ pub mod date;
|
|||||||
pub mod disposable_stack;
|
pub mod disposable_stack;
|
||||||
/// Error 内置对象实现(各类 Error 构造函数与 stack/toString)。
|
/// Error 内置对象实现(各类 Error 构造函数与 stack/toString)。
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
/// eval 全局函数实现(动态编译执行脚本/表达式)。
|
||||||
|
pub mod eval;
|
||||||
/// Function 内置对象实现(call/apply/bind/toString)。
|
/// Function 内置对象实现(call/apply/bind/toString)。
|
||||||
pub mod function;
|
pub mod function;
|
||||||
/// 全局函数模块(URI 编解码与 Annex B 的 escape/unescape)。
|
/// 全局函数模块(URI 编解码与 Annex B 的 escape/unescape)。
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ const BUILTIN_GLOBALS: &[&str] = &[
|
|||||||
"SyntaxError",
|
"SyntaxError",
|
||||||
"URIError",
|
"URIError",
|
||||||
"EvalError",
|
"EvalError",
|
||||||
|
"eval",
|
||||||
"SuppressedError",
|
"SuppressedError",
|
||||||
"DisposableStack",
|
"DisposableStack",
|
||||||
"AsyncDisposableStack",
|
"AsyncDisposableStack",
|
||||||
|
|||||||
@@ -468,6 +468,54 @@ fn is_skipped(meta: &TestMeta) -> Option<String> {
|
|||||||
None
|
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 记为失败。
|
/// 在 catch_unwind 保护下运行单个测试,把引擎 panic 记为失败。
|
||||||
#[expect(clippy::too_many_arguments)]
|
#[expect(clippy::too_many_arguments)]
|
||||||
fn run_test(
|
fn run_test(
|
||||||
@@ -1262,9 +1310,12 @@ fn process_path(
|
|||||||
|
|
||||||
if !no_skip {
|
if !no_skip {
|
||||||
let path_str = path.to_string_lossy().replace('\\', "/");
|
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());
|
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) {
|
if let Some(reason) = is_skipped(&meta) {
|
||||||
return TestResult::skip(path.to_path_buf(), reason);
|
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),
|
("__moduleSeal", oxide_builtins::module::module_seal::<crate::vm::Vm> as *const (), 1),
|
||||||
("__moduleEval", oxide_builtins::module::module_eval::<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),
|
("__moduleData", oxide_builtins::module::module_data::<crate::vm::Vm> as *const (), 2),
|
||||||
|
("eval", oxide_builtins::eval::eval::<crate::vm::Vm> as *const (), 1),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (),
|
oxide_builtins::global::decode_uri_component::<crate::vm::Vm> as *const (),
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
|
("eval", oxide_builtins::eval::eval::<crate::vm::Vm> as *const (), 1),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user