diff --git a/Cargo.lock b/Cargo.lock index e5c1449bfdf..0f612c2e3a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3568,6 +3568,7 @@ dependencies = [ "noirc_evaluator", "noirc_frontend", "rand 0.8.5", + "serde", "thiserror 1.0.69", ] @@ -5490,6 +5491,7 @@ name = "ssa_fuzzer_fuzz" version = "0.0.1" dependencies = [ "acvm", + "bincode", "env_logger", "libfuzzer-sys", "log", @@ -5497,6 +5499,8 @@ dependencies = [ "noir_ssa_fuzzer", "noirc_driver", "noirc_evaluator", + "rand 0.8.5", + "serde", ] [[package]] diff --git a/tooling/ssa_fuzzer/Cargo.toml b/tooling/ssa_fuzzer/Cargo.toml index 7400c20a96e..4e86b55ec91 100644 --- a/tooling/ssa_fuzzer/Cargo.toml +++ b/tooling/ssa_fuzzer/Cargo.toml @@ -22,6 +22,7 @@ acvm.workspace = true thiserror.workspace = true libfuzzer-sys = { workspace = true, features = ["arbitrary-derive"] } log.workspace = true +serde.workspace = true [dev-dependencies] rand.workspace = true diff --git a/tooling/ssa_fuzzer/fuzzer/Cargo.toml b/tooling/ssa_fuzzer/fuzzer/Cargo.toml index 1486eb798e8..33b4057b479 100644 --- a/tooling/ssa_fuzzer/fuzzer/Cargo.toml +++ b/tooling/ssa_fuzzer/fuzzer/Cargo.toml @@ -16,6 +16,9 @@ acvm.workspace = true noir_ssa_executor.workspace = true log.workspace = true env_logger.workspace = true +bincode = "1.3" +rand.workspace = true +serde.workspace = true [dependencies.noir_ssa_fuzzer] path = "../" diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/base_context.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/base_context.rs index 698ef62a422..42289281d24 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/base_context.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/base_context.rs @@ -12,6 +12,7 @@ use noir_ssa_fuzzer::{ }; use noirc_driver::CompiledProgram; use noirc_evaluator::ssa::ir::basic_block::BasicBlockId; +use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet, VecDeque}, hash::Hash, @@ -24,7 +25,7 @@ const NUMBER_OF_BLOCKS_INSERTING_IN_LOOP: usize = 4; /// Represents set of commands for the fuzzer /// /// After executing all commands, terminates all blocks from current_block_queue with return -#[derive(Arbitrary, Debug, Clone, Hash)] +#[derive(Arbitrary, Debug, Clone, Hash, Serialize, Deserialize)] pub(crate) enum FuzzerCommand { /// Adds instructions to current_block_context from stored instruction_blocks InsertSimpleInstructionBlock { instruction_block_idx: usize }, diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzz_target_lib.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzz_target_lib.rs index 206a9eebea7..39c1255810d 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzz_target_lib.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzz_target_lib.rs @@ -1,20 +1,21 @@ -use super::NUMBER_OF_VARIABLES_INITIAL; use super::base_context::FuzzerCommand; use super::fuzzer::Fuzzer; use super::instruction::InstructionBlock; use super::options::FuzzerOptions; +use super::{NUMBER_OF_PREDEFINED_VARIABLES, NUMBER_OF_VARIABLES_INITIAL}; use acvm::FieldElement; use acvm::acir::native_types::{Witness, WitnessMap}; use libfuzzer_sys::arbitrary; use libfuzzer_sys::arbitrary::Arbitrary; use noir_ssa_fuzzer::typed_value::ValueType; +use serde::{Deserialize, Serialize}; /// Field modulus has 254 bits, and FieldElement::from supports u128, so we use two unsigneds to represent a field element /// field = low + high * 2^128 -#[derive(Debug, Clone, Hash, Arbitrary)] +#[derive(Debug, Clone, Hash, Arbitrary, Serialize, Deserialize)] pub(crate) struct FieldRepresentation { - high: u128, - low: u128, + pub(crate) high: u128, + pub(crate) low: u128, } impl From<&FieldRepresentation> for FieldElement { @@ -25,7 +26,7 @@ impl From<&FieldRepresentation> for FieldElement { } } -#[derive(Debug, Clone, Hash, Arbitrary)] +#[derive(Debug, Clone, Hash, Arbitrary, Serialize, Deserialize)] pub(crate) enum WitnessValue { Field(FieldRepresentation), U64(u64), @@ -37,15 +38,26 @@ pub(crate) enum WitnessValue { /// Represents the data for the fuzzer /// `methods` - sequence of instructions to be added to the program /// `initial_witness` - initial witness values for the program as `FieldRepresentation` -#[derive(Arbitrary, Debug)] +#[derive(Arbitrary, Debug, Serialize, Deserialize)] pub(crate) struct FuzzerData { - blocks: Vec, - commands: Vec, + pub(crate) blocks: Vec, + pub(crate) commands: Vec, /// initial witness values for the program as `WitnessValue` - /// last and last but one values are preserved for the boolean values (true, false) - /// ↓ we subtract 2, because [initialize_witness_map] func inserts two boolean variables itself - initial_witness: [WitnessValue; (NUMBER_OF_VARIABLES_INITIAL - 2) as usize], - return_instruction_block_idx: usize, + pub(crate) initial_witness: + [WitnessValue; (NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES) as usize], + pub(crate) return_instruction_block_idx: usize, +} + +impl Default for FuzzerData { + fn default() -> Self { + Self { + blocks: vec![], + commands: vec![], + initial_witness: [const { WitnessValue::U64(0) }; + (NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES) as usize], + return_instruction_block_idx: 0, + } + } } fn initialize_witness_map( @@ -67,10 +79,16 @@ fn initialize_witness_map( types.push(type_); } // insert true and false boolean values - witness_map.insert(Witness(NUMBER_OF_VARIABLES_INITIAL - 2), FieldElement::from(1_u32)); + witness_map.insert( + Witness(NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES), + FieldElement::from(1_u32), + ); values.push(FieldElement::from(1_u32)); types.push(ValueType::Boolean); - witness_map.insert(Witness(NUMBER_OF_VARIABLES_INITIAL - 1), FieldElement::from(0_u32)); + witness_map.insert( + Witness(NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES + 1), + FieldElement::from(0_u32), + ); values.push(FieldElement::from(0_u32)); types.push(ValueType::Boolean); (witness_map, values, types) diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs index 233e2abe8bf..bdf351c199a 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs @@ -160,7 +160,8 @@ impl Fuzzer { (Err(acir_error), Err(brillig_error)) => { log::debug!("ACIR compilation error: {:?}", acir_error); log::debug!("Brillig compilation error: {:?}", brillig_error); - panic!("ACIR and Brillig compilation failed"); + log::debug!("ACIR and Brillig compilation failed"); + return None; } (Ok(acir), Err(brillig_error)) => { let acir_result = execute_single(&acir.program, initial_witness); diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/instruction.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/instruction.rs index a4522d43a3e..c8a2b5fb1d5 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/instruction.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/instruction.rs @@ -1,8 +1,9 @@ use libfuzzer_sys::arbitrary; use libfuzzer_sys::arbitrary::Arbitrary; use noir_ssa_fuzzer::typed_value::ValueType; +use serde::{Deserialize, Serialize}; -#[derive(Arbitrary, Debug, Clone, Copy)] +#[derive(Arbitrary, Debug, Clone, Copy, Serialize, Deserialize)] pub(crate) struct Argument { /// Index of the argument in the context of stored variables of this type /// e.g. if we have variables with ids [0, 1] in u64 vector and variables with ids [5, 8] in fields vector @@ -17,7 +18,7 @@ pub(crate) struct Argument { /// Represents set of instructions /// /// For operations that take two arguments we ignore type of the second argument. -#[derive(Arbitrary, Debug, Clone, Copy)] +#[derive(Arbitrary, Debug, Clone, Copy, Serialize, Deserialize)] pub(crate) enum Instruction { /// Addition of two values AddChecked { lhs: Argument, rhs: Argument }, @@ -71,7 +72,7 @@ pub(crate) enum Instruction { /// Represents set of instructions /// NOT EQUAL TO SSA BLOCK -#[derive(Arbitrary, Debug, Clone)] +#[derive(Arbitrary, Debug, Clone, Serialize, Deserialize)] pub(crate) struct InstructionBlock { pub(crate) instructions: Vec, } diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/mod.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/mod.rs index c56d02daee5..51b27cd4c1e 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/mod.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/mod.rs @@ -6,3 +6,5 @@ pub(crate) mod instruction; pub(crate) mod options; pub(crate) const NUMBER_OF_VARIABLES_INITIAL: u32 = 7; +/// Numbers of variables that are predefined in the fuzzer +pub(crate) const NUMBER_OF_PREDEFINED_VARIABLES: u32 = 2; diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs index f2fc4d7dc2e..90c7d277b55 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs @@ -1,11 +1,16 @@ #![no_main] mod fuzz_lib; +mod mutations; + +use bincode::{deserialize, serialize}; use fuzz_lib::fuzz_target_lib::{FuzzerData, fuzz_target}; -use fuzz_lib::options::{FuzzerCommandOptions, FuzzerOptions, InstructionOptions}; +use fuzz_lib::options::{FuzzerOptions, InstructionOptions}; +use mutations::mutate; use noirc_driver::CompileOptions; +use rand::{SeedableRng, rngs::StdRng}; -libfuzzer_sys::fuzz_target!(|data: FuzzerData| { +libfuzzer_sys::fuzz_target!(|data: &[u8]| { let _ = env_logger::try_init(); let mut compile_options = CompileOptions::default(); if let Ok(triage_value) = std::env::var("TRIAGE") { @@ -26,22 +31,24 @@ libfuzzer_sys::fuzz_target!(|data: FuzzerData| { // Disable some instructions with bugs that are not fixed yet let instruction_options = InstructionOptions { - cast_enabled: true, - lt_enabled: true, shl_enabled: false, shr_enabled: false, - mod_enabled: true, ..InstructionOptions::default() }; - let options = FuzzerOptions { - constrain_idempotent_morphing_enabled: false, - constant_execution_enabled: false, - compile_options, - max_ssa_blocks_num: 30, // it takes too long to run program with more blocks - max_instructions_num: 1500, // it takes too long to run program with more instructions - max_iterations_num: 10000, - instruction_options, - fuzzer_command_options: FuzzerCommandOptions::default(), - }; + let options = + FuzzerOptions { compile_options, instruction_options, ..FuzzerOptions::default() }; + let data = deserialize(data).unwrap_or_default(); fuzz_target(data, options); }); + +libfuzzer_sys::fuzz_mutator!(|data: &mut [u8], _size: usize, max_size: usize, seed: u32| { + let mut rng = StdRng::seed_from_u64(seed as u64); + let mut new_fuzzer_data: FuzzerData = deserialize(data).unwrap_or_default(); + new_fuzzer_data = mutate(new_fuzzer_data, &mut rng); + let new_bytes = serialize(&new_fuzzer_data).unwrap(); + if new_bytes.len() > max_size { + return 0; + } + data[..new_bytes.len()].copy_from_slice(&new_bytes); + new_bytes.len() +}); diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/commands_mutator.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/commands_mutator.rs new file mode 100644 index 00000000000..a87a2fa25e5 --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/commands_mutator.rs @@ -0,0 +1,80 @@ +//! This file contains mechanisms for deterministically mutating a given vector of [FuzzerCommand](crate::fuzz_lib::base_context::FuzzerCommand) values +//! Types of mutations applied: +//! 1. Random (randomly select a new vector of fuzzer commands) +//! 2. Remove command +//! 3. Add command +//! 4. Replace command with random command + +use crate::fuzz_lib::base_context::FuzzerCommand; +use crate::mutations::configuration::{ + BASIC_FUZZER_COMMAND_MUTATION_CONFIGURATION, FuzzerCommandMutationOptions, +}; +use libfuzzer_sys::arbitrary::Unstructured; +use rand::{Rng, rngs::StdRng}; + +trait MutateVecFuzzerCommand { + fn mutate(rng: &mut StdRng, commands: &mut Vec); +} + +/// Return new random vector of fuzzer commands +struct RandomMutation; +impl MutateVecFuzzerCommand for RandomMutation { + fn mutate(rng: &mut StdRng, commands: &mut Vec) { + let mut bytes = [0u8; 128]; + rng.fill(&mut bytes); + *commands = Unstructured::new(&bytes).arbitrary().unwrap(); + } +} + +/// Remove randomly chosen command from the vector +struct RemoveCommandMutation; +impl MutateVecFuzzerCommand for RemoveCommandMutation { + fn mutate(rng: &mut StdRng, commands: &mut Vec) { + if !commands.is_empty() { + commands.remove(rng.gen_range(0..commands.len())); + } + } +} + +/// Add randomly generated command to the vector +struct AddCommandMutation; +impl MutateVecFuzzerCommand for AddCommandMutation { + fn mutate(rng: &mut StdRng, commands: &mut Vec) { + let mut bytes = [0u8; 25]; + rng.fill(&mut bytes); + let command = Unstructured::new(&bytes).arbitrary().unwrap(); + commands.push(command); + } +} + +/// Replace randomly chosen command with randomly generated command +struct ReplaceCommandMutation; +impl MutateVecFuzzerCommand for ReplaceCommandMutation { + fn mutate(rng: &mut StdRng, commands: &mut Vec) { + let mut bytes = [0u8; 25]; + rng.fill(&mut bytes); + let command = Unstructured::new(&bytes).arbitrary().unwrap(); + if !commands.is_empty() { + let command_idx = rng.gen_range(0..commands.len()); + commands[command_idx] = command; + } + } +} + +pub(crate) fn mutate_vec_fuzzer_command( + vec_fuzzer_command: &mut Vec, + rng: &mut StdRng, +) { + match BASIC_FUZZER_COMMAND_MUTATION_CONFIGURATION.select(rng) { + FuzzerCommandMutationOptions::Random => RandomMutation::mutate(rng, vec_fuzzer_command), + FuzzerCommandMutationOptions::RemoveCommand => { + RemoveCommandMutation::mutate(rng, vec_fuzzer_command) + } + FuzzerCommandMutationOptions::AddCommand => { + AddCommandMutation::mutate(rng, vec_fuzzer_command) + } + FuzzerCommandMutationOptions::ReplaceCommand => { + ReplaceCommandMutation::mutate(rng, vec_fuzzer_command) + } + } +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/configuration.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/configuration.rs new file mode 100644 index 00000000000..e45c6e3e11f --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/configuration.rs @@ -0,0 +1,186 @@ +//! This file contains configurations for selecting particular behaviors during mutations +use rand::{Rng, rngs::StdRng}; + +pub(crate) const MAX_NUMBER_OF_MUTATIONS: usize = 25; + +pub(crate) struct WeightedSelectionConfig { + pub(crate) options_with_weights: [(T, usize); N], + pub(crate) total_weight: usize, +} + +impl WeightedSelectionConfig { + pub(crate) const fn new(options_with_weights: [(T, usize); N]) -> Self { + let mut total_weight = 0; + let mut i = 0; + while i < options_with_weights.len() { + total_weight += options_with_weights[i].1; + i += 1; + } + + Self { options_with_weights, total_weight } + } + + pub(crate) fn select(&self, rng: &mut StdRng) -> T { + let mut selector = rng.gen_range(0..self.total_weight); + for (option, weight) in &self.options_with_weights { + if selector < *weight { + return *option; + } + selector -= weight; + } + unreachable!("Should have returned by now") + } +} + +/// Mutations config for single mutation +#[derive(Copy, Clone, Debug)] +pub(crate) enum MutationOptions { + InstructionBlocks, + FuzzerCommands, + Witnesses, +} + +pub(crate) type MutationConfig = WeightedSelectionConfig; +pub(crate) const BASIC_MUTATION_CONFIGURATION: MutationConfig = MutationConfig::new([ + (MutationOptions::InstructionBlocks, 1), + (MutationOptions::FuzzerCommands, 1), + (MutationOptions::Witnesses, 1), +]); + +/// Mutations of witness values +#[derive(Copy, Clone, Debug)] +pub(crate) enum WitnessMutationOptions { + Random, + MaxValue, + MinValue, + SmallAddSub, + PowerOfTwoAddSub, +} + +pub(crate) type WitnessMutationConfig = WeightedSelectionConfig; +pub(crate) const BASIC_WITNESS_MUTATION_CONFIGURATION: WitnessMutationConfig = + WitnessMutationConfig::new([ + (WitnessMutationOptions::Random, 1), + (WitnessMutationOptions::MaxValue, 2), + (WitnessMutationOptions::MinValue, 2), + (WitnessMutationOptions::SmallAddSub, 4), + (WitnessMutationOptions::PowerOfTwoAddSub, 3), + ]); + +/// Mutations of fuzzer commands +#[derive(Copy, Clone, Debug)] +pub(crate) enum FuzzerCommandMutationOptions { + Random, + RemoveCommand, + AddCommand, + ReplaceCommand, +} + +pub(crate) type FuzzerCommandMutationConfig = + WeightedSelectionConfig; +pub(crate) const BASIC_FUZZER_COMMAND_MUTATION_CONFIGURATION: FuzzerCommandMutationConfig = + FuzzerCommandMutationConfig::new([ + (FuzzerCommandMutationOptions::Random, 1), + (FuzzerCommandMutationOptions::RemoveCommand, 2), + (FuzzerCommandMutationOptions::AddCommand, 4), + (FuzzerCommandMutationOptions::ReplaceCommand, 3), + ]); + +/// Mutations of vector of instruction blocks +#[derive(Copy, Clone, Debug)] +pub(crate) enum VectorOfInstructionBlocksMutationOptions { + Random, + InstructionBlockDeletion, + InstructionBlockInsertion, + InstructionBlockMutation, + InstructionBlockSwap, +} + +pub(crate) type VectorOfInstructionBlocksMutationConfig = + WeightedSelectionConfig; +pub(crate) const BASIC_VECTOR_OF_INSTRUCTION_BLOCKS_MUTATION_CONFIGURATION: + VectorOfInstructionBlocksMutationConfig = VectorOfInstructionBlocksMutationConfig::new([ + (VectorOfInstructionBlocksMutationOptions::Random, 1), + (VectorOfInstructionBlocksMutationOptions::InstructionBlockDeletion, 15), + (VectorOfInstructionBlocksMutationOptions::InstructionBlockInsertion, 15), + (VectorOfInstructionBlocksMutationOptions::InstructionBlockMutation, 55), + (VectorOfInstructionBlocksMutationOptions::InstructionBlockSwap, 15), +]); + +/// Mutations of single instruction block +#[derive(Copy, Clone, Debug)] +pub(crate) enum InstructionBlockMutationOptions { + Random, + InstructionDeletion, + InstructionInsertion, + InstructionMutation, + InstructionSwap, +} + +pub(crate) type InstructionBlockMutationConfig = + WeightedSelectionConfig; +pub(crate) const BASIC_INSTRUCTION_BLOCK_MUTATION_CONFIGURATION: InstructionBlockMutationConfig = + InstructionBlockMutationConfig::new([ + (InstructionBlockMutationOptions::Random, 1), + (InstructionBlockMutationOptions::InstructionDeletion, 15), + (InstructionBlockMutationOptions::InstructionInsertion, 15), + (InstructionBlockMutationOptions::InstructionMutation, 55), + (InstructionBlockMutationOptions::InstructionSwap, 15), + ]); + +/// Mutations of instructions +#[derive(Copy, Clone, Debug)] +pub(crate) enum InstructionMutationOptions { + Random, + ArgumentMutation, +} + +pub(crate) type InstructionMutationConfig = WeightedSelectionConfig; +pub(crate) const BASIC_INSTRUCTION_MUTATION_CONFIGURATION: InstructionMutationConfig = + InstructionMutationConfig::new([ + (InstructionMutationOptions::Random, 1), + (InstructionMutationOptions::ArgumentMutation, 4), + ]); + +/// Instruction argument mutation configuration +#[derive(Copy, Clone, Debug)] +pub(crate) enum InstructionArgumentMutationOptions { + Left, + Right, +} + +pub(crate) type InstructionArgumentMutationConfig = + WeightedSelectionConfig; +pub(crate) const BASIC_INSTRUCTION_ARGUMENT_MUTATION_CONFIGURATION: + InstructionArgumentMutationConfig = InstructionArgumentMutationConfig::new([ + (InstructionArgumentMutationOptions::Left, 1), + (InstructionArgumentMutationOptions::Right, 1), +]); + +/// Mutations of arguments of instructions +#[derive(Copy, Clone, Debug)] +pub(crate) enum ArgumentMutationOptions { + Random, + IncrementIndex, + DecrementIndex, + ChangeType, +} + +pub(crate) type ArgumentMutationConfig = WeightedSelectionConfig; +pub(crate) const BASIC_ARGUMENT_MUTATION_CONFIGURATION: ArgumentMutationConfig = + ArgumentMutationConfig::new([ + (ArgumentMutationOptions::Random, 1), + (ArgumentMutationOptions::IncrementIndex, 3), + (ArgumentMutationOptions::DecrementIndex, 3), + (ArgumentMutationOptions::ChangeType, 2), + ]); + +/// Mutations of value types +#[derive(Copy, Clone, Debug)] +pub(crate) enum ValueTypeMutationOptions { + Random, +} + +pub(crate) type ValueTypeMutationConfig = WeightedSelectionConfig; +pub(crate) const BASIC_VALUE_TYPE_MUTATION_CONFIGURATION: ValueTypeMutationConfig = + ValueTypeMutationConfig::new([(ValueTypeMutationOptions::Random, 1)]); diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/argument_mutator.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/argument_mutator.rs new file mode 100644 index 00000000000..009b9db9bd3 --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/argument_mutator.rs @@ -0,0 +1,65 @@ +//! This file contains mechanisms for deterministically mutating a given [Argument](crate::fuzz_lib::instruction::Argument) value +//! Types of mutations applied: +//! 1. Random (randomly select a new argument value) +//! 2. Increment index +//! 3. Decrement index +//! 4. Change type + +use crate::fuzz_lib::instruction::Argument; +use crate::mutations::{ + configuration::{ArgumentMutationOptions, BASIC_ARGUMENT_MUTATION_CONFIGURATION}, + instructions::type_mutations::type_mutator, +}; +use libfuzzer_sys::arbitrary::Unstructured; +use rand::{Rng, rngs::StdRng}; + +trait ArgumentsMutator { + fn mutate(rng: &mut StdRng, value: &mut Argument); +} + +/// Return new random argument +struct RandomMutation; +impl ArgumentsMutator for RandomMutation { + fn mutate(rng: &mut StdRng, value: &mut Argument) { + let mut bytes = [0u8; 17]; + rng.fill(&mut bytes); + *value = Unstructured::new(&bytes).arbitrary().unwrap(); + } +} + +/// Increment index of the argument +struct IncrementArgumentIndexMutation; +impl ArgumentsMutator for IncrementArgumentIndexMutation { + fn mutate(_rng: &mut StdRng, value: &mut Argument) { + value.index = value.index.saturating_add(1); + } +} + +/// Decrement index of the argument +struct DecrementArgumentIndexMutation; +impl ArgumentsMutator for DecrementArgumentIndexMutation { + fn mutate(_rng: &mut StdRng, value: &mut Argument) { + value.index = value.index.saturating_sub(1); + } +} + +/// Change type of the argument +struct ChangeTypeMutation; +impl ArgumentsMutator for ChangeTypeMutation { + fn mutate(rng: &mut StdRng, value: &mut Argument) { + type_mutator(&mut value.value_type, rng); + } +} + +pub(crate) fn argument_mutator(argument: &mut Argument, rng: &mut StdRng) { + match BASIC_ARGUMENT_MUTATION_CONFIGURATION.select(rng) { + ArgumentMutationOptions::Random => RandomMutation::mutate(rng, argument), + ArgumentMutationOptions::IncrementIndex => { + IncrementArgumentIndexMutation::mutate(rng, argument) + } + ArgumentMutationOptions::DecrementIndex => { + DecrementArgumentIndexMutation::mutate(rng, argument) + } + ArgumentMutationOptions::ChangeType => ChangeTypeMutation::mutate(rng, argument), + } +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_block_mutator.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_block_mutator.rs new file mode 100644 index 00000000000..236b053cd1d --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_block_mutator.rs @@ -0,0 +1,102 @@ +//! This file contains mechanisms for deterministically mutating a given [InstructionBlock](crate::fuzz_lib::instruction::InstructionBlock) value +//! Types of mutations applied: +//! 1. Random (randomly select a new instruction block) +//! 2. Instruction deletion +//! 3. Instruction insertion +//! 4. Instruction mutation + +use crate::fuzz_lib::instruction::InstructionBlock; +use crate::mutations::configuration::{ + BASIC_INSTRUCTION_BLOCK_MUTATION_CONFIGURATION, InstructionBlockMutationOptions, +}; +use crate::mutations::instructions::instruction_mutator::instruction_mutator; +use libfuzzer_sys::arbitrary::Unstructured; +use rand::{Rng, rngs::StdRng}; + +trait InstructionBlockMutator { + fn mutate(rng: &mut StdRng, value: &mut InstructionBlock); +} + +/// Return new random instruction block +struct RandomMutation; +impl InstructionBlockMutator for RandomMutation { + fn mutate(rng: &mut StdRng, value: &mut InstructionBlock) { + let mut bytes = [0u8; 25]; + rng.fill(&mut bytes); + *value = Unstructured::new(&bytes).arbitrary().unwrap(); + } +} + +/// Remove randomly chosen instruction from the block +struct InstructionBlockDeletionMutation; +impl InstructionBlockMutator for InstructionBlockDeletionMutation { + fn mutate(rng: &mut StdRng, value: &mut InstructionBlock) { + let blocks = &mut value.instructions; + if !blocks.is_empty() { + let block_idx = rng.gen_range(0..blocks.len()); + blocks.remove(block_idx); + } + } +} + +/// Insert randomly generated instruction into the block +struct InstructionBlockInsertionMutation; +impl InstructionBlockMutator for InstructionBlockInsertionMutation { + fn mutate(rng: &mut StdRng, value: &mut InstructionBlock) { + let mut bytes = [0u8; 25]; + rng.fill(&mut bytes); + let mut unstructured = Unstructured::new(&bytes); + let instruction = unstructured.arbitrary().unwrap(); + let blocks = &mut value.instructions; + blocks.insert( + if blocks.is_empty() { 0 } else { rng.gen_range(0..blocks.len()) }, + instruction, + ); + } +} + +/// Mutate randomly chosen instruction in the block +struct InstructionBlockInstructionMutation; +impl InstructionBlockMutator for InstructionBlockInstructionMutation { + fn mutate(rng: &mut StdRng, value: &mut InstructionBlock) { + let instructions = &mut value.instructions; + if !instructions.is_empty() { + let instruction_idx = rng.gen_range(0..instructions.len()); + instruction_mutator(&mut instructions[instruction_idx], rng); + } + } +} + +/// Swap randomly chosen instruction in the block +struct InstructionBlockInstructionSwapMutation; +impl InstructionBlockMutator for InstructionBlockInstructionSwapMutation { + fn mutate(rng: &mut StdRng, value: &mut InstructionBlock) { + let instructions = &mut value.instructions; + if !instructions.is_empty() { + let instruction_idx_1 = rng.gen_range(0..instructions.len()); + let instruction_idx_2 = rng.gen_range(0..instructions.len()); + instructions.swap(instruction_idx_1, instruction_idx_2); + } + } +} + +pub(crate) fn instruction_block_mutator( + instruction_block: &mut InstructionBlock, + rng: &mut StdRng, +) { + match BASIC_INSTRUCTION_BLOCK_MUTATION_CONFIGURATION.select(rng) { + InstructionBlockMutationOptions::Random => RandomMutation::mutate(rng, instruction_block), + InstructionBlockMutationOptions::InstructionDeletion => { + InstructionBlockDeletionMutation::mutate(rng, instruction_block) + } + InstructionBlockMutationOptions::InstructionInsertion => { + InstructionBlockInsertionMutation::mutate(rng, instruction_block) + } + InstructionBlockMutationOptions::InstructionMutation => { + InstructionBlockInstructionMutation::mutate(rng, instruction_block) + } + InstructionBlockMutationOptions::InstructionSwap => { + InstructionBlockInstructionSwapMutation::mutate(rng, instruction_block) + } + } +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_mutator.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_mutator.rs new file mode 100644 index 00000000000..3549c112f7d --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_mutator.rs @@ -0,0 +1,119 @@ +//! This file contains mechanisms for deterministically mutating a given [Instruction](crate::fuzz_lib::instruction::Instruction) value +//! Types of mutations applied: +//! 1. Random (randomly select a new instruction) +//! 2. Argument mutation + +use crate::fuzz_lib::instruction::{Argument, Instruction}; +use crate::mutations::configuration::{ + BASIC_INSTRUCTION_ARGUMENT_MUTATION_CONFIGURATION, BASIC_INSTRUCTION_MUTATION_CONFIGURATION, + InstructionArgumentMutationOptions, InstructionMutationOptions, +}; +use crate::mutations::instructions::argument_mutator::argument_mutator; +use crate::mutations::instructions::type_mutations::type_mutator; +use libfuzzer_sys::arbitrary::Unstructured; +use noir_ssa_fuzzer::typed_value::ValueType; +use rand::{Rng, rngs::StdRng}; + +trait InstructionMutator { + fn mutate(rng: &mut StdRng, value: &mut Instruction); +} + +/// Return new random instruction +struct RandomMutation; +impl InstructionMutator for RandomMutation { + fn mutate(rng: &mut StdRng, value: &mut Instruction) { + let mut bytes = [0u8; 17]; + rng.fill(&mut bytes); + *value = Unstructured::new(&bytes).arbitrary().unwrap(); + } +} + +/// Mutate arguments of the instruction +struct InstructionArgumentsMutation; +impl InstructionMutator for InstructionArgumentsMutation { + fn mutate(rng: &mut StdRng, value: &mut Instruction) { + match value { + // Binary operations + Instruction::AddChecked { lhs, rhs } + | Instruction::SubChecked { lhs, rhs } + | Instruction::MulChecked { lhs, rhs } + | Instruction::Div { lhs, rhs } + | Instruction::Eq { lhs, rhs } + | Instruction::Mod { lhs, rhs } + | Instruction::Shl { lhs, rhs } + | Instruction::Shr { lhs, rhs } + | Instruction::And { lhs, rhs } + | Instruction::Or { lhs, rhs } + | Instruction::Xor { lhs, rhs } + | Instruction::Lt { lhs, rhs } => { + match BASIC_INSTRUCTION_ARGUMENT_MUTATION_CONFIGURATION.select(rng) { + InstructionArgumentMutationOptions::Left => { + argument_mutator(lhs, rng); + } + InstructionArgumentMutationOptions::Right => { + argument_mutator(rhs, rng); + } + } + } + + // Unary operations + Instruction::Not { lhs } + | Instruction::AddToMemory { lhs } + | Instruction::LoadFromMemory { memory_addr: lhs } => { + argument_mutator(lhs, rng); + } + + // Special cases + Instruction::Cast { lhs, type_ } => { + match BASIC_INSTRUCTION_ARGUMENT_MUTATION_CONFIGURATION.select(rng) { + InstructionArgumentMutationOptions::Left => { + argument_mutator(lhs, rng); + } + InstructionArgumentMutationOptions::Right => { + type_mutator(type_, rng); + } + } + } + Instruction::SetToMemory { memory_addr_index, value } => { + match BASIC_INSTRUCTION_ARGUMENT_MUTATION_CONFIGURATION.select(rng) { + InstructionArgumentMutationOptions::Left => { + argument_mutator(value, rng); + } + InstructionArgumentMutationOptions::Right => { + let mut pseudo_argument = + Argument { index: *memory_addr_index, value_type: ValueType::U64 }; + argument_mutator(&mut pseudo_argument, rng); + *memory_addr_index = pseudo_argument.index; + } + } + } + + Instruction::AddSubConstrain { lhs, rhs } + | Instruction::MulDivConstrain { lhs, rhs } => { + match BASIC_INSTRUCTION_ARGUMENT_MUTATION_CONFIGURATION.select(rng) { + InstructionArgumentMutationOptions::Left => { + let mut pseudo_argument = + Argument { index: *lhs, value_type: ValueType::U64 }; + argument_mutator(&mut pseudo_argument, rng); + *lhs = pseudo_argument.index; + } + InstructionArgumentMutationOptions::Right => { + let mut pseudo_argument = + Argument { index: *rhs, value_type: ValueType::U64 }; + argument_mutator(&mut pseudo_argument, rng); + *rhs = pseudo_argument.index; + } + } + } + } + } +} + +pub(crate) fn instruction_mutator(instruction: &mut Instruction, rng: &mut StdRng) { + match BASIC_INSTRUCTION_MUTATION_CONFIGURATION.select(rng) { + InstructionMutationOptions::Random => RandomMutation::mutate(rng, instruction), + InstructionMutationOptions::ArgumentMutation => { + InstructionArgumentsMutation::mutate(rng, instruction) + } + } +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/mod.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/mod.rs new file mode 100644 index 00000000000..b6ab1799ca9 --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/mod.rs @@ -0,0 +1,107 @@ +//! This file contains mechanisms for deterministically mutating a given vector of [InstructionBlock](crate::fuzz_lib::instruction::InstructionBlock) values +//! Types of mutations applied: +//! 1. Random (randomly select a new instruction block) +//! 2. Instruction block deletion +//! 3. Instruction block insertion +//! 4. Instruction mutation + +mod argument_mutator; +mod instruction_block_mutator; +mod instruction_mutator; +mod type_mutations; + +use super::configuration::{ + BASIC_VECTOR_OF_INSTRUCTION_BLOCKS_MUTATION_CONFIGURATION, + VectorOfInstructionBlocksMutationOptions, +}; +use crate::fuzz_lib::instruction::InstructionBlock; +use instruction_block_mutator::instruction_block_mutator; +use libfuzzer_sys::arbitrary::Unstructured; +use rand::{Rng, rngs::StdRng}; + +trait MutateVecInstructionBlock { + fn mutate(rng: &mut StdRng, value: &mut Vec); +} + +/// Return new random vector of instruction blocks +struct RandomMutation; +impl MutateVecInstructionBlock for RandomMutation { + fn mutate(rng: &mut StdRng, value: &mut Vec) { + let mut bytes = [0u8; 128]; + rng.fill(&mut bytes); + *value = Unstructured::new(&bytes).arbitrary().unwrap(); + } +} + +/// Return vector of instruction blocks with one randomly chosen block removed +struct InstructionBlockDeletionMutation; +impl MutateVecInstructionBlock for InstructionBlockDeletionMutation { + fn mutate(rng: &mut StdRng, value: &mut Vec) { + let blocks = value; + if !blocks.is_empty() { + let block_idx = rng.gen_range(0..blocks.len()); + blocks.remove(block_idx); + } + } +} + +/// Return vector of instruction blocks with one randomly generated block inserted +struct InstructionBlockInsertionMutation; +impl MutateVecInstructionBlock for InstructionBlockInsertionMutation { + fn mutate(rng: &mut StdRng, value: &mut Vec) { + let blocks = value; + let block_idx = if blocks.is_empty() { 0 } else { rng.gen_range(0..blocks.len()) }; + let mut bytes = [0u8; 25]; + rng.fill(&mut bytes); + let mut unstructured = Unstructured::new(&bytes); + let instruction_block = unstructured.arbitrary().unwrap(); + blocks.insert(block_idx, instruction_block); + } +} + +/// Return vector of instruction blocks with one randomly chosen block mutated +struct InstructionBlockInstructionMutation; +impl MutateVecInstructionBlock for InstructionBlockInstructionMutation { + fn mutate(rng: &mut StdRng, value: &mut Vec) { + let blocks = value; + if !blocks.is_empty() { + let block_idx = rng.gen_range(0..blocks.len()); + instruction_block_mutator(&mut blocks[block_idx], rng); + } + } +} + +struct InstructionBlockInstructionSwapMutation; +impl MutateVecInstructionBlock for InstructionBlockInstructionSwapMutation { + fn mutate(rng: &mut StdRng, value: &mut Vec) { + let blocks = value; + if !blocks.is_empty() { + let block_idx_1 = rng.gen_range(0..blocks.len()); + let block_idx_2 = rng.gen_range(0..blocks.len()); + blocks.swap(block_idx_1, block_idx_2); + } + } +} + +pub(crate) fn mutate_vec_instruction_block( + vec_instruction_block: &mut Vec, + rng: &mut StdRng, +) { + match BASIC_VECTOR_OF_INSTRUCTION_BLOCKS_MUTATION_CONFIGURATION.select(rng) { + VectorOfInstructionBlocksMutationOptions::Random => { + RandomMutation::mutate(rng, vec_instruction_block) + } + VectorOfInstructionBlocksMutationOptions::InstructionBlockDeletion => { + InstructionBlockDeletionMutation::mutate(rng, vec_instruction_block) + } + VectorOfInstructionBlocksMutationOptions::InstructionBlockInsertion => { + InstructionBlockInsertionMutation::mutate(rng, vec_instruction_block) + } + VectorOfInstructionBlocksMutationOptions::InstructionBlockMutation => { + InstructionBlockInstructionMutation::mutate(rng, vec_instruction_block) + } + VectorOfInstructionBlocksMutationOptions::InstructionBlockSwap => { + InstructionBlockInstructionSwapMutation::mutate(rng, vec_instruction_block) + } + } +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/type_mutations.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/type_mutations.rs new file mode 100644 index 00000000000..788f7668ab0 --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/type_mutations.rs @@ -0,0 +1,27 @@ +//! This file contains mechanisms for deterministically mutating a given [ValueType](noir_ssa_fuzzer::typed_value::ValueType) value + +use crate::mutations::configuration::{ + BASIC_VALUE_TYPE_MUTATION_CONFIGURATION, ValueTypeMutationOptions, +}; +use libfuzzer_sys::arbitrary::Unstructured; +use noir_ssa_fuzzer::typed_value::ValueType; +use rand::{Rng, rngs::StdRng}; + +trait TypeMutator { + fn mutate(rng: &mut StdRng, value: &mut ValueType); +} + +struct RandomMutation; +impl TypeMutator for RandomMutation { + fn mutate(rng: &mut StdRng, value: &mut ValueType) { + let mut bytes = [0u8; 17]; + rng.fill(&mut bytes); + *value = Unstructured::new(&bytes).arbitrary().unwrap(); + } +} + +pub(crate) fn type_mutator(value_type: &mut ValueType, rng: &mut StdRng) { + match BASIC_VALUE_TYPE_MUTATION_CONFIGURATION.select(rng) { + ValueTypeMutationOptions::Random => RandomMutation::mutate(rng, value_type), + } +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/mod.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/mod.rs new file mode 100644 index 00000000000..e0ee7e10e05 --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/mod.rs @@ -0,0 +1,36 @@ +mod commands_mutator; +mod configuration; +mod instructions; +mod witness_mutator; + +use crate::fuzz_lib::fuzz_target_lib::FuzzerData; +use crate::mutations::configuration::{ + BASIC_MUTATION_CONFIGURATION, MAX_NUMBER_OF_MUTATIONS, MutationOptions, +}; +use rand::{Rng, rngs::StdRng}; + +pub(crate) fn mutate(data: FuzzerData, rng: &mut StdRng) -> FuzzerData { + let (mut blocks, mut commands, mut initial_witness) = + (data.blocks, data.commands, data.initial_witness); + let number_of_mutations = rng.gen_range(1..MAX_NUMBER_OF_MUTATIONS); + for _ in 0..number_of_mutations { + match BASIC_MUTATION_CONFIGURATION.select(rng) { + MutationOptions::InstructionBlocks => { + instructions::mutate_vec_instruction_block(&mut blocks, rng); + } + MutationOptions::FuzzerCommands => { + commands_mutator::mutate_vec_fuzzer_command(&mut commands, rng); + } + MutationOptions::Witnesses => { + let index = rng.gen_range(0..initial_witness.len()); + witness_mutator::witness_mutate(&mut initial_witness[index], rng); + } + } + } + FuzzerData { + blocks, + commands, + initial_witness, + return_instruction_block_idx: data.return_instruction_block_idx, + } +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/witness_mutator.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/witness_mutator.rs new file mode 100644 index 00000000000..76a7ebc10a1 --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/witness_mutator.rs @@ -0,0 +1,156 @@ +//! This file contains mechanisms for deterministically mutating a given [WitnessValue](crate::fuzz_lib::fuzz_target_lib::WitnessValue) value +//! Types of mutations applied: +//! 1. Random (randomly select a new witness value) +//! 2. Max value +//! 3. Min value + +use crate::fuzz_lib::fuzz_target_lib::{FieldRepresentation, WitnessValue}; +use crate::mutations::configuration::{ + BASIC_WITNESS_MUTATION_CONFIGURATION, WitnessMutationOptions, +}; +use libfuzzer_sys::arbitrary::Unstructured; +use rand::{Rng, rngs::StdRng}; + +trait WitnessMutator { + fn mutate(rng: &mut StdRng, value: &mut WitnessValue); +} + +/// Return new random witness value +struct RandomMutation; +impl WitnessMutator for RandomMutation { + fn mutate(rng: &mut StdRng, value: &mut WitnessValue) { + let mut bytes = [0u8; 17]; + rng.fill(&mut bytes); + *value = Unstructured::new(&bytes).arbitrary().unwrap(); + } +} + +/// Return witness value with max value +struct MaxValueMutation; +impl WitnessMutator for MaxValueMutation { + fn mutate(_rng: &mut StdRng, value: &mut WitnessValue) { + let mutated_value = match value { + WitnessValue::Field(_) => WitnessValue::Field(FieldRepresentation { + high: 64323764613183177041862057485226039389, + low: 53438638232309528389504892708671455232, // high * 2^128 + low = p - 1 + }), + WitnessValue::U64(_) => WitnessValue::U64(u64::MAX), + WitnessValue::Boolean(_) => WitnessValue::Boolean(true), + WitnessValue::I64(_) => WitnessValue::I64((1 << 63) - 1), // 2^63 - 1, sign bit is 0 + WitnessValue::I32(_) => WitnessValue::I32((1 << 31) - 1), // 2^31 - 1, sign bit is 0 + }; + *value = mutated_value; + } +} + +/// Return witness value with min value +struct MinValueMutation; +impl WitnessMutator for MinValueMutation { + fn mutate(_rng: &mut StdRng, value: &mut WitnessValue) { + let mutated_value = match value { + WitnessValue::Field(_) => WitnessValue::Field(FieldRepresentation { high: 0, low: 0 }), + WitnessValue::U64(_) => WitnessValue::U64(0), + WitnessValue::Boolean(_) => WitnessValue::Boolean(false), + WitnessValue::I64(_) => WitnessValue::I64(1 << 63), // 2^63, sign bit is 1 + WitnessValue::I32(_) => WitnessValue::I32(1 << 31), // 2^31, sign bit is 1 + }; + *value = mutated_value; + } +} + +/// Add or subtract small value to/from witness value +struct WitnessSmallAddSubMutation; +impl WitnessMutator for WitnessSmallAddSubMutation { + fn mutate(rng: &mut StdRng, value: &mut WitnessValue) { + let small_update: i128 = rng.gen_range(0..256); + let sign: bool = rng.gen_range(0..2) == 0; + *value = match value { + WitnessValue::Field(field) => WitnessValue::Field(FieldRepresentation { + high: field.high, + low: if !sign { + field.low.wrapping_add(small_update as u128) + } else { + field.low.wrapping_sub(small_update as u128) + }, + }), + WitnessValue::U64(u64) => WitnessValue::U64(if !sign { + u64.wrapping_add(small_update as u64) + } else { + u64.wrapping_sub(small_update as u64) + }), + WitnessValue::I64(i64) => WitnessValue::I64(if !sign { + i64.wrapping_add(small_update as u64) + } else { + i64.wrapping_sub(small_update as u64) + }), + WitnessValue::I32(i32) => WitnessValue::I32(if !sign { + i32.wrapping_add(small_update as u32) + } else { + i32.wrapping_sub(small_update as u32) + }), + WitnessValue::Boolean(bool) => WitnessValue::Boolean(*bool ^ (small_update % 2 == 1)), + } + } +} + +/// Add or subtract power of two to/from witness value +struct WitnessAddSubPowerOfTwoMutation; +impl WitnessMutator for WitnessAddSubPowerOfTwoMutation { + fn mutate(rng: &mut StdRng, value: &mut WitnessValue) { + let exponent = rng.gen_range(0..254); + let sign: bool = rng.gen_range(0..2) == 0; + *value = match value { + WitnessValue::Field(field) => { + // I don't think implementing field addition is worth the effort, so we just + // add the power of two to the high or low part of the field + let is_high = exponent > 127; + let power_of_two: i128 = 1 << (exponent % 128); + + WitnessValue::Field(FieldRepresentation { + high: if !sign { + field.high.wrapping_add((is_high as i128 * power_of_two) as u128) + } else { + field.high.wrapping_sub((is_high as i128 * power_of_two) as u128) + }, + low: if !sign { + field.low.wrapping_add((!is_high as i128 * power_of_two) as u128) + } else { + field.low.wrapping_sub((!is_high as i128 * power_of_two) as u128) + }, + }) + } + WitnessValue::U64(u64) => WitnessValue::U64(if !sign { + u64.wrapping_add(1 << (exponent % 64)) + } else { + u64.wrapping_sub(1 << (exponent % 64)) + }), + WitnessValue::I64(i64) => WitnessValue::I64(if !sign { + i64.wrapping_add(1 << (exponent % 64)) + } else { + i64.wrapping_sub(1 << (exponent % 64)) + }), + WitnessValue::I32(i32) => WitnessValue::I32(if !sign { + i32.wrapping_add(1 << (exponent % 32)) + } else { + i32.wrapping_sub(1 << (exponent % 32)) + }), + WitnessValue::Boolean(bool) => { + WitnessValue::Boolean(*bool ^ (1 << (exponent % 2) == 1)) + } + } + } +} + +pub(crate) fn witness_mutate(witness_value: &mut WitnessValue, rng: &mut StdRng) { + match BASIC_WITNESS_MUTATION_CONFIGURATION.select(rng) { + WitnessMutationOptions::Random => RandomMutation::mutate(rng, witness_value), + WitnessMutationOptions::MaxValue => MaxValueMutation::mutate(rng, witness_value), + WitnessMutationOptions::MinValue => MinValueMutation::mutate(rng, witness_value), + WitnessMutationOptions::SmallAddSub => { + WitnessSmallAddSubMutation::mutate(rng, witness_value) + } + WitnessMutationOptions::PowerOfTwoAddSub => { + WitnessAddSubPowerOfTwoMutation::mutate(rng, witness_value) + } + } +} diff --git a/tooling/ssa_fuzzer/src/typed_value.rs b/tooling/ssa_fuzzer/src/typed_value.rs index 190524ceba0..b5917f5b894 100644 --- a/tooling/ssa_fuzzer/src/typed_value.rs +++ b/tooling/ssa_fuzzer/src/typed_value.rs @@ -2,8 +2,9 @@ use libfuzzer_sys::arbitrary; use libfuzzer_sys::arbitrary::Arbitrary; use noirc_evaluator::ssa::ir::types::{NumericType, Type}; use noirc_evaluator::ssa::ir::{map::Id, value::Value}; +use serde::{Deserialize, Serialize}; -#[derive(Arbitrary, Debug, Clone, PartialEq, Eq, Hash, Copy)] +#[derive(Arbitrary, Debug, Clone, PartialEq, Eq, Hash, Copy, Serialize, Deserialize)] pub enum ValueType { Field, Boolean,