Skip to content

Commit 34537a3

Browse files
authored
Merge pull request #3734 from fitzgen/externref-debug-assertion-fix
Fix a debug assertion in `externref` garbage collections
2 parents 90bfa12 + 19f8d94 commit 34537a3

File tree

4 files changed

+144
-27
lines changed

4 files changed

+144
-27
lines changed

crates/fuzzing/src/generators/table_ops.rs

+127-25
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@
33
use arbitrary::Arbitrary;
44
use std::ops::Range;
55
use wasm_encoder::{
6-
CodeSection, EntityType, Export, ExportSection, Function, FunctionSection, ImportSection,
7-
Instruction, Module, TableSection, TableType, TypeSection, ValType,
6+
CodeSection, EntityType, Export, ExportSection, Function, FunctionSection, GlobalSection,
7+
ImportSection, Instruction, Module, TableSection, TableType, TypeSection, ValType,
88
};
99

1010
/// A description of a Wasm module that makes a series of `externref` table
1111
/// operations.
1212
#[derive(Arbitrary, Debug)]
1313
pub struct TableOps {
1414
num_params: u8,
15+
num_globals: u8,
1516
table_size: u32,
1617
ops: Vec<TableOp>,
1718
}
1819

1920
const NUM_PARAMS_RANGE: Range<u8> = 1..10;
21+
const NUM_GLOBALS_RANGE: Range<u8> = 1..10;
2022
const TABLE_SIZE_RANGE: Range<u32> = 1..100;
2123
const MAX_OPS: usize = 100;
2224

@@ -28,6 +30,13 @@ impl TableOps {
2830
num_params
2931
}
3032

33+
/// Get the number of globals this module has.
34+
pub fn num_globals(&self) -> u8 {
35+
let num_globals = std::cmp::max(self.num_globals, NUM_GLOBALS_RANGE.start);
36+
let num_globals = std::cmp::min(num_globals, NUM_GLOBALS_RANGE.end);
37+
num_globals
38+
}
39+
3140
/// Get the size of the table that this module uses.
3241
pub fn table_size(&self) -> u32 {
3342
let table_size = std::cmp::max(self.table_size, TABLE_SIZE_RANGE.start);
@@ -98,6 +107,18 @@ impl TableOps {
98107
maximum: None,
99108
});
100109

110+
// Define our globals.
111+
let mut globals = GlobalSection::new();
112+
for _ in 0..self.num_globals() {
113+
globals.global(
114+
wasm_encoder::GlobalType {
115+
val_type: wasm_encoder::ValType::ExternRef,
116+
mutable: true,
117+
},
118+
Instruction::RefNull(wasm_encoder::ValType::ExternRef),
119+
);
120+
}
121+
101122
// Define the "run" function export.
102123
let mut functions = FunctionSection::new();
103124
functions.function(1);
@@ -111,7 +132,12 @@ impl TableOps {
111132

112133
func.instruction(Instruction::Loop(wasm_encoder::BlockType::Empty));
113134
for op in self.ops.iter().take(MAX_OPS) {
114-
op.insert(&mut func, self.num_params() as u32, self.table_size());
135+
op.insert(
136+
&mut func,
137+
self.num_params() as u32,
138+
self.table_size(),
139+
self.num_globals() as u32,
140+
);
115141
}
116142
func.instruction(Instruction::Br(0));
117143
func.instruction(Instruction::End);
@@ -125,89 +151,118 @@ impl TableOps {
125151
.section(&imports)
126152
.section(&functions)
127153
.section(&tables)
154+
.section(&globals)
128155
.section(&exports)
129156
.section(&code);
130157

131158
module.finish()
132159
}
133160
}
134161

