diff --git a/crates/js/src/host_tests.rs b/crates/js/src/host_tests.rs index 963f91776d..48aaa96106 100644 --- a/crates/js/src/host_tests.rs +++ b/crates/js/src/host_tests.rs @@ -292,6 +292,27 @@ fn a_marked_host_error_keeps_its_mark_only_while_no_catch_takes_it() { assert_eq!(r.error_mark(), None, "a later run that ends normally has no mark"); } +/// Marked errors that `finally` blocks discard with `return`, `break` or `continue` are collected +/// with the rest of the garbage, so a run that discards many of them stays within its heap. +#[test] +fn marked_errors_a_finally_block_discards_are_collected() { + let mut r = Realm::new(); + r.set_limits(Limits { fuel: 200_000_000, heap_bytes: 1 << 20, ..Limits::default() }); + let f: HostFunction = Rc::new(|_| Err(HostError::Marked { error: Box::new(HostError::Syntax("bad file".into())), mark: 7 })); + r.define_host_object("host", vec![("f".into(), HostItem::Function(f))]).unwrap(); + let cases = [ + "function d() { try { host.f(); } finally { return; } }\nfor (var i = 0; i < 20000; i++) d();", + "for (var i = 0; i < 20000; i++) { try { host.f(); } finally { continue; } }", + "for (var i = 0; i < 20000; i++) { do { try { host.f(); } finally { break; } } while (false); }", + ]; + for source in cases { + r.run(source).unwrap_or_else(|e| panic!("{source}: {e:?}")); + assert_eq!(r.error_mark(), None, "{source}"); + } + assert!(r.run("try { host.f(); } finally { 1; }").is_err()); + assert_eq!(r.error_mark(), Some(7)); +} + #[test] fn an_error_says_where_in_the_source_it_was_raised() { let mut r = realm(1_000_000); diff --git a/crates/js/src/interp.rs b/crates/js/src/interp.rs index 0e2f1e7ed9..e3cb102c72 100644 --- a/crates/js/src/interp.rs +++ b/crates/js/src/interp.rs @@ -201,10 +201,10 @@ pub(crate) struct Interp { /// propagating. pub(crate) thrown_mark: Option, /// The marked exceptions `finally` blocks are running for, innermost last, each with its mark, - /// which the block's end throws it on with. A block left by `break`, `continue` or `return` - /// leaves its entry until the run ends, so there are at most as many as the host errors the - /// run's host marked. - pub(crate) finally_marks: Vec<(ObjId, u32)>, + /// which the block's end throws it on with, and the stack slot that holds it while the block + /// runs. An entry whose slot no longer holds its exception belongs to a block that `break`, + /// `continue` or `return` left, and [`Self::drop_left_finally_marks`] removes it. + pub(crate) finally_marks: Vec<(ObjId, u32, usize)>, /// The mark of the host error the last run ended with. pub(crate) error_mark: Option, } @@ -560,12 +560,20 @@ impl Interp { Ok(()) } + /// Removes the [`Self::finally_marks`] entries of `finally` blocks that `break`, `continue` or + /// `return` left: those whose stack slot no longer holds their exception. + fn drop_left_finally_marks(&mut self) { + let stack = &self.stack; + self.finally_marks.retain(|&(id, _, slot)| matches!(stack.get(slot), Some(Value::Object(o)) if *o == id)); + } + pub(crate) fn collect_garbage(&mut self) { + self.drop_left_finally_marks(); let mut roots = Roots::default(); roots.values.extend(self.stack.iter()); roots.values.extend(self.temp_roots.iter()); roots.values.push(&self.last_thrown); - roots.objects.extend(self.finally_marks.iter().map(|&(id, _)| id)); + roots.objects.extend(self.finally_marks.iter().map(|&(id, _, _)| id)); for f in &self.frames { roots.values.push(&f.this); roots.values.push(&f.completion); @@ -668,8 +676,9 @@ impl Interp { return internal("handler stack deeper than the stack"); } self.stack.truncate(target_len); + self.drop_left_finally_marks(); if let (Some(mark), false, Value::Object(id)) = (self.thrown_mark.take(), caught, &v) { - self.finally_marks.push((*id, mark)); + self.finally_marks.push((*id, mark, target_len)); } self.stack.push(v); self.throw_site = None; @@ -859,9 +868,9 @@ impl Interp { if kind == 1.0 { let v = self.pop()?; if let Value::Object(o) = v - && let Some(i) = self.finally_marks.iter().rposition(|&(id, _)| id == o) + && let Some(i) = self.finally_marks.iter().rposition(|&(id, _, _)| id == o) { - self.thrown_mark = self.finally_marks.get(i).map(|&(_, mark)| mark); + self.thrown_mark = self.finally_marks.get(i).map(|&(_, mark, _)| mark); self.finally_marks.truncate(i); } return Err(Abrupt::Throw(v));