diff --git a/crates/wasmtime/src/runtime/externals/table.rs b/crates/wasmtime/src/runtime/externals/table.rs index 752570d354e1..0e7bec42d0c1 100644 --- a/crates/wasmtime/src/runtime/externals/table.rs +++ b/crates/wasmtime/src/runtime/externals/table.rs @@ -430,22 +430,26 @@ impl Table { destination table's element type", )?; + let mut copy_elem = |src: u64, dst: u64| -> Result<()> { + let scope = store.enter_gc_lifo_scope(); + let result = match src_table.get(&mut *store, src) { + Some(val) => dst_table.set(&mut *store, dst, val), + None => Err(Trap::TableOutOfBounds.into()), + }; + store.exit_gc_lifo_scope(scope); + result + }; + // Do a forwards or backwards copy depending on the indices involved to // ensure that elements that are part of the copy aren't accidentally // clobbered. if dst_index < src_index { for (src, dst) in src_range.zip(dst_range) { - let val = src_table - .get(&mut *store, src) - .ok_or(Trap::TableOutOfBounds)?; - dst_table.set(&mut *store, dst, val)?; + copy_elem(src, dst)?; } } else { for (src, dst) in src_range.rev().zip(dst_range.rev()) { - let val = src_table - .get(&mut *store, src) - .ok_or(Trap::TableOutOfBounds)?; - dst_table.set(&mut *store, dst, val)?; + copy_elem(src, dst)?; } } Ok(()) diff --git a/tests/all/gc.rs b/tests/all/gc.rs index 3b27a5d09493..0cc08f7c029f 100644 --- a/tests/all/gc.rs +++ b/tests/all/gc.rs @@ -883,6 +883,34 @@ fn table_copy_doesnt_leak() -> Result<()> { Ok(()) } +#[test] +#[cfg_attr(miri, ignore)] +fn table_copy_without_root_scope_doesnt_leak() -> Result<()> { + let _ = env_logger::try_init(); + + let mut store = Store::<()>::default(); + let flag = Arc::new(AtomicBool::new(false)); + + let table = Table::new( + &mut store, + TableType::new(RefType::EXTERNREF, 10, Some(10))?, + Ref::Extern(None), + )?; + + { + let mut scope = RootScope::new(&mut store); + let x = ExternRef::new(&mut scope, SetFlagOnDrop(flag.clone()))?; + table.fill(&mut scope, 2, x.into(), 3)?; + } + + Table::copy(&mut store, &table, 5, &table, 2, 3)?; + table.fill(&mut store, 0, Ref::Extern(None), 10)?; + + store.gc(None)?; + assert!(flag.load(SeqCst)); + Ok(()) +} + #[test] #[cfg_attr(miri, ignore)] fn table_set_doesnt_leak() -> Result<()> {