135-
#[derive(Arbitrary, Debug)]
162+
#[derive(Arbitrary, Copy, Clone, Debug)]
136163
pub(crate) enum TableOp {
137164
// `call $gc; drop; drop; drop;`
138165
Gc,
166+
139167
// `(drop (table.get x))`
140168
Get(i32),
169+
170+
// `(drop (global.get i))`
171+
GetGlobal(u32),
172+
141173
// `(table.set x (local.get y))`
142174
SetFromParam(i32, u32),
175+
143176
// `(table.set x (table.get y))`
144177
SetFromGet(i32, i32),
178+
145179
// `call $make_refs; table.set x; table.set y; table.set z`
146180
SetFromMake(i32, i32, i32),
181+
182+
// `(global.set x (local.get y))`
183+
SetGlobalFromParam(u32, u32),
184+
185+
// `(global.set x (table.get y))`
186+
SetGlobalFromGet(u32, i32),
187+
188+
// `call $make_refs; global.set x; global.set y; global.set z`
189+
SetGlobalFromMake(u32, u32, u32),
190+
147191
// `call $make_refs; drop; drop; drop;`
148192
Make,
193+
149194
// `local.get x; local.get y; local.get z; call $take_refs`
150195
TakeFromParams(u32, u32, u32),
196+
151197
// `table.get x; table.get y; table.get z; call $take_refs`
152198
TakeFromGet(i32, i32, i32),
199+
200+
// `global.get x; global.get y; global.get z; call $take_refs`
201+
TakeFromGlobalGet(u32, u32, u32),
202+
153203
// `call $make_refs; call $take_refs`
154204
TakeFromMake,
205+
155206
// `call $gc; call $take_refs`
156207
TakeFromGc,
157208
}
158209

