diff --git a/crates/oxide_compiler/src/opcode.rs b/crates/oxide_compiler/src/opcode.rs index ef368245..f06cd07c 100644 --- a/crates/oxide_compiler/src/opcode.rs +++ b/crates/oxide_compiler/src/opcode.rs @@ -118,6 +118,22 @@ impl OpCode { | OpCode::JOIN ) } + + pub fn has_ic_ext_words(&self) -> bool { + matches!( + self, + OpCode::IC_GET_PROP + | OpCode::IC_SET_PROP + | OpCode::MEMBER_INC + | OpCode::MEMBER_DEC + | OpCode::COMPOUND_MEMBER_ADD + | OpCode::COMPOUND_MEMBER_SUB + | OpCode::COMPOUND_MEMBER_MUL + | OpCode::COMPOUND_MEMBER_DIV + | OpCode::COMPOUND_MEMBER_MOD + | OpCode::COMPOUND_MEMBER_EXP + ) + } } impl TryFrom for OpCode { diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 4ee63f03..78fc4718 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -864,9 +864,27 @@ impl Vm { self.regs = [JsValue::undefined(); 256]; self.frames.clear(); self.for_in_iters.clear(); + self.clear_ic_caches(); self.dispatch() } + fn clear_ic_caches(&mut self) { + let mut i = 0; + while i < self.bytecode.len() { + let op = opcode::opcode(self.bytecode[i]); + if op.has_ic_ext_words() { + if i + 3 < self.bytecode.len() { + self.bytecode[i + 1] = 0; + self.bytecode[i + 2] = 0; + self.bytecode[i + 3] = 0; + } + i += 4; + } else { + i += 1; + } + } + } + pub fn run(&mut self, module: &CompiledModule) -> Result { self.constants = self.convert_constants(module); self.sub_modules = module.sub_modules.clone(); diff --git a/crates/oxide_vm/tests/regression_tests.rs b/crates/oxide_vm/tests/regression_tests.rs index e51c5ae1..6414396b 100644 --- a/crates/oxide_vm/tests/regression_tests.rs +++ b/crates/oxide_vm/tests/regression_tests.rs @@ -30,10 +30,29 @@ fn regression_to_boolean_empty_string() { } #[test] -fn regression_continue_in_for() { +fn regression_rerun_clears_ic_cache() { + let allocator = Allocator::default(); + let source = "var o = {a: 1}; o.a"; + let program = oxide_parser::parse(&allocator, source).expect("parse"); + let module = Compiler::new().compile(&program).expect("compile"); + let mut vm = Vm::new(); + assert_eq!(format!("{}", vm.run(&module).unwrap()), "1"); + + let source2 = "var o = {a: 2}; o.a"; + let program2 = oxide_parser::parse(&allocator, source2).expect("parse"); + let module2 = Compiler::new().compile(&program2).expect("compile"); + vm.run(&module2).unwrap(); assert_eq!( - eval("var r=0; for(i=0;i<3;i=i+1){if(i==1)continue;r=r+i;}r"), + format!("{}", vm.rerun().unwrap()), "2", - "continue should skip iteration body" + "rerun should re-read IC-cached property, not stale value from previous run" + ); +} + +#[test] +fn regression_recursion_depth_limit() { + assert_eq!( + eval("function f(){f()} f()"), + "vm error: RangeError: Maximum call stack size exceeded" ); }