Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/fuzzing/src/generators/gc_ops/tests.rs
3068 views
1
use crate::generators::gc_ops::{
2
limits::GcOpsLimits,
3
ops::{GcOp, GcOps, OP_NAMES},
4
types::{RecGroupId, TypeId, Types},
5
};
6
use mutatis;
7
use rand::rngs::StdRng;
8
use rand::{Rng, SeedableRng};
9
use wasmparser;
10
use wasmprinter;
11
12
/// Creates empty GcOps
13
fn empty_test_ops() -> GcOps {
14
let mut t = GcOps {
15
limits: GcOpsLimits {
16
num_params: 5,
17
num_globals: 5,
18
table_size: 5,
19
max_rec_groups: 5,
20
max_types: 5,
21
},
22
ops: vec![],
23
types: Types::new(),
24
};
25
for i in 0..t.limits.max_rec_groups {
26
t.types.insert_rec_group(RecGroupId(i));
27
}
28
t
29
}
30
31
/// Creates GcOps with all default opcodes
32
fn test_ops(num_params: u32, num_globals: u32, table_size: u32) -> GcOps {
33
let mut t = GcOps {
34
limits: GcOpsLimits {
35
num_params,
36
num_globals,
37
table_size,
38
max_rec_groups: 7,
39
max_types: 10,
40
},
41
ops: vec![
42
GcOp::NullExtern,
43
GcOp::Drop,
44
GcOp::Gc,
45
GcOp::LocalSet { local_index: 0 },
46
GcOp::LocalGet { local_index: 0 },
47
GcOp::GlobalSet { global_index: 0 },
48
GcOp::GlobalGet { global_index: 0 },
49
GcOp::StructNew { type_index: 0 },
50
],
51
types: Types::new(),
52
};
53
54
for i in 0..t.limits.max_rec_groups {
55
t.types.insert_rec_group(RecGroupId(i));
56
}
57
58
let mut rng = StdRng::seed_from_u64(0xC0FFEE);
59
if t.limits.max_rec_groups > 0 {
60
for i in 0..t.limits.max_types {
61
let gid = RecGroupId(rng.gen_range(0..t.limits.max_rec_groups));
62
t.types.insert_empty_struct(TypeId(i), gid);
63
}
64
}
65
66
t
67
}
68
69
#[test]
70
fn mutate_gc_ops_with_default_mutator() -> mutatis::Result<()> {
71
let _ = env_logger::try_init();
72
73
let mut features = wasmparser::WasmFeatures::default();
74
features.insert(wasmparser::WasmFeatures::REFERENCE_TYPES);
75
features.insert(wasmparser::WasmFeatures::FUNCTION_REFERENCES);
76
features.insert(wasmparser::WasmFeatures::GC_TYPES);
77
features.insert(wasmparser::WasmFeatures::GC);
78
79
let mut ops = test_ops(5, 5, 5);
80
81
let mut session = mutatis::Session::new();
82
for _ in 0..2048 {
83
session.mutate(&mut ops)?;
84
85
let wasm = ops.to_wasm_binary();
86
crate::oracles::log_wasm(&wasm);
87
88
let mut validator = wasmparser::Validator::new_with_features(features);
89
if let Err(e) = validator.validate_all(&wasm) {
90
let mut config = wasmprinter::Config::new();
91
config.print_offsets(true);
92
config.print_operand_stack(true);
93
let mut wat = String::new();
94
let wat = match config.print(&wasm, &mut wasmprinter::PrintFmtWrite(&mut wat)) {
95
Ok(()) => wat,
96
Err(e) => format!("<failed to disassemble Wasm binary to WAT: {e}>"),
97
};
98
panic!(
99
"Emitted Wasm binary is not valid!\n\n\
100
=== Validation Error ===\n\n\
101
{e}\n\n\
102
=== GcOps ===\n\n\
103
{ops:#?}\n\n\
104
=== Wat ===\n\n\
105
{wat}"
106
);
107
}
108
}
109
Ok(())
110
}
111
112
#[test]
113
fn struct_new_removed_when_no_types() -> mutatis::Result<()> {
114
let _ = env_logger::try_init();
115
116
let mut ops = test_ops(0, 0, 0);
117
ops.limits.max_types = 0;
118
ops.ops = vec![GcOp::StructNew { type_index: 42 }];
119
120
ops.fixup();
121
assert!(
122
ops.ops
123
.iter()
124
.all(|op| !matches!(op, GcOp::StructNew { .. })),
125
"StructNew should be removed when there are no types"
126
);
127
Ok(())
128
}
129
130
#[test]
131
fn local_ops_removed_when_no_params() -> mutatis::Result<()> {
132
let _ = env_logger::try_init();
133
134
let mut ops = test_ops(0, 0, 0);
135
ops.limits.num_params = 0;
136
ops.ops = vec![
137
GcOp::LocalGet { local_index: 42 },
138
GcOp::LocalSet { local_index: 99 },
139
];
140
141
ops.fixup();
142
assert!(
143
ops.ops
144
.iter()
145
.all(|op| !matches!(op, GcOp::LocalGet { .. } | GcOp::LocalSet { .. })),
146
"LocalGet/LocalSet should be removed when there are no params"
147
);
148
Ok(())
149
}
150
151
#[test]
152
fn global_ops_removed_when_no_globals() -> mutatis::Result<()> {
153
let _ = env_logger::try_init();
154
155
let mut ops = test_ops(0, 0, 0);
156
ops.limits.num_globals = 0;
157
ops.ops = vec![
158
GcOp::GlobalGet { global_index: 42 },
159
GcOp::GlobalSet { global_index: 99 },
160
];
161
162
ops.fixup();
163
assert!(
164
ops.ops
165
.iter()
166
.all(|op| !matches!(op, GcOp::GlobalGet { .. } | GcOp::GlobalSet { .. })),
167
"GlobalGet/GlobalSet should be removed when there are no globals"
168
);
169
Ok(())
170
}
171
172
#[test]
173
fn every_op_generated() -> mutatis::Result<()> {
174
let _ = env_logger::try_init();
175
let mut unseen_ops: std::collections::HashSet<_> = OP_NAMES.iter().copied().collect();
176
177
let mut res = empty_test_ops();
178
let mut session = mutatis::Session::new().seed(0xC0FFEE);
179
180
'outer: for _ in 0..=1024 {
181
session.mutate(&mut res)?;
182
for op in &res.ops {
183
unseen_ops.remove(op.name());
184
if unseen_ops.is_empty() {
185
break 'outer;
186
}
187
}
188
}
189
190
assert!(unseen_ops.is_empty(), "Failed to generate {unseen_ops:?}");
191
Ok(())
192
}
193
194
#[test]
195
fn emits_empty_rec_groups_and_validates() -> mutatis::Result<()> {
196
let _ = env_logger::try_init();
197
198
let mut ops = test_ops(5, 5, 5);
199
200
let wasm = ops.to_wasm_binary();
201
202
let feats = wasmparser::WasmFeatures::default();
203
feats.reference_types();
204
feats.gc();
205
let mut validator = wasmparser::Validator::new_with_features(feats);
206
assert!(
207
validator.validate_all(&wasm).is_ok(),
208
"GC validation failed"
209
);
210
211
let wat = wasmprinter::print_bytes(&wasm).expect("to WAT");
212
let recs = wat.matches("(rec").count();
213
let structs = wat.matches("(struct)").count();
214
215
assert_eq!(recs, 7, "expected 2 (rec) blocks, got {recs}");
216
assert_eq!(structs, 10, "expected no struct types, got {structs}");
217
218
Ok(())
219
}
220
221
#[test]
222
fn fixup_check_types_and_indexes() -> mutatis::Result<()> {
223
let _ = env_logger::try_init();
224
225
let mut ops = test_ops(5, 5, 5);
226
227
// These `GcOp`s do not have their operands satisfied, and their results are
228
// not the operands of the next op, so `fixup` will need to deal with
229
// that. Additionally, their immediates are out-of-bounds of their
230
// respective index spaces, which `fixup` will also need to address.
231
ops.ops = vec![
232
GcOp::TakeTypedStructCall {
233
type_index: ops.limits.max_types + 7,
234
},
235
GcOp::GlobalSet {
236
global_index: ops.limits.num_globals * 2,
237
},
238
GcOp::StructNew {
239
type_index: ops.limits.max_types + 9,
240
},
241
GcOp::LocalSet {
242
local_index: ops.limits.num_params * 5,
243
},
244
];
245
246
// Call `fixup` to insert missing types, rewrite the immediates such that
247
// they are within their bounds, insert missing operands, and drop unused
248
// results.
249
ops.fixup();
250
251
// Check that we got the expected `GcOp` sequence after `fixup`:
252
assert_eq!(
253
ops.ops,
254
[
255
// Inserted by `fixup` to satisfy `TakeTypedStructCall`'s operands.
256
GcOp::StructNew { type_index: 7 },
257
// The `type_index` is now valid.
258
GcOp::TakeTypedStructCall { type_index: 7 },
259
// Inserted by `fixup` to satisfy `GlobalSet`'s operands.
260
GcOp::NullExtern,
261
// The `global_index` is now valid.
262
GcOp::GlobalSet { global_index: 0 },
263
// The `type_index` is now valid.
264
GcOp::StructNew { type_index: 9 },
265
// Inserted by `fixup` to satisfy `LocalSet`'s operands.
266
GcOp::NullExtern,
267
// The `local_index` is now valid.
268
GcOp::LocalSet { local_index: 0 },
269
// Inserted by `fixup` to make sure the operand stack is empty at
270
// the end of the block.
271
GcOp::Drop,
272
]
273
);
274
275
// Verify that we generate a valid Wasm binary after calling `fixup`.
276
let wasm = ops.to_wasm_binary();
277
let wat = wasmprinter::print_bytes(&wasm).unwrap();
278
log::debug!("{wat}");
279
let feats = wasmparser::WasmFeatures::default();
280
feats.reference_types();
281
feats.gc();
282
let mut validator = wasmparser::Validator::new_with_features(feats);
283
assert!(
284
validator.validate_all(&wasm).is_ok(),
285
"GC validation should pass after fixup"
286
);
287
288
Ok(())
289
}
290
291