159210
impl TableOp {
160-
fn insert(&self, func: &mut Function, num_params: u32, table_size: u32) {
211+
fn insert(self, func: &mut Function, num_params: u32, table_size: u32, num_globals: u32) {
161212
assert!(num_params > 0);
162213
assert!(table_size > 0);
163214

164215
// Add one to make sure that out of bounds table accesses are possible,
165216
// but still rare.
166217
let table_mod = table_size as i32 + 1;
167218

219+
let gc_func_idx = 0;
220+
let take_refs_func_idx = 1;
221+
let make_refs_func_idx = 2;
222+
168223
match self {
169224
Self::Gc => {
170-
func.instruction(Instruction::Call(0));
225+
func.instruction(Instruction::Call(gc_func_idx));
171226
func.instruction(Instruction::Drop);
172227
func.instruction(Instruction::Drop);
173228
func.instruction(Instruction::Drop);
174229
}
175230
Self::Get(x) => {
176-
func.instruction(Instruction::I32Const(*x % table_mod));
231+
func.instruction(Instruction::I32Const(x % table_mod));
177232
func.instruction(Instruction::TableGet { table: 0 });
178233
func.instruction(Instruction::Drop);
179234
}
180235
Self::SetFromParam(x, y) => {
181-
func.instruction(Instruction::I32Const(*x % table_mod));
182-
func.instruction(Instruction::LocalGet(*y % num_params));
236+
func.instruction(Instruction::I32Const(x % table_mod));
237+
func.instruction(Instruction::LocalGet(y % num_params));
183238
func.instruction(Instruction::TableSet { table: 0 });
184239
}
185240
Self::SetFromGet(x, y) => {
186-
func.instruction(Instruction::I32Const(*x % table_mod));
187-
func.instruction(Instruction::I32Const(*y % table_mod));
241+
func.instruction(Instruction::I32Const(x % table_mod));
242+
func.instruction(Instruction::I32Const(y % table_mod));
188243
func.instruction(Instruction::TableGet { table: 0 });
189244
func.instruction(Instruction::TableSet { table: 0 });
190245
}
191246
Self::SetFromMake(x, y, z) => {
192-
func.instruction(Instruction::Call(2));
247+
func.instruction(Instruction::Call(make_refs_func_idx));
193248

194249
func.instruction(Instruction::LocalSet(num_params));
195-
func.instruction(Instruction::I32Const(*x % table_mod));
250+
func.instruction(Instruction::I32Const(x % table_mod));
196251
func.instruction(Instruction::LocalGet(num_params));
197252
func.instruction(Instruction::TableSet { table: 0 });
198253

199254
func.instruction(Instruction::LocalSet(num_params));
200-
func.instruction(Instruction::I32Const(*y % table_mod));
255+
func.instruction(Instruction::I32Const(y % table_mod));
201256
func.instruction(Instruction::LocalGet(num_params));
202257
func.instruction(Instruction::TableSet { table: 0 });
203258

204259
func.instruction(Instruction::LocalSet(num_params));
205-
func.instruction(Instruction::I32Const(*z % table_mod));
260+
func.instruction(Instruction::I32Const(z % table_mod));
206261
func.instruction(Instruction::LocalGet(num_params));
207262
func.instruction(Instruction::TableSet { table: 0 });
208263
}
209264
TableOp::Make => {
210-
func.instruction(Instruction::Call(2));
265+
func.instruction(Instruction::Call(make_refs_func_idx));
211266
func.instruction(Instruction::Drop);
212267
func.instruction(Instruction::Drop);
213268
func.instruction(Instruction::Drop);
@@ -216,27 +271,52 @@ impl TableOp {
216271
func.instruction(Instruction::LocalGet(x % num_params));
217272
func.instruction(Instruction::LocalGet(y % num_params));
218273
func.instruction(Instruction::LocalGet(z % num_params));
219-
func.instruction(Instruction::Call(1));
274+
func.instruction(Instruction::Call(take_refs_func_idx));
220275
}
221276
TableOp::TakeFromGet(x, y, z) => {
222-
func.instruction(Instruction::I32Const(*x % table_mod));
277+
func.instruction(Instruction::I32Const(x % table_mod));
223278
func.instruction(Instruction::TableGet { table: 0 });
224279

225-
func.instruction(Instruction::I32Const(*y % table_mod));
280+
func.instruction(Instruction::I32Const(y % table_mod));
226281
func.instruction(Instruction::TableGet { table: 0 });
227282

228-
func.instruction(Instruction::I32Const(*z % table_mod));
283+
func.instruction(Instruction::I32Const(z % table_mod));
229284
func.instruction(Instruction::TableGet { table: 0 });
230285

231-
func.instruction(Instruction::Call(1));
286+
func.instruction(Instruction::Call(take_refs_func_idx));
232287
}
233288
TableOp::TakeFromMake => {
234-
func.instruction(Instruction::Call(2));
235-
func.instruction(Instruction::Call(1));
289+
func.instruction(Instruction::Call(make_refs_func_idx));
290+
func.instruction(Instruction::Call(take_refs_func_idx));
236291
}
237292
Self::TakeFromGc => {
238-
func.instruction(Instruction::Call(0));
239-
func.instruction(Instruction::Call(1));
293+
func.instruction(Instruction::Call(gc_func_idx));
294+
func.instruction(Instruction::Call(take_refs_func_idx));
295+
}
296+
TableOp::GetGlobal(x) => {
297+
func.instruction(Instruction::GlobalGet(x % num_globals));
298+
func.instruction(Instruction::Drop);
299+
}
300+
TableOp::SetGlobalFromParam(global, param) => {
301+
func.instruction(Instruction::LocalGet(param % num_params));
302+
func.instruction(Instruction::GlobalSet(global % num_globals));
303+
}
304+
TableOp::SetGlobalFromGet(global, x) => {
305+
func.instruction(Instruction::I32Const(x));
306+
func.instruction(Instruction::TableGet { table: 0 });
307+
func.instruction(Instruction::GlobalSet(global % num_globals));
308+
}
309+
TableOp::SetGlobalFromMake(x, y, z) => {
310+
func.instruction(Instruction::Call(make_refs_func_idx));
311+
func.instruction(Instruction::GlobalSet(x % num_globals));
312+
func.instruction(Instruction::GlobalSet(y % num_globals));
313+
func.instruction(Instruction::GlobalSet(z % num_globals));
314+
}
315+
TableOp::TakeFromGlobalGet(x, y, z) => {
316+
func.instruction(Instruction::GlobalGet(x % num_globals));
317+
func.instruction(Instruction::GlobalGet(y % num_globals));
318+
func.instruction(Instruction::GlobalGet(z % num_globals));
319+
func.instruction(Instruction::Call(take_refs_func_idx));
240320
}
241321
}
242322
}
@@ -250,6 +330,7 @@ mod tests {
250330
fn test_wat_string() {
251331
let ops = TableOps {
252332
num_params: 5,
333+
num_globals: 1,
253334
table_size: 20,
254335
ops: vec![
255336
TableOp::Gc,
@@ -261,6 +342,11 @@ mod tests {
261342
TableOp::TakeFromParams(8, 9, 10),
262343
TableOp::TakeFromGet(11, 12, 13),
263344
TableOp::TakeFromMake,
345+
TableOp::GetGlobal(14),
346+
TableOp::SetGlobalFromParam(15, 16),
347+
TableOp::SetGlobalFromGet(17, 18),
348+
TableOp::SetGlobalFromMake(19, 20, 21),
349+
TableOp::TakeFromGlobalGet(22, 23, 24),
264350
],
265351
};
266352

@@ -320,9 +406,25 @@ mod tests {
320406
call 1
321407
call 2
322408
call 1
409+
global.get 0
410+
drop
411+
local.get 1
412+
global.set 0
413+
i32.const 18
414+
table.get 0
415+
global.set 0
416+
call 2
417+
global.set 0
418+
global.set 0
419+
global.set 0
420+
global.get 0
421+
global.get 0
422+
global.get 0
423+
call 1
323424
br 0 (;@1;)
324425
end)
325426
(table (;0;) 20 externref)
427+
(global (;0;) (mut externref) (ref.null extern))
326428
(export "run" (func 3)))
327429
"#;
328430
eprintln!("expected WAT = {}", expected);

crates/fuzzing/src/oracles.rs

+3
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,9 @@ pub fn table_ops(mut fuzz_config: generators::Config, ops: generators::table_ops
540540
.map(|_| Val::ExternRef(Some(ExternRef::new(CountDrops(num_dropped.clone())))))
541541
.collect();
542542
let _ = run.call(&mut store, &args, &mut []);
543+
544+
// Do a final GC after running the Wasm.
545+
store.gc();
543546
}
544547

545548
assert_eq!(num_dropped.load(SeqCst), expected_drops.load(SeqCst));

crates/runtime/src/externref.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ impl Deref for VMExternRef {
463463
///
464464
/// We use this so that we can morally put `VMExternRef`s inside of `HashSet`s
465465
/// even though they don't implement `Eq` and `Hash` to avoid foot guns.
466-
#[derive(Clone)]
466+
#[derive(Clone, Debug)]
467467
struct VMExternRefWithTraits(VMExternRef);
468468

469469
impl Hash for VMExternRefWithTraits {
@@ -940,7 +940,9 @@ pub unsafe fn gc(
940940
debug_assert!(
941941
r.is_null() || activations_table_set.contains(&r),
942942
"every on-stack externref inside a Wasm frame should \
943-
have an entry in the VMExternRefActivationsTable"
943+
have an entry in the VMExternRefActivationsTable; \
944+
{:?} is not in the table",
945+
r
944946
);
945947
if let Some(r) = NonNull::new(r) {
946948
VMExternRefActivationsTable::insert_precise_stack_root(

crates/runtime/src/libcalls.rs

+10
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,16 @@ pub unsafe extern "C" fn wasmtime_activations_table_insert_with_gc(
402402
let externref = VMExternRef::clone_from_raw(externref);
403403
let instance = (*vmctx).instance();
404404
let (activations_table, module_info_lookup) = (*instance.store()).externref_activations_table();
405+
406+
// Invariant: all `externref`s on the stack have an entry in the activations
407+
// table. So we need to ensure that this `externref` is in the table
408+
// *before* we GC, even though `insert_with_gc` will ensure that it is in
409+
// the table *after* the GC. This technically results in one more hash table
410+
// look up than is strictly necessary -- which we could avoid by having an
411+
// additional GC method that is aware of these GC-triggering references --
412+
// but it isn't really a concern because this is already a slow path.
413+
activations_table.insert_without_gc(externref.clone());
414+
405415
activations_table.insert_with_gc(externref, module_info_lookup);
406416
}
407417

0 commit comments

Comments
 (0)