From a82e4cd2044c20975cda9b409e326ff4b73acbcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Sat, 8 Aug 2026 03:07:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Function=20=E6=9E=84=E9=80=A0=E5=99=A8?= =?UTF-8?q?=EF=BC=88=E5=8A=A8=E6=80=81=E7=BC=96=E8=AF=91=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2=E7=94=9F=E6=88=90=E5=87=BD=E6=95=B0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/function.rs | 43 +++++++- crates/oxide_runtime_api/src/lib.rs | 3 + crates/oxide_vm/Cargo.toml | 4 +- crates/oxide_vm/src/bindings/bind_function.rs | 17 ++- crates/oxide_vm/src/vm.rs | 85 ++++++++++++++ crates/oxide_vm/src/vm_support.rs | 104 ++++++++++++++++++ 6 files changed, 252 insertions(+), 4 deletions(-) diff --git a/crates/oxide_builtins/src/function.rs b/crates/oxide_builtins/src/function.rs index 7d1c0ce..42623cf 100644 --- a/crates/oxide_builtins/src/function.rs +++ b/crates/oxide_builtins/src/function.rs @@ -2,7 +2,7 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, NativeFnPtr}; use oxide_types::value::JsValue; -use oxide_runtime_api::{NativeResult, VmHost}; +use oxide_runtime_api::{to_string_full, NativeResult, VmHost}; fn invoke_target(vm: &mut H, target_val: JsValue, this_val: JsValue, arg_regs: &[u8]) -> NativeResult { let args: Vec = arg_regs.iter().map(|&r| vm.reg(r)).collect(); @@ -13,6 +13,47 @@ fn invoke_target(vm: &mut H, target_val: JsValue, this_val: JsValue, } } +/// `Function(...)` / `new Function(...)` 构造器:动态编译一个匿名函数。 +/// +/// 除最后一个实参为函数体外,其余实参为形参名;无实参时函数体为空串。 +/// 编译成功返回函数对象,语法错误抛 SyntaxError,实参 ToString 失败抛 TypeError。 +pub fn function_constructor(vm: &mut H, args: &[u8]) -> NativeResult { + let arg_regs = &args[1..]; + let (param_regs, body_reg) = if arg_regs.is_empty() { + (vec![], None) + } else { + let (head, tail) = arg_regs.split_at(arg_regs.len() - 1); + (head.to_vec(), Some(tail[0])) + }; + + let mut params = Vec::with_capacity(param_regs.len()); + for &r in ¶m_regs { + match to_string_full(vm.reg(r), vm) { + Ok(s) => params.push(s), + Err(e) => return NativeResult::Err(to_string_error_value(vm, &e)), + } + } + let body = match body_reg { + Some(r) => match to_string_full(vm.reg(r), vm) { + Ok(s) => s, + Err(e) => return NativeResult::Err(to_string_error_value(vm, &e)), + }, + None => String::new(), + }; + + match vm.create_dynamic_function(¶ms, &body) { + Ok(func) => NativeResult::Ok(func), + Err(msg) => NativeResult::Err(crate::error::create_syntax_error(vm, &msg)), + } +} + +/// 把 ToString 强制转换错误转成可抛异常值:用户回调抛出的原始值优先原样传播 +/// (经 `last_uncaught_value` 侧通道),否则按普通 TypeError 处理。 +fn to_string_error_value(vm: &mut H, msg: &str) -> JsValue { + vm.take_uncaught_value() + .unwrap_or_else(|| crate::error::create_type_error(vm, msg)) +} + fn bind_dispatcher(vm: &mut H, args: &[u8]) -> NativeResult { let wrapper_val = vm.reg(254); let wrapper = unsafe { &*wrapper_val.as_js_object_ptr() }; diff --git a/crates/oxide_runtime_api/src/lib.rs b/crates/oxide_runtime_api/src/lib.rs index 91d8091..716d673 100644 --- a/crates/oxide_runtime_api/src/lib.rs +++ b/crates/oxide_runtime_api/src/lib.rs @@ -117,6 +117,9 @@ pub trait VmHost { fn step_rng(&mut self); fn math_rng_value(&self) -> f64; fn sub_module_function_name(&self, sub_idx: u16) -> String; + /// 动态编译一个函数体(`Function` 构造器用):把参数列表与函数体编译为可调用 + /// 函数对象。编译或解析失败返回 `Err`,由调用方转为 `SyntaxError`。 + fn create_dynamic_function(&mut self, params: &[String], body: &str) -> Result; fn symbol_intern(&mut self, desc: String) -> u32; fn symbol_description(&self, idx: u32) -> Option<&str>; fn symbol_lookup_global(&self, key: &str) -> Option; diff --git a/crates/oxide_vm/Cargo.toml b/crates/oxide_vm/Cargo.toml index a0ec935..16f6c1f 100644 --- a/crates/oxide_vm/Cargo.toml +++ b/crates/oxide_vm/Cargo.toml @@ -13,6 +13,8 @@ oxide_runtime_api = { path = "../oxide_runtime_api" } oxide_builtins = { path = "../oxide_builtins" } oxide_types = { path = "../oxide_types" } oxide_log = { path = "../oxide_log" } +oxide_compiler = { path = "../oxide_compiler" } +oxide_parser = { path = "../oxide_parser" } bumpalo.workspace = true fast-float.workspace = true ryu.workspace = true @@ -26,9 +28,7 @@ unicode-normalization.workspace = true rustc-hash.workspace = true [dev-dependencies] -oxide_compiler = { path = "../oxide_compiler" } proptest.workspace = true -oxide_parser = { path = "../oxide_parser" } oxide_ir = { path = "../oxide_ir" } criterion = { version = "0.5", features = ["html_reports"] } diff --git a/crates/oxide_vm/src/bindings/bind_function.rs b/crates/oxide_vm/src/bindings/bind_function.rs index 22a8562..a8e6a07 100644 --- a/crates/oxide_vm/src/bindings/bind_function.rs +++ b/crates/oxide_vm/src/bindings/bind_function.rs @@ -2,10 +2,11 @@ use std::sync::Arc; use oxide_kernel::builtin::FunctionMethods; use oxide_kernel::kernel::{KernelCore, KernelSession}; -use oxide_types::object::JsObject; +use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::value::JsValue; use super::bind_global_value; +use super::configure_native_constructor; /// 把 Function 构造器与原型方法绑定到 global。 pub fn bind_function(core: &Arc, session: &KernelSession, global: &mut JsObject) { @@ -22,5 +23,19 @@ pub fn bind_function(core: &Arc, session: &KernelSession, global: &m ); let function_ctor = session.builtin_world().function_constructor.as_ptr() as *mut JsObject; + configure_native_constructor( + unsafe { &mut *function_ctor }, + oxide_builtins::function::function_constructor:: as *const (), + 1, + ); + // 设置 Function.length = 1(configure_native_constructor 不写 length 属性), + // 属性为不可写不可枚举可配置。 + let length_si = core.perm_interner().intern("length").0; + let length_shape = core.shape_forge().make_shape(unsafe { &*function_ctor }.shape_id(), length_si); + let ctor = unsafe { &mut *function_ctor }; + ctor.set_shape_id(length_shape); + ctor.ensure_hash_props().push(JsValue::int(1)); + let length_pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true)); bind_global_value(core, global, "Function", JsValue::from_js_object(function_ctor)); } diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index a068e25..7856ae3 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1475,6 +1475,9 @@ impl oxide_runtime_api::VmHost for Vm { .and_then(|m| m.function_name.clone()) .unwrap_or_default() } + fn create_dynamic_function(&mut self, params: &[String], body: &str) -> Result { + self.create_dynamic_function(params, body) + } fn symbol_intern(&mut self, desc: String) -> u32 { self.symbols.intern(desc) } @@ -1492,6 +1495,88 @@ impl oxide_runtime_api::VmHost for Vm { } } +impl Vm { + /// 动态编译函数(`Function` 构造器路径):把参数列表与函数体 wrap 成匿名函数 + /// 源码,走完整编译链后取匿名函数模块追加进 VM 平表,返回对应函数对象。 + /// + /// # 步骤 + /// 1. wrap 源码 `function anonymous(p...) { body }`,parse + compile。 + /// 2. 取 `sub_modules[0]`(匿名函数模块),把其子树 flat_id 重编号到平表末尾 + /// 并重写子树内每条 `CREATE_CLOSURE` 的 imm16。 + /// 3. 同步 resize `immutables_cache`,建函数对象并设置 name/length。 + /// + /// # 边界与前提 + /// - 编译或解析失败返回 `Err`(由 builtin 层转 SyntaxError)。 + /// - 追加的子树原 flat_id 自 1 连续(flatten 后 1=匿名体,2…=其嵌套函数); + /// 新 id = 平表长度 + (old - 1)。 + /// - 动态函数只在本次 `run()` 内有效:下次 run 重建平表,跨 run 引用会越界。 + /// + /// # 副作用 + /// - 修改 `self.sub_modules` 与 `self.immutables_cache`。 + pub fn create_dynamic_function(&mut self, params: &[String], body: &str) -> Result { + // wrap 源码:末尾换行防止 body 以行注释结尾吞掉右花括号。 + let params_str = params.join(", "); + let source = format!("function anonymous({params_str}) {{\n{body}\n}}"); + + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, &source) + .map_err(|errs| errs.into_iter().map(|e| e.message).collect::>().join("\n"))?; + let mut module = oxide_compiler::compiler::Compiler::new().compile(&program)?; + let anonymous = module.sub_modules.remove(0); + // 形参数以编译结果为准:单个实参 "a,b,c" 拼接后解析为 3 个形参 + // (ES 动态函数把非末位实参以逗号连接成参数串再解析)。 + let formal_count = anonymous.n_args as i32; + + // 子树重编号 + 追加:base = 当前平表长度,DFS 前序压入,push 序即新 flat_id。 + let base = self.sub_modules.len() as u32; + let mut added = Vec::new(); + rehome_subtree(&anonymous, base, &mut added); + Arc::make_mut(&mut self.sub_modules).extend(added); + // 平表变长后同步扩容常量缓存,否则激活新模块常量时越界 panic。 + self.immutables_cache + .extend((0..self.sub_modules.len().saturating_sub(self.immutables_cache.len())).map(|_| OnceLock::new())); + + let func_val = self.create_function_object(base, false, false, false, false); + let func_obj = unsafe { &mut *func_val.as_js_object_ptr() }; + let length_si = self.kernel_core.perm_interner().intern("length").0; + let name_si = self.kernel_core.perm_interner().intern("name").0; + // length/name 为不可写不可枚举可配置,且 length 先于 name(规范属性顺序)。 + let attrs = PropAttributes::new(false, false, true); + let length_val = JsValue::int(formal_count); + let name_val = self.new_string("anonymous"); + self.define_data_property(func_obj, length_si, length_val, attrs)?; + self.define_data_property(func_obj, name_si, name_val, attrs)?; + Ok(func_val) + } +} + +/// 把 flatten 后的子模块子树重编号到平表偏移 `base`:DFS 前序拷贝进 `out`, +/// 新 flat_id = base + (old - 1),子树内每条 `CREATE_CLOSURE` 的 imm16 同步重写。 +/// 原子树 flat_id 自 1 连续,因此拷贝顺序即新 id 顺序,`out` 下标对齐平表槽位。 +fn rehome_subtree(module: &CompiledModule, base: u32, out: &mut Vec) { + let new_id = base + module.flat_id - 1; + let mut bytecode = module.bytecode.clone(); + for instr in &mut bytecode { + if opcode::opcode(*instr) == OpCode::CREATE_CLOSURE { + let old = opcode::imm16(*instr) as u32; + let new_flat = base + (old - 1); + *instr = opcode::encode( + OpCode::CREATE_CLOSURE, + opcode::rd(*instr), + (new_flat & 0xFF) as u8, + ((new_flat >> 8) & 0xFF) as u8, + ); + } + } + let mut rehomed = module.clone(); + rehomed.bytecode = bytecode; + rehomed.flat_id = new_id; + out.push(rehomed); + for sub in &module.sub_modules { + rehome_subtree(sub, base, out); + } +} + #[cfg(test)] mod tests { use super::{opcode, JsValue, TryHandler, Vm}; diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 2695187..5664278 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -497,6 +497,110 @@ mod tests { // 子模块常量改走 temp_immutables(避免缓存下标冲突) } + #[test] + fn dynamic_function_basic_arity() { + let mut vm = Vm::new(); + // 与等价静态函数返回值逐位一致(引擎函数调用统一产 Double 数值)。 + let expected = run_source(&mut vm, "function f(a,b){return a+b} f(3,4)"); + let result = run_source(&mut vm, "new Function('a','b','return a+b')(3,4)"); + assert_eq!(result, expected); + } + + #[test] + fn dynamic_function_called_without_new() { + let mut vm = Vm::new(); + let expected = run_source(&mut vm, "function f(a,b){return a*b} f(6,7)"); + let result = run_source(&mut vm, "Function('a','b','return a*b')(6,7)"); + assert_eq!(result, expected); + } + + #[test] + fn dynamic_function_empty_body_returns_undefined() { + let mut vm = Vm::new(); + assert!(run_source(&mut vm, "Function()()").is_undefined()); + } + + #[test] + fn dynamic_function_syntax_error_throws_syntax_error() { + let mut vm = Vm::new(); + let result = run_source(&mut vm, "try{new Function('return {{')}catch(e){e.name}"); + assert!(result.is_string()); + assert_eq!(vm.lookup_str(result).as_deref(), Some("SyntaxError")); + } + + #[test] + fn dynamic_function_nested_closure_renumbering() { + let mut vm = Vm::new(); + // 匿名函数体声明嵌套函数 g,返回值是引用 g 的闭包:验证子树 flat_id 重编号 + // 与 CREATE_CLOSURE imm16 重写后嵌套调用仍指向正确的子模块。 + let expected = run_source( + &mut vm, + "function outer(){var g=function(n){return n*2}; return function(){return g(21)}} outer()()", + ); + let result = run_source( + &mut vm, + "new Function('var g=function(n){return n*2}; return function(){return g(21)}')()()", + ); + assert_eq!(result, expected); + } + + #[test] + fn dynamic_function_multiple_in_one_run() { + let mut vm = Vm::new(); + // 同一 run 内连续创建多个动态函数:验证 base 偏移累计正确。 + let expected = run_source( + &mut vm, + "function f1(){return 1} function f2(){return 2} function f3(a){return a*3} f1()+f2()+f3(4)", + ); + let result = run_source( + &mut vm, + "new Function('return 1')() + new Function('return 2')() + new Function('a','return a*3')(4)", + ); + assert_eq!(result, expected); + } + + #[test] + fn dynamic_function_name_and_length() { + let mut vm = Vm::new(); + let name = run_source(&mut vm, "var f=new Function('a','b','return a'); f.name"); + assert!(name.is_string()); + assert_eq!(vm.lookup_str(name).as_deref(), Some("anonymous")); + let len = run_source(&mut vm, "var f=new Function('a','b','return a'); f.length"); + assert_eq!(len, JsValue::int(2)); + } + + #[test] + fn dynamic_function_comma_split_params_count() { + let mut vm = Vm::new(); + // 单个实参 "a,b,c" 拼接解析为 3 个形参,length 应为解析后的形参数。 + let result = run_source(&mut vm, "new Function('a,b,c','null').length"); + assert_eq!(result, JsValue::int(3)); + } + + #[test] + fn dynamic_function_name_and_length_attributes() { + let mut vm = Vm::new(); + // length/name 为不可写、不可枚举、可配置的数据属性。 + let attrs = run_source( + &mut vm, + "var d=Object.getOwnPropertyDescriptor(new Function('a','return a'),'length'); String(d.value)+d.writable+d.enumerable+d.configurable", + ); + assert_eq!(vm.lookup_str(attrs).as_deref(), Some("1falsefalsetrue")); + let name_attrs = run_source( + &mut vm, + "var d=Object.getOwnPropertyDescriptor(Function(),'name'); String(d.writable)+d.enumerable+d.configurable", + ); + assert_eq!(vm.lookup_str(name_attrs).as_deref(), Some("falsefalsetrue")); + } + + #[test] + fn dynamic_function_rethrows_to_string_exception() { + let mut vm = Vm::new(); + // 形参 ToString 回调抛出的原始值须原样传播,而非包成 TypeError。 + let result = run_source(&mut vm, "try{new Function({toString:function(){throw 7}})}catch(e){e}"); + assert_eq!(result, JsValue::int(7)); + } + #[test] fn session_epoch_reset_is_only_in_full_reset_state_clear() { let src = include_str!("vm_support.rs");