Path: blob/main/crates/fuzzing/src/generators/gc_ops/tests.rs
3068 views
use crate::generators::gc_ops::{1limits::GcOpsLimits,2ops::{GcOp, GcOps, OP_NAMES},3types::{RecGroupId, TypeId, Types},4};5use mutatis;6use rand::rngs::StdRng;7use rand::{Rng, SeedableRng};8use wasmparser;9use wasmprinter;1011/// Creates empty GcOps12fn empty_test_ops() -> GcOps {13let mut t = GcOps {14limits: GcOpsLimits {15num_params: 5,16num_globals: 5,17table_size: 5,18max_rec_groups: 5,19max_types: 5,20},21ops: vec![],22types: Types::new(),23};24for i in 0..t.limits.max_rec_groups {25t.types.insert_rec_group(RecGroupId(i));26}27t28}2930/// Creates GcOps with all default opcodes31fn test_ops(num_params: u32, num_globals: u32, table_size: u32) -> GcOps {32let mut t = GcOps {33limits: GcOpsLimits {34num_params,35num_globals,36table_size,37max_rec_groups: 7,38max_types: 10,39},40ops: vec![41GcOp::NullExtern,42GcOp::Drop,43GcOp::Gc,44GcOp::LocalSet { local_index: 0 },45GcOp::LocalGet { local_index: 0 },46GcOp::GlobalSet { global_index: 0 },47GcOp::GlobalGet { global_index: 0 },48GcOp::StructNew { type_index: 0 },49],50types: Types::new(),51};5253for i in 0..t.limits.max_rec_groups {54t.types.insert_rec_group(RecGroupId(i));55}5657let mut rng = StdRng::seed_from_u64(0xC0FFEE);58if t.limits.max_rec_groups > 0 {59for i in 0..t.limits.max_types {60let gid = RecGroupId(rng.gen_range(0..t.limits.max_rec_groups));61t.types.insert_empty_struct(TypeId(i), gid);62}63}6465t66}6768#[test]69fn mutate_gc_ops_with_default_mutator() -> mutatis::Result<()> {70let _ = env_logger::try_init();7172let mut features = wasmparser::WasmFeatures::default();73features.insert(wasmparser::WasmFeatures::REFERENCE_TYPES);74features.insert(wasmparser::WasmFeatures::FUNCTION_REFERENCES);75features.insert(wasmparser::WasmFeatures::GC_TYPES);76features.insert(wasmparser::WasmFeatures::GC);7778let mut ops = test_ops(5, 5, 5);7980let mut session = mutatis::Session::new();81for _ in 0..2048 {82session.mutate(&mut ops)?;8384let wasm = ops.to_wasm_binary();85crate::oracles::log_wasm(&wasm);8687let mut validator = wasmparser::Validator::new_with_features(features);88if let Err(e) = validator.validate_all(&wasm) {89let mut config = wasmprinter::Config::new();90config.print_offsets(true);91config.print_operand_stack(true);92let mut wat = String::new();93let wat = match config.print(&wasm, &mut wasmprinter::PrintFmtWrite(&mut wat)) {94Ok(()) => wat,95Err(e) => format!("<failed to disassemble Wasm binary to WAT: {e}>"),96};97panic!(98"Emitted Wasm binary is not valid!\n\n\99=== Validation Error ===\n\n\100{e}\n\n\101=== GcOps ===\n\n\102{ops:#?}\n\n\103=== Wat ===\n\n\104{wat}"105);106}107}108Ok(())109}110111#[test]112fn struct_new_removed_when_no_types() -> mutatis::Result<()> {113let _ = env_logger::try_init();114115let mut ops = test_ops(0, 0, 0);116ops.limits.max_types = 0;117ops.ops = vec![GcOp::StructNew { type_index: 42 }];118119ops.fixup();120assert!(121ops.ops122.iter()123.all(|op| !matches!(op, GcOp::StructNew { .. })),124"StructNew should be removed when there are no types"125);126Ok(())127}128129#[test]130fn local_ops_removed_when_no_params() -> mutatis::Result<()> {131let _ = env_logger::try_init();132133let mut ops = test_ops(0, 0, 0);134ops.limits.num_params = 0;135ops.ops = vec![136GcOp::LocalGet { local_index: 42 },137GcOp::LocalSet { local_index: 99 },138];139140ops.fixup();141assert!(142ops.ops143.iter()144.all(|op| !matches!(op, GcOp::LocalGet { .. } | GcOp::LocalSet { .. })),145"LocalGet/LocalSet should be removed when there are no params"146);147Ok(())148}149150#[test]151fn global_ops_removed_when_no_globals() -> mutatis::Result<()> {152let _ = env_logger::try_init();153154let mut ops = test_ops(0, 0, 0);155ops.limits.num_globals = 0;156ops.ops = vec![157GcOp::GlobalGet { global_index: 42 },158GcOp::GlobalSet { global_index: 99 },159];160161ops.fixup();162assert!(163ops.ops164.iter()165.all(|op| !matches!(op, GcOp::GlobalGet { .. } | GcOp::GlobalSet { .. })),166"GlobalGet/GlobalSet should be removed when there are no globals"167);168Ok(())169}170171#[test]172fn every_op_generated() -> mutatis::Result<()> {173let _ = env_logger::try_init();174let mut unseen_ops: std::collections::HashSet<_> = OP_NAMES.iter().copied().collect();175176let mut res = empty_test_ops();177let mut session = mutatis::Session::new().seed(0xC0FFEE);178179'outer: for _ in 0..=1024 {180session.mutate(&mut res)?;181for op in &res.ops {182unseen_ops.remove(op.name());183if unseen_ops.is_empty() {184break 'outer;185}186}187}188189assert!(unseen_ops.is_empty(), "Failed to generate {unseen_ops:?}");190Ok(())191}192193#[test]194fn emits_empty_rec_groups_and_validates() -> mutatis::Result<()> {195let _ = env_logger::try_init();196197let mut ops = test_ops(5, 5, 5);198199let wasm = ops.to_wasm_binary();200201let feats = wasmparser::WasmFeatures::default();202feats.reference_types();203feats.gc();204let mut validator = wasmparser::Validator::new_with_features(feats);205assert!(206validator.validate_all(&wasm).is_ok(),207"GC validation failed"208);209210let wat = wasmprinter::print_bytes(&wasm).expect("to WAT");211let recs = wat.matches("(rec").count();212let structs = wat.matches("(struct)").count();213214assert_eq!(recs, 7, "expected 2 (rec) blocks, got {recs}");215assert_eq!(structs, 10, "expected no struct types, got {structs}");216217Ok(())218}219220#[test]221fn fixup_check_types_and_indexes() -> mutatis::Result<()> {222let _ = env_logger::try_init();223224let mut ops = test_ops(5, 5, 5);225226// These `GcOp`s do not have their operands satisfied, and their results are227// not the operands of the next op, so `fixup` will need to deal with228// that. Additionally, their immediates are out-of-bounds of their229// respective index spaces, which `fixup` will also need to address.230ops.ops = vec![231GcOp::TakeTypedStructCall {232type_index: ops.limits.max_types + 7,233},234GcOp::GlobalSet {235global_index: ops.limits.num_globals * 2,236},237GcOp::StructNew {238type_index: ops.limits.max_types + 9,239},240GcOp::LocalSet {241local_index: ops.limits.num_params * 5,242},243];244245// Call `fixup` to insert missing types, rewrite the immediates such that246// they are within their bounds, insert missing operands, and drop unused247// results.248ops.fixup();249250// Check that we got the expected `GcOp` sequence after `fixup`:251assert_eq!(252ops.ops,253[254// Inserted by `fixup` to satisfy `TakeTypedStructCall`'s operands.255GcOp::StructNew { type_index: 7 },256// The `type_index` is now valid.257GcOp::TakeTypedStructCall { type_index: 7 },258// Inserted by `fixup` to satisfy `GlobalSet`'s operands.259GcOp::NullExtern,260// The `global_index` is now valid.261GcOp::GlobalSet { global_index: 0 },262// The `type_index` is now valid.263GcOp::StructNew { type_index: 9 },264// Inserted by `fixup` to satisfy `LocalSet`'s operands.265GcOp::NullExtern,266// The `local_index` is now valid.267GcOp::LocalSet { local_index: 0 },268// Inserted by `fixup` to make sure the operand stack is empty at269// the end of the block.270GcOp::Drop,271]272);273274// Verify that we generate a valid Wasm binary after calling `fixup`.275let wasm = ops.to_wasm_binary();276let wat = wasmprinter::print_bytes(&wasm).unwrap();277log::debug!("{wat}");278let feats = wasmparser::WasmFeatures::default();279feats.reference_types();280feats.gc();281let mut validator = wasmparser::Validator::new_with_features(feats);282assert!(283validator.validate_all(&wasm).is_ok(),284"GC validation should pass after fixup"285);286287Ok(())288}289290291