From 23e2a105e9b705bd4a4883f5109b6bd1b2aa679a 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: Mon, 8 Jun 2026 23:25:54 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=98=BE=E5=BC=8F=E4=BC=A0=E6=92=AD=20R?= =?UTF-8?q?egExp=20=E5=B8=B8=E9=87=8F=E5=88=9D=E5=A7=8B=E5=8C=96=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/vm.rs | 211 ++++++++++++++++++++++++-------------- 1 file changed, 132 insertions(+), 79 deletions(-) diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index dd66d2c..d345b9b 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -338,60 +338,84 @@ impl Vm { JsValue::object(obj_ptr as *mut u8) } - /// Convert a CompiledModule's constants into JsValue vec. - fn convert_constants(&mut self, module: &CompiledModule) -> Vec { - module - .constants - .iter() - .map(|c| match c { - Constant::Number(v) => JsValue::float(*v), - Constant::Int(v) => JsValue::int(*v), - Constant::String(s) => self.intern(s), - Constant::Boolean(b) => JsValue::bool(*b), - Constant::Null => JsValue::null(), - Constant::Undefined => JsValue::undefined(), - Constant::BytecodeFunc(idx) => { - // 1-indexed: idx 1 = sub_modules[0], idx 0 = sentinel - let sub_idx = *idx as usize; - let is_arrow = if sub_idx > 0 && sub_idx <= module.sub_modules.len() { - module.sub_modules[sub_idx - 1].is_arrow - } else { - false - }; - self.create_function_object(*idx, is_arrow) - } - Constant::RegExp(pattern, flags) => { - let pat_si = self.kernel.string_forge().intern(pattern).0; - let flags_si = self.kernel.string_forge().intern(flags).0; - let pat_val = JsValue::string(pat_si, 0); - let flags_val = JsValue::string(flags_si, 0); + fn error_text(&self, val: JsValue) -> String { + if let Some(s) = self.lookup_str(val) { + return s; + } + if val.is_object() { + let obj = unsafe { &*val.as_js_object_ptr() }; + let name_si = self.kernel.string_forge().intern("name").0; + let message_si = self.kernel.string_forge().intern("message").0; + let name = self + .resolve_property(obj, name_si) + .and_then(|v| self.lookup_str(v)) + .unwrap_or_else(|| "Error".to_string()); + let message = self + .resolve_property(obj, message_si) + .and_then(|v| self.lookup_str(v)) + .unwrap_or_default(); + return if message.is_empty() { + name + } else { + format!("{name}: {message}") + }; + } + format!("{val}") + } - let native_fn = - self.kernel.builtin_world().regexp_constructor.as_ptr() as *mut JsObject; - let ctor = unsafe { &*native_fn }; - let ctor_fn = ctor.native_fn(); - if ctor_fn.is_none() { - return JsValue::undefined(); - } - let saved_0 = self.regs[0]; - let saved_1 = self.regs[1]; - let saved_2 = self.regs[2]; - self.regs[0] = JsValue::undefined(); - self.regs[1] = pat_val; - self.regs[2] = flags_val; - let func: crate::native::NativeFn = - unsafe { std::mem::transmute(ctor_fn.unwrap()) }; - let result = func(self, &[0, 1, 2]); - self.regs[0] = saved_0; - self.regs[1] = saved_1; - self.regs[2] = saved_2; - match result { - Ok(val) => val, - Err(_) => JsValue::undefined(), - } - } - }) - .collect() + fn convert_constant(&mut self, constant: &Constant) -> Result { + match constant { + Constant::Number(v) => Ok(JsValue::float(*v)), + Constant::Int(v) => Ok(JsValue::int(*v)), + Constant::String(s) => Ok(self.intern(s)), + Constant::Boolean(b) => Ok(JsValue::bool(*b)), + Constant::Null => Ok(JsValue::null()), + Constant::Undefined => Ok(JsValue::undefined()), + Constant::BytecodeFunc(idx) => { + let sub_idx = *idx as usize; + let is_arrow = if sub_idx > 0 && sub_idx <= self.sub_modules.len() { + self.sub_modules[sub_idx - 1].is_arrow + } else { + false + }; + Ok(self.create_function_object(*idx, is_arrow)) + } + Constant::RegExp(pattern, flags) => { + let pat_si = self.kernel.string_forge().intern(pattern).0; + let flags_si = self.kernel.string_forge().intern(flags).0; + let pat_val = JsValue::string(pat_si, 0); + let flags_val = JsValue::string(flags_si, 0); + + let ctor_ptr = + self.kernel.builtin_world().regexp_constructor.as_ptr() as *mut JsObject; + let ctor = unsafe { &*ctor_ptr }; + let Some(native_fn) = ctor.native_fn() else { + return Err("SyntaxError: RegExp constructor unavailable".into()); + }; + + let saved_0 = self.regs[0]; + let saved_1 = self.regs[1]; + let saved_2 = self.regs[2]; + self.regs[0] = JsValue::undefined(); + self.regs[1] = pat_val; + self.regs[2] = flags_val; + let func: crate::native::NativeFn = unsafe { std::mem::transmute(native_fn) }; + let result = func(self, &[0, 1, 2]); + self.regs[0] = saved_0; + self.regs[1] = saved_1; + self.regs[2] = saved_2; + result.map_err(|err| self.error_text(err)) + } + } + } + + /// Convert a module constant pool into runtime values. + fn convert_constants(&mut self, constants: &[Constant]) -> Result, String> { + let mut values = Vec::with_capacity(constants.len()); + for constant in constants { + values.push(self.convert_constant(constant)?); + } + Ok(values) } pub fn lookup_str(&self, val: JsValue) -> Option { @@ -547,8 +571,8 @@ impl Vm { pub fn run(&mut self, module: &CompiledModule) -> Result { self.clear_execution_state(); - self.constants = self.convert_constants(module); self.sub_modules = module.sub_modules.clone(); + self.constants = self.convert_constants(&module.constants)?; self.sub_module_constants = vec![Vec::new(); self.sub_modules.len()]; self.bytecode = module.bytecode.clone(); self.root_reg_limit = module.n_registers.max(1); @@ -804,6 +828,9 @@ impl Vm { }; self.regs[255] = JsValue::undefined(); + let converted_sub_constants = + self.convert_constants(&sub_constants)?; + // Save current bytecode/constants self.saved_bytecode_stack .push(std::mem::take(&mut self.bytecode)); @@ -824,31 +851,8 @@ impl Vm { saved_new_target, }); - // Convert sub_module constants self.bytecode = sub_bytecode; - self.constants = sub_constants - .iter() - .map(|c| match c { - Constant::Number(v) => JsValue::float(*v), - Constant::Int(v) => JsValue::int(*v), - Constant::String(s) => self.intern(s), - Constant::Boolean(b) => JsValue::bool(*b), - Constant::Null => JsValue::null(), - Constant::Undefined => JsValue::undefined(), - Constant::BytecodeFunc(idx) => { - let idx_usize = *idx as usize; - let nested_is_arrow = if idx_usize > 0 - && idx_usize <= self.sub_modules.len() - { - self.sub_modules[idx_usize - 1].is_arrow - } else { - false - }; - self.create_function_object(*idx, nested_is_arrow) - } - Constant::RegExp(_, _) => JsValue::undefined(), - }) - .collect(); + self.constants = converted_sub_constants; // Pre-fill builtin registers from the global object // (same as Vm::run does for the main module). @@ -1883,7 +1887,7 @@ impl Default for Vm { #[cfg(test)] mod tests { use super::{opcode, JsValue, TryHandler, Vm}; - use oxide_compiler::module::CompiledModule; + use oxide_compiler::module::{CompiledModule, Constant}; #[test] fn reset_clears_runtime_state_like_rerun() { @@ -1963,4 +1967,53 @@ mod tests { assert_eq!(err, expected); } } + + #[test] + fn invalid_regexp_constant_fails_explicitly() { + let module = CompiledModule { + constants: vec![Constant::RegExp("[".into(), "".into())], + bytecode: vec![opcode::encode(opcode::OpCode::HALT, 0, 0, 0)], + n_registers: 1, + ..CompiledModule::new() + }; + let mut vm = Vm::new(); + let err = vm + .run(&module) + .expect_err("invalid RegExp constant should fail explicitly"); + assert!( + err.contains("SyntaxError: Invalid regular expression"), + "unexpected error: {err}" + ); + } + + #[test] + fn invalid_regexp_constant_in_submodule_fails_explicitly() { + let submodule = CompiledModule { + constants: vec![Constant::RegExp("[".into(), "".into())], + bytecode: vec![opcode::encode(opcode::OpCode::RETURN, 0, 0, 0)], + n_registers: 1, + ..CompiledModule::new() + }; + let module = CompiledModule { + constants: vec![Constant::BytecodeFunc(1), Constant::Undefined], + bytecode: vec![ + opcode::encode(opcode::OpCode::LOAD_CONST, 0, 0, 0), + opcode::encode(opcode::OpCode::LOAD_CONST, 1, 1, 0), + opcode::encode(opcode::OpCode::CALL, 0, 1, 0), + 0, + opcode::encode(opcode::OpCode::HALT, 0, 0, 0), + ], + n_registers: 2, + sub_modules: vec![submodule], + ..CompiledModule::new() + }; + let mut vm = Vm::new(); + let err = vm + .run(&module) + .expect_err("invalid submodule RegExp constant should fail explicitly"); + assert!( + err.contains("SyntaxError: Invalid regular expression"), + "unexpected error: {err}" + ); + } }