From 69506405a5adee7700895e9704ea66e766f0b854 Mon Sep 17 00:00:00 2001 From: Venkkatesh Sekar Date: Thu, 8 Jan 2026 13:08:18 +0000 Subject: [PATCH 1/7] instrumentation check --- canfuzz/src/instrumentation.rs | 280 +++++++++++++++++++++++---------- 1 file changed, 197 insertions(+), 83 deletions(-) diff --git a/canfuzz/src/instrumentation.rs b/canfuzz/src/instrumentation.rs index dabca97..89949d4 100644 --- a/canfuzz/src/instrumentation.rs +++ b/canfuzz/src/instrumentation.rs @@ -15,10 +15,11 @@ use rand::Rng; use rand::RngCore; use rand::SeedableRng; use wirm::ir::function::FunctionBuilder; -use wirm::ir::id::{FunctionID, GlobalID, LocalID}; +use wirm::ir::id::{FunctionID, GlobalID, LocalID, MemoryID}; use wirm::ir::module::module_functions::FuncKind; use wirm::ir::types::{InitExpr, Instructions, Value}; use wirm::module_builder::AddLocal; +use wirm::opcode::Inject; use wirm::wasmparser::{MemArg, Operator, Validator}; use wirm::{DataType, InitInstr, Module, Opcode}; @@ -101,13 +102,25 @@ fn instrument_for_afl( module: &mut Module<'_>, instrumentation_args: &InstrumentationArgs, ) -> Result<()> { + let is_memory64 = module + .memories + .get_mem_by_id(MemoryID(0)) + .map(|m| m.ty.memory64) + .unwrap_or(false); + + println!("Memory type : {:?}", is_memory64); let (afl_prev_loc_indices, afl_mem_ptr_idx) = - inject_globals(module, instrumentation_args.history_size); + inject_globals(module, instrumentation_args.history_size, is_memory64); println!( " -> Injected globals: prev_locs @ indices {afl_prev_loc_indices:?}, mem_ptr @ index {afl_mem_ptr_idx:?}" ); - inject_afl_coverage_export(module, instrumentation_args.history_size, afl_mem_ptr_idx)?; + inject_afl_coverage_export( + module, + instrumentation_args.history_size, + afl_mem_ptr_idx, + is_memory64, + )?; println!(" -> Injected `canister_update __export_coverage_for_afl` function."); instrument_branches( @@ -115,6 +128,7 @@ fn instrument_for_afl( &afl_prev_loc_indices, afl_mem_ptr_idx, instrumentation_args.seed, + is_memory64, ); println!(" -> Instrumented branch instructions in all functions."); @@ -127,20 +141,31 @@ fn instrument_for_afl( /// of the previously executed basic blocks. This is used to track execution history /// in the control flow graph. /// - `__afl_mem_ptr`: An immutable i32 global that holds the base address (0) of the coverage map. -fn inject_globals(module: &mut Module<'_>, history_size: usize) -> (Vec, GlobalID) { +fn inject_globals( + module: &mut Module<'_>, + history_size: usize, + is_memory64: bool, +) -> (Vec, GlobalID) { let mut afl_prev_loc_indices = Vec::with_capacity(history_size); + + let (ptr_type, init_val) = if is_memory64 { + (DataType::I64, Value::I64(0)) + } else { + (DataType::I32, Value::I32(0)) + }; + for _ in 0..history_size { let global_id = module.add_global( - InitExpr::new(vec![InitInstr::Value(Value::I32(0))]), - DataType::I32, + InitExpr::new(vec![InitInstr::Value(init_val)]), + ptr_type, true, false, ); afl_prev_loc_indices.push(global_id); } let afl_mem_ptr_idx = module.add_global( - InitExpr::new(vec![InitInstr::Value(Value::I32(0))]), - DataType::I32, + InitExpr::new(vec![InitInstr::Value(init_val)]), + ptr_type, false, false, ); @@ -157,22 +182,35 @@ fn inject_afl_coverage_export<'a>( module: &mut Module<'a>, history_size: usize, afl_mem_ptr_idx: GlobalID, + is_memory64: bool, ) -> Result<()> { - let (msg_reply_data_append_idx, msg_reply_idx) = ensure_ic0_imports(module)?; + let (msg_reply_data_append_idx, msg_reply_idx) = ensure_ic0_imports(module, is_memory64)?; let mut func_builder = FunctionBuilder::new(&[], &[]); - func_builder - .global_get(afl_mem_ptr_idx) - .i32_const(AFL_COVERAGE_MAP_SIZE * history_size as i32) - .call(msg_reply_data_append_idx) - .call(msg_reply_idx) - .global_get(afl_mem_ptr_idx) - .i32_const(0) - .i32_const(AFL_COVERAGE_MAP_SIZE * history_size as i32) - .memory_fill(0); - let coverage_function_id = func_builder.finish_module(module); + if is_memory64 { + func_builder + .global_get(afl_mem_ptr_idx) + .i64_const(AFL_COVERAGE_MAP_SIZE as i64 * history_size as i64) + .call(msg_reply_data_append_idx) + .call(msg_reply_idx) + .global_get(afl_mem_ptr_idx) + .i32_const(0) + .i64_const(AFL_COVERAGE_MAP_SIZE as i64 * history_size as i64) + .memory_fill(0); + } else { + func_builder + .global_get(afl_mem_ptr_idx) + .i32_const(AFL_COVERAGE_MAP_SIZE * history_size as i32) + .call(msg_reply_data_append_idx) + .call(msg_reply_idx) + .global_get(afl_mem_ptr_idx) + .i32_const(0) + .i32_const(AFL_COVERAGE_MAP_SIZE * history_size as i32) + .memory_fill(0); + } + let coverage_function_id = func_builder.finish_module(module); let export_name = format!("canister_update {COVERAGE_FN_EXPORT_NAME}"); module .exports @@ -192,9 +230,10 @@ fn instrument_branches( afl_prev_loc_indices: &[GlobalID], afl_mem_ptr_idx: GlobalID, seed: Seed, + is_memory64: bool, ) { let instrumentation_function = - afl_instrumentation_slice(module, afl_prev_loc_indices, afl_mem_ptr_idx); + afl_instrumentation_slice(module, afl_prev_loc_indices, afl_mem_ptr_idx, is_memory64); let seed = match seed { Seed::Random => rand::rng().next_u32(), @@ -206,9 +245,16 @@ fn instrument_branches( let mut create_instrumentation_ops = |ops: &mut Vec| { let curr_location = rng.random_range(0..AFL_COVERAGE_MAP_SIZE * afl_prev_loc_indices.len() as i32); - ops.push(Operator::I32Const { - value: curr_location, - }); + + if is_memory64 { + ops.push(Operator::I64Const { + value: curr_location as i64, + }); + } else { + ops.push(Operator::I32Const { + value: curr_location as i32, + }); + } ops.push(Operator::Call { function_index: instrumentation_function.0, }); @@ -274,52 +320,105 @@ fn afl_instrumentation_slice( module: &mut Module<'_>, afl_prev_loc_indices: &[GlobalID], afl_mem_ptr_idx: GlobalID, + is_memory64: bool, ) -> FunctionID { - let mut func_builder = FunctionBuilder::new(&[DataType::I32], &[]); - let curr_location = LocalID(0); - let afl_local_idx = func_builder.add_local(DataType::I32); - - func_builder.local_get(curr_location); - for &prev_loc_idx in afl_prev_loc_indices { - func_builder.global_get(prev_loc_idx).i32_xor(); - } + if is_memory64 { + let mut func_builder = FunctionBuilder::new(&[DataType::I64], &[]); + let curr_location = LocalID(0); + let afl_local_idx = func_builder.add_local(DataType::I64); + + func_builder.local_get(curr_location); + for &prev_loc_idx in afl_prev_loc_indices { + func_builder.global_get(prev_loc_idx).i64_xor(); + } - func_builder - .global_get(afl_mem_ptr_idx) - .i32_add() - .local_tee(afl_local_idx) - .local_get(afl_local_idx) - .i32_load8_u(MemArg { - offset: 0, - align: 0, - memory: 0, - max_align: 0, - }) - .i32_const(1) - .i32_add() - .i32_store8(MemArg { - offset: 0, - align: 0, - memory: 0, - max_align: 0, + func_builder + .global_get(afl_mem_ptr_idx) + .i64_add() + .local_tee(afl_local_idx) + .local_get(afl_local_idx) + .i64_load8_u(MemArg { + offset: 0, + align: 0, + memory: 0, + max_align: 0, + }) + .i64_const(1) + .i64_add(); + + // i64_store8 opcode trait doesn't exist + func_builder.inject(Operator::I64Store8 { + memarg: MemArg { + offset: 0, + align: 0, + memory: 0, + max_align: 0, + }, }); - // Shift the history - for i in (1..afl_prev_loc_indices.len()).rev() { + // Shift the history + for i in (1..afl_prev_loc_indices.len()).rev() { + func_builder + .global_get(afl_prev_loc_indices[i - 1]) + .i64_const(1) + .i64_shr_unsigned() + .global_set(afl_prev_loc_indices[i]); + } + + func_builder + .local_get(curr_location) + .i64_const(1) + .i64_shr_unsigned() + .global_set(afl_prev_loc_indices[0]); + + func_builder.finish_module(module) + } else { + let mut func_builder = FunctionBuilder::new(&[DataType::I32], &[]); + let curr_location = LocalID(0); + let afl_local_idx = func_builder.add_local(DataType::I32); + + func_builder.local_get(curr_location); + for &prev_loc_idx in afl_prev_loc_indices { + func_builder.global_get(prev_loc_idx).i32_xor(); + } + func_builder - .global_get(afl_prev_loc_indices[i - 1]) + .global_get(afl_mem_ptr_idx) + .i32_add() + .local_tee(afl_local_idx) + .local_get(afl_local_idx) + .i32_load8_u(MemArg { + offset: 0, + align: 0, + memory: 0, + max_align: 0, + }) .i32_const(1) - .i32_shr_unsigned() - .global_set(afl_prev_loc_indices[i]); - } + .i32_add() + .i32_store8(MemArg { + offset: 0, + align: 0, + memory: 0, + max_align: 0, + }); - func_builder - .local_get(curr_location) - .i32_const(1) - .i32_shr_unsigned() - .global_set(afl_prev_loc_indices[0]); + // Shift the history + for i in (1..afl_prev_loc_indices.len()).rev() { + func_builder + .global_get(afl_prev_loc_indices[i - 1]) + .i32_const(1) + .i32_shr_unsigned() + .global_set(afl_prev_loc_indices[i]); + } + + func_builder + .local_get(curr_location) + .i32_const(1) + .i32_shr_unsigned() + .global_set(afl_prev_loc_indices[0]); - func_builder.finish_module(module) + func_builder.finish_module(module) + } } /// Ensures that the necessary `ic0` System API functions are imported. @@ -328,7 +427,10 @@ fn afl_instrumentation_slice( /// for the `export_coverage` function. This function checks if they are already /// imported. If not, it adds them to the module's import section. /// It returns the function indices for both imports. -fn ensure_ic0_imports(module: &mut Module<'_>) -> Result<(FunctionID, FunctionID)> { +fn ensure_ic0_imports( + module: &mut Module<'_>, + is_memory64: bool, +) -> Result<(FunctionID, FunctionID)> { let mut data_append_idx = module.imports.get_func( API_VERSION_IC0.to_string(), "msg_reply_data_append".to_string(), @@ -338,9 +440,12 @@ fn ensure_ic0_imports(module: &mut Module<'_>) -> Result<(FunctionID, FunctionID .get_func(API_VERSION_IC0.to_string(), "msg_reply".to_string()); if data_append_idx.is_none() { - let type_id = module - .types - .add_func_type(&[DataType::I32, DataType::I32], &[]); + let ptr_type = if is_memory64 { + DataType::I64 + } else { + DataType::I32 + }; + let type_id = module.types.add_func_type(&[ptr_type, ptr_type], &[]); let (func_index, _) = module.add_import_func( API_VERSION_IC0.to_string(), "msg_reply_data_append".to_string(), @@ -389,7 +494,8 @@ mod tests { let history_range: [usize; 4] = [1, 2, 4, 8]; for history_size in history_range { let mut module = Module::parse(&wat, false, false).unwrap(); - let (afl_prev_loc_indices, afl_mem_ptr_idx) = inject_globals(&mut module, history_size); + let (afl_prev_loc_indices, afl_mem_ptr_idx) = + inject_globals(&mut module, history_size, false); assert_eq!(afl_prev_loc_indices.len(), history_size); (0..history_size) @@ -415,7 +521,8 @@ mod tests { let history_range: [usize; 4] = [1, 2, 4, 8]; for history_size in history_range { let mut module = Module::parse(&wat, false, false).unwrap(); - let (afl_prev_loc_indices, afl_mem_ptr_idx) = inject_globals(&mut module, history_size); + let (afl_prev_loc_indices, afl_mem_ptr_idx) = + inject_globals(&mut module, history_size, false); assert_eq!(afl_prev_loc_indices.len(), history_size); (0..history_size).for_each(|index| { @@ -454,7 +561,8 @@ mod tests { let history_range: [usize; 4] = [1, 2, 4, 8]; for history_size in history_range { let mut module = Module::parse(&wat, false, false).unwrap(); - let (afl_prev_loc_indices, afl_mem_ptr_idx) = inject_globals(&mut module, history_size); + let (afl_prev_loc_indices, afl_mem_ptr_idx) = + inject_globals(&mut module, history_size, false); for g in afl_prev_loc_indices.iter() { let global = module.globals.get_kind(*g); @@ -475,7 +583,7 @@ mod tests { .unwrap(); let mut module = Module::parse(&wat, false, false).unwrap(); - let (data_append_idx, reply_idx) = ensure_ic0_imports(&mut module).unwrap(); + let (data_append_idx, reply_idx) = ensure_ic0_imports(&mut module, false).unwrap(); assert_eq!(data_append_idx, FunctionID(0)); assert_eq!(reply_idx, FunctionID(1)); @@ -494,7 +602,7 @@ mod tests { .unwrap(); let mut module = Module::parse(&wat, false, false).unwrap(); - let (data_append_idx, reply_idx) = ensure_ic0_imports(&mut module).unwrap(); + let (data_append_idx, reply_idx) = ensure_ic0_imports(&mut module, false).unwrap(); assert_eq!(data_append_idx, FunctionID(1)); assert_eq!(reply_idx, FunctionID(2)); @@ -513,7 +621,7 @@ mod tests { .unwrap(); let mut module = Module::parse(&wat, false, false).unwrap(); - let (data_append_idx, reply_idx) = ensure_ic0_imports(&mut module).unwrap(); + let (data_append_idx, reply_idx) = ensure_ic0_imports(&mut module, false).unwrap(); assert_eq!(data_append_idx, FunctionID(0)); assert_eq!(reply_idx, FunctionID(1)); @@ -532,7 +640,7 @@ mod tests { .unwrap(); let mut module = Module::parse(&wat, false, false).unwrap(); - let (data_append_idx, reply_idx) = ensure_ic0_imports(&mut module).unwrap(); + let (data_append_idx, reply_idx) = ensure_ic0_imports(&mut module, false).unwrap(); assert_eq!(data_append_idx, FunctionID(1)); assert_eq!(reply_idx, FunctionID(0)); @@ -553,7 +661,7 @@ mod tests { .unwrap(); let mut module = Module::parse(&wat, false, false).unwrap(); - let (data_append_idx, reply_idx) = ensure_ic0_imports(&mut module).unwrap(); + let (data_append_idx, reply_idx) = ensure_ic0_imports(&mut module, false).unwrap(); assert_eq!(data_append_idx, FunctionID(0)); assert_eq!(reply_idx, FunctionID(1)); @@ -584,9 +692,10 @@ mod tests { let history_size = 1; let mut module = Module::parse(&wat, false, false).unwrap(); - let (afl_prev_loc_indices, afl_mem_ptr_idx) = inject_globals(&mut module, history_size); + let (afl_prev_loc_indices, afl_mem_ptr_idx) = + inject_globals(&mut module, history_size, false); let instrumentation_function = - afl_instrumentation_slice(&mut module, &afl_prev_loc_indices, afl_mem_ptr_idx); + afl_instrumentation_slice(&mut module, &afl_prev_loc_indices, afl_mem_ptr_idx, false); assert_eq!(instrumentation_function, FunctionID(0)); let expected_wasm = wat::parse_str( r#"(module @@ -632,9 +741,10 @@ mod tests { let history_size = 2; let mut module = Module::parse(&wat, false, false).unwrap(); - let (afl_prev_loc_indices, afl_mem_ptr_idx) = inject_globals(&mut module, history_size); + let (afl_prev_loc_indices, afl_mem_ptr_idx) = + inject_globals(&mut module, history_size, false); let instrumentation_function = - afl_instrumentation_slice(&mut module, &afl_prev_loc_indices, afl_mem_ptr_idx); + afl_instrumentation_slice(&mut module, &afl_prev_loc_indices, afl_mem_ptr_idx, false); assert_eq!(instrumentation_function, FunctionID(0)); let expected_wasm = wat::parse_str( r#"(module @@ -687,9 +797,9 @@ mod tests { let history_size = 1; let mut module = Module::parse(&wat, false, false).unwrap(); - let (_, afl_mem_ptr_idx) = inject_globals(&mut module, history_size); + let (_, afl_mem_ptr_idx) = inject_globals(&mut module, history_size, false); let coverage_function = - inject_afl_coverage_export(&mut module, history_size, afl_mem_ptr_idx); + inject_afl_coverage_export(&mut module, history_size, afl_mem_ptr_idx, false); assert!(coverage_function.is_ok()); let expected_wasm = wat::parse_str( r#"(module @@ -731,9 +841,9 @@ mod tests { let history_size = 2; let mut module = Module::parse(&wat, false, false).unwrap(); - let (_, afl_mem_ptr_idx) = inject_globals(&mut module, history_size); + let (_, afl_mem_ptr_idx) = inject_globals(&mut module, history_size, false); let coverage_function = - inject_afl_coverage_export(&mut module, history_size, afl_mem_ptr_idx); + inject_afl_coverage_export(&mut module, history_size, afl_mem_ptr_idx, false); assert!(coverage_function.is_ok()); let expected_wasm = wat::parse_str( r#"(module @@ -769,12 +879,14 @@ mod tests { let history_size = 2; let mut module = Module::parse(&wat, false, false).unwrap(); - let (afl_prev_loc_indices, afl_mem_ptr_idx) = inject_globals(&mut module, history_size); + let (afl_prev_loc_indices, afl_mem_ptr_idx) = + inject_globals(&mut module, history_size, false); instrument_branches( &mut module, &afl_prev_loc_indices, afl_mem_ptr_idx, Seed::Static(42), + false, ); let instructions = module @@ -1032,12 +1144,14 @@ mod tests { let history_size = 2; let mut module = Module::parse(&wat, false, false).unwrap(); - let (afl_prev_loc_indices, afl_mem_ptr_idx) = inject_globals(&mut module, history_size); + let (afl_prev_loc_indices, afl_mem_ptr_idx) = + inject_globals(&mut module, history_size, false); instrument_branches( &mut module, &afl_prev_loc_indices, afl_mem_ptr_idx, Seed::Static(42), + false, ); let instructions = module From a74ab20e6380d3f636af7425074a8de0575874cf Mon Sep 17 00:00:00 2001 From: Venkkatesh Sekar Date: Thu, 8 Jan 2026 13:20:25 +0000 Subject: [PATCH 2/7] test --- canfuzz/src/instrumentation.rs | 125 +++++++++++++++++++++++++++++++-- 1 file changed, 121 insertions(+), 4 deletions(-) diff --git a/canfuzz/src/instrumentation.rs b/canfuzz/src/instrumentation.rs index 89949d4..eda7a52 100644 --- a/canfuzz/src/instrumentation.rs +++ b/canfuzz/src/instrumentation.rs @@ -137,10 +137,10 @@ fn instrument_for_afl( /// Injects the necessary global variables for AFL instrumentation. /// -/// - `__afl_prev_loc_N`: A set of `history_size` mutable i32 globals to store the IDs +/// - `__afl_prev_loc_N`: A set of `history_size` mutable i32 (or i64 for wasm64) globals to store the IDs /// of the previously executed basic blocks. This is used to track execution history /// in the control flow graph. -/// - `__afl_mem_ptr`: An immutable i32 global that holds the base address (0) of the coverage map. +/// - `__afl_mem_ptr`: An immutable i32 (or i64 for wasm64) global that holds the base address (0) of the coverage map. fn inject_globals( module: &mut Module<'_>, history_size: usize, @@ -310,8 +310,8 @@ fn instrument_branches( /// ... /// prev_loc[0] = curr_location >> 1; /// ``` -/// The generated function takes the current location (`curr_location`) as an i32 parameter -/// and is added to the module. +/// The generated function takes the current location (`curr_location`) as an i32 (or i64 for wasm64) +/// parameter and is added to the module. /// /// # Returns /// @@ -1324,4 +1324,121 @@ mod tests { wasm_equality(generated, expected); } + + #[test] + fn instrumentation_round_trip_wasm64() { + let wat = wat::parse_str( + r#" + (module + (type (;0;) (func (param i64))) + (memory (;0;) i64 1) + (export "memory" (memory 0)) + (export "check_even" (func 0)) + (func (;0;) (type 0) (param $num i64) + local.get $num + i64.const 2 + i64.rem_u + i64.eqz + if ;; label = @1 + i64.const 0 + i64.const 1 + i64.store + else + i64.const 0 + i64.const 0 + i64.store + end + ) + ) + "#, + ) + .unwrap(); + + let history_size: usize = 2; + + let expected = wat::parse_str( + r#" + (module + (type (;0;) (func (param i64))) + (type (;1;) (func (param i64 i64))) + (type (;2;) (func)) + (import "ic0" "msg_reply_data_append" (func (;0;) (type 1))) + (import "ic0" "msg_reply" (func (;1;) (type 2))) + (memory (;0;) i64 1) + (global (;0;) (mut i64) i64.const 0) + (global (;1;) (mut i64) i64.const 0) + (global (;2;) i64 i64.const 0) + (export "memory" (memory 0)) + (export "check_even" (func 2)) + (export "canister_update __export_coverage_for_afl" (func 3)) + (func (;2;) (type 0) (param i64) + i64.const 17486 + call 4 + local.get 0 + i64.const 2 + i64.rem_u + i64.eqz + if ;; label = @1 + i64.const 69016 + call 4 + i64.const 0 + i64.const 1 + i64.store + else + i64.const 32602 + call 4 + i64.const 0 + i64.const 0 + i64.store + end + ) + (func (;3;) (type 2) + i64.const 71136 + call 4 + global.get 2 + i64.const 131072 + call 0 + call 1 + global.get 2 + i32.const 0 + i64.const 131072 + memory.fill + ) + (func (;4;) (type 0) (param i64) + (local i64) + local.get 0 + global.get 0 + i64.xor + global.get 1 + i64.xor + global.get 2 + i64.add + local.tee 1 + local.get 1 + i64.load8_u + i64.const 1 + i64.add + i64.store8 + global.get 0 + i64.const 1 + i64.shr_u + global.set 1 + local.get 0 + i64.const 1 + i64.shr_u + global.set 0 + ) + ) + "#, + ) + .unwrap(); + + let generated = instrument_wasm_for_fuzzing(InstrumentationArgs { + wasm_bytes: wat, + history_size, + seed: Seed::Static(42), + }); + + wasm_equality(generated, expected); + } } From 3f49608a94b1f5824ee2ada39a725302815d30ee Mon Sep 17 00:00:00 2001 From: Venkkatesh Sekar Date: Thu, 8 Jan 2026 14:40:53 +0000 Subject: [PATCH 3/7] clippy --- .github/workflows/ci.yml | 2 +- canfuzz/src/custom/mutator/candid.rs | 62 +++++++++---------- .../src/decode_candid_by_instructions.rs | 6 +- examples/motoko_diff/src/motoko_diff.rs | 6 +- 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b62f966..d8eb01a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: env: RUST_VERSION: 1.89.0 - DFX_VERSION: 0.29.1 + DFX_VERSION: 0.30.1 MOPS_VERSION: 1 jobs: diff --git a/canfuzz/src/custom/mutator/candid.rs b/canfuzz/src/custom/mutator/candid.rs index 6ccd978..5fa7d36 100644 --- a/canfuzz/src/custom/mutator/candid.rs +++ b/canfuzz/src/custom/mutator/candid.rs @@ -262,29 +262,28 @@ fn mutate_value(val: &mut IDLValue, ty: &Type, env: &TypeEnv, rng: &mut IDLValue::Record(fields) => { if let Ok(TypeInner::Record(field_types)) = env.trace_type(ty).map(|t| t.as_ref().clone()) + && !fields.is_empty() { - if !fields.is_empty() { - let num_to_mutate = rng.random_range(1..=fields.len()); - let mut available_indices: Vec = (0..fields.len()).collect(); - for _ in 0..num_to_mutate { - if available_indices.is_empty() { - break; - } - let random_vec_idx = rng.random_range(0..available_indices.len()); - let field_idx_to_mutate = available_indices.remove(random_vec_idx); - - if let Some(field_ty) = field_types - .iter() - .find(|f| f.id == Rc::new(fields[field_idx_to_mutate].id.clone())) - { - mutate_value( - &mut fields[field_idx_to_mutate].val, - &field_ty.ty, - env, - rng, - depth + 1, - ); - } + let num_to_mutate = rng.random_range(1..=fields.len()); + let mut available_indices: Vec = (0..fields.len()).collect(); + for _ in 0..num_to_mutate { + if available_indices.is_empty() { + break; + } + let random_vec_idx = rng.random_range(0..available_indices.len()); + let field_idx_to_mutate = available_indices.remove(random_vec_idx); + + if let Some(field_ty) = field_types + .iter() + .find(|f| f.id == Rc::new(fields[field_idx_to_mutate].id.clone())) + { + mutate_value( + &mut fields[field_idx_to_mutate].val, + &field_ty.ty, + env, + rng, + depth + 1, + ); } } } @@ -303,13 +302,13 @@ fn mutate_value(val: &mut IDLValue, ty: &Type, env: &TypeEnv, rng: &mut &seed, Configs::from_str("").unwrap(), env, - &[new_field_type.ty.clone()], + std::slice::from_ref(&new_field_type.ty), &None, - ) { - if !random_val.args.is_empty() { - new_val = random_val.args[0].clone(); - } + ) && !random_val.args.is_empty() + { + new_val = random_val.args[0].clone(); } + v.0.id = (*new_field_type.id).clone(); v.0.val = new_val; v.1 = new_variant_idx as u64; @@ -532,12 +531,11 @@ fn mutate_opt(val: &mut IDLValue, ty: &Type, env: &TypeEnv, rng: &mut R, &seed, Configs::from_str("").unwrap(), env, - &[inner_ty.clone()], + std::slice::from_ref(&inner_ty), &None, - ) { - if let Some(new_val) = random_val.args.into_iter().next() { - *val = IDLValue::Opt(Box::new(new_val)); - } + ) && let Some(new_val) = random_val.args.into_iter().next() + { + *val = IDLValue::Opt(Box::new(new_val)); } } // Mutate inner value diff --git a/examples/decode_candid_by_instructions/src/decode_candid_by_instructions.rs b/examples/decode_candid_by_instructions/src/decode_candid_by_instructions.rs index dab62a0..b053ba3 100644 --- a/examples/decode_candid_by_instructions/src/decode_candid_by_instructions.rs +++ b/examples/decode_candid_by_instructions/src/decode_candid_by_instructions.rs @@ -110,8 +110,10 @@ impl FuzzerOrchestrator for DecodeCandidFuzzer { } else { ExitKind::Ok }; - let instructions = if status == ExitKind::Ok && result.is_ok() { - Decode!(&result.unwrap(), u64).unwrap() + let instructions = if status == ExitKind::Ok + && let Ok(result) = result + { + Decode!(&result, u64).unwrap() } else { 0 }; diff --git a/examples/motoko_diff/src/motoko_diff.rs b/examples/motoko_diff/src/motoko_diff.rs index 44f8428..c6e765f 100644 --- a/examples/motoko_diff/src/motoko_diff.rs +++ b/examples/motoko_diff/src/motoko_diff.rs @@ -106,8 +106,10 @@ impl FuzzerOrchestrator for MotokoDiffFuzzer { let exit_status = parse_canister_result_for_trap(result.clone()); - let exit_status = if exit_status == ExitKind::Ok && result.is_ok() { - let result = Decode!(&result.unwrap(), Vec).unwrap(); + let exit_status = if exit_status == ExitKind::Ok + && let Ok(result) = result + { + let result = Decode!(&result, Vec).unwrap(); if let Ok((signature, _)) = hazmat::sign_prehashed::(&key_inner, k_inner, &digest) { From adc56e5dbaba6ddc58df382a073b592267ae27b2 Mon Sep 17 00:00:00 2001 From: Venkkatesh Sekar Date: Thu, 8 Jan 2026 14:55:38 +0000 Subject: [PATCH 4/7] force canister rebuild --- build_canister/src/lib.rs | 1 + scripts/test_run.sh | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build_canister/src/lib.rs b/build_canister/src/lib.rs index fb6819e..ce4a55f 100644 --- a/build_canister/src/lib.rs +++ b/build_canister/src/lib.rs @@ -17,6 +17,7 @@ pub struct CanisterBuildOpts<'a> { pub fn build_canisters(canisters: Vec) { println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-env-changed=FORCE_BUILD"); for canister in canisters { let wasm_path = match canister.ty { diff --git a/scripts/test_run.sh b/scripts/test_run.sh index ee00043..ccf354c 100755 --- a/scripts/test_run.sh +++ b/scripts/test_run.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e EXAMPLES_DIR="examples" RUN_DURATION=5 @@ -11,7 +12,7 @@ for fuzzer_path in "$EXAMPLES_DIR"/*; do echo "Building fuzzer: $fuzzer_name" cargo build --release -p "$fuzzer_name" echo "Running fuzzer: $fuzzer_name" - $TIMEOUT_CMD "${RUN_DURATION}s" cargo run --release -p "$fuzzer_name" + CANISTER_FORCE_BUILD=1 $TIMEOUT_CMD "${RUN_DURATION}s" cargo run --release -p "$fuzzer_name" || [ $? -eq 124 ] fi done From 051dbdf0b133444e36080d876830a8f19e885b1d Mon Sep 17 00:00:00 2001 From: Venkkatesh Sekar Date: Thu, 8 Jan 2026 15:15:10 +0000 Subject: [PATCH 5/7] remove println! --- canfuzz/src/instrumentation.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/canfuzz/src/instrumentation.rs b/canfuzz/src/instrumentation.rs index eda7a52..93c929f 100644 --- a/canfuzz/src/instrumentation.rs +++ b/canfuzz/src/instrumentation.rs @@ -108,7 +108,6 @@ fn instrument_for_afl( .map(|m| m.ty.memory64) .unwrap_or(false); - println!("Memory type : {:?}", is_memory64); let (afl_prev_loc_indices, afl_mem_ptr_idx) = inject_globals(module, instrumentation_args.history_size, is_memory64); println!( From 2525bdb6e02fb985fc6f7d75a8317a122573af4b Mon Sep 17 00:00:00 2001 From: Venkkatesh Sekar Date: Thu, 8 Jan 2026 15:16:47 +0000 Subject: [PATCH 6/7] fix env --- build_canister/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_canister/src/lib.rs b/build_canister/src/lib.rs index ce4a55f..f34642b 100644 --- a/build_canister/src/lib.rs +++ b/build_canister/src/lib.rs @@ -17,7 +17,7 @@ pub struct CanisterBuildOpts<'a> { pub fn build_canisters(canisters: Vec) { println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-env-changed=FORCE_BUILD"); + println!("cargo:rerun-if-env-changed=CANISTER_FORCE_BUILD"); for canister in canisters { let wasm_path = match canister.ty { From 586a8f0222f324813e6e91dd2ca916c6564bcee1 Mon Sep 17 00:00:00 2001 From: Venkkatesh Sekar Date: Thu, 8 Jan 2026 15:22:16 +0000 Subject: [PATCH 7/7] fix build --- scripts/test_run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test_run.sh b/scripts/test_run.sh index ccf354c..953c4d2 100755 --- a/scripts/test_run.sh +++ b/scripts/test_run.sh @@ -10,7 +10,7 @@ for fuzzer_path in "$EXAMPLES_DIR"/*; do if [ -d "$fuzzer_path" ]; then fuzzer_name=$(basename "$fuzzer_path") echo "Building fuzzer: $fuzzer_name" - cargo build --release -p "$fuzzer_name" + CANISTER_FORCE_BUILD=1 cargo build --release -p "$fuzzer_name" echo "Running fuzzer: $fuzzer_name" CANISTER_FORCE_BUILD=1 $TIMEOUT_CMD "${RUN_DURATION}s" cargo run --release -p "$fuzzer_name" || [ $? -eq 124 ] fi