diff --git a/tooling/ssa_fuzzer/fuzzer/Cargo.toml b/tooling/ssa_fuzzer/fuzzer/Cargo.toml index 2fb7f948257..19034c08dcc 100644 --- a/tooling/ssa_fuzzer/fuzzer/Cargo.toml +++ b/tooling/ssa_fuzzer/fuzzer/Cargo.toml @@ -31,6 +31,9 @@ lazy_static = "1.4" [dependencies.noir_ssa_fuzzer] path = "../" +[profile.release] +debug = true + [[bin]] name = "fuzz_target" path = "src/fuzz_target.rs" diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs index 173c56f6424..4847b7a2c41 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs @@ -15,8 +15,8 @@ use super::function_context::{FunctionData, WitnessValue}; use super::instruction::InstructionBlock; -use super::options::{FunctionContextOptions, FuzzerOptions}; -use super::program_context::FuzzerProgramContext; +use super::options::{FuzzerMode, FuzzerOptions}; +use super::program_context::{FuzzerProgramContext, program_context_by_mode}; use super::{NUMBER_OF_PREDEFINED_VARIABLES, NUMBER_OF_VARIABLES_INITIAL}; use acvm::FieldElement; use acvm::acir::native_types::{WitnessMap, WitnessStack}; @@ -26,6 +26,7 @@ use noir_ssa_fuzzer::runner::{CompareResults, run_and_compare}; use noir_ssa_fuzzer::typed_value::ValueType; use noirc_driver::CompiledProgram; use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; #[derive(Arbitrary, Debug, Clone, Serialize, Deserialize)] pub(crate) struct FuzzerData { @@ -47,11 +48,10 @@ impl Default for FuzzerData { } pub(crate) struct Fuzzer { - pub(crate) context_non_constant: Option, - pub(crate) context_non_constant_with_idempotent_morphing: Option, - pub(crate) context_constant: Option, + pub(crate) contexts: Vec, } +#[derive(Clone, Debug)] pub(crate) struct FuzzerOutput { pub(crate) witness_stack: WitnessStack, pub(crate) program: CompiledProgram, @@ -70,96 +70,55 @@ impl Fuzzer { values: Vec, options: FuzzerOptions, ) -> Self { - let context_constant = if options.constant_execution_enabled { - Some(FuzzerProgramContext::new_constant_context( - FunctionContextOptions { - idempotent_morphing_enabled: false, - ..FunctionContextOptions::from(&options) - }, + let mut contexts = vec![]; + for mode in &options.modes { + contexts.push(program_context_by_mode( + mode.clone(), instruction_blocks.clone(), values.clone(), - )) - } else { - None - }; - let context_non_constant = Some(FuzzerProgramContext::new( - FunctionContextOptions { - idempotent_morphing_enabled: false, - ..FunctionContextOptions::from(&options) - }, - instruction_blocks.clone(), - values.clone(), - )); - let context_non_constant_with_idempotent_morphing = - if options.constrain_idempotent_morphing_enabled { - Some(FuzzerProgramContext::new( - FunctionContextOptions { - idempotent_morphing_enabled: true, - ..FunctionContextOptions::from(&options) - }, - instruction_blocks.clone(), - values.clone(), - )) - } else { - None - }; - Self { - context_non_constant, - context_non_constant_with_idempotent_morphing, - context_constant, + options.clone(), + )); } + Self { contexts } } pub(crate) fn process_function(&mut self, function_data: FunctionData, types: Vec) { - if let Some(context) = &mut self.context_non_constant { + for context in &mut self.contexts { context.process_function(function_data.clone(), types.clone()); } - if let Some(context) = &mut self.context_non_constant_with_idempotent_morphing { - context.process_function(function_data.clone(), types.clone()); - } - if let Some(context) = &mut self.context_constant { - context.process_function(function_data, types); - } } - /// Finalizes the function for both contexts, executes and compares the results + /// Finalizes the function for contexts, executes and compares the results pub(crate) fn finalize_and_run( - mut self, + self, initial_witness: WitnessMap, ) -> Option { - let mut non_constant_context = self.context_non_constant.take().unwrap(); - non_constant_context.finalize_program(); - let non_constant_result = - Self::execute_and_compare(non_constant_context, initial_witness.clone()); - - if let Some(context) = self.context_constant.take() { - let mut constant_context = context; - constant_context.finalize_program(); - let constant_result = - Self::execute_and_compare(constant_context, initial_witness.clone()); - if non_constant_result.is_some() { - assert_eq!( - non_constant_result.as_ref().unwrap().get_return_value(), - constant_result?.get_return_value(), - "Non-constant and constant contexts should return the same result" - ); - } + let mut execution_results: HashMap> = HashMap::new(); + for mut context in self.contexts { + context.finalize_program(); + execution_results.insert( + context.get_mode(), + Self::execute_and_compare(context, initial_witness.clone()), + ); } + let results_set = execution_results + .values() + .map(|result| -> Option { result.as_ref().map(|r| r.get_return_value()) }) + .collect::>(); - if let Some(context) = self.context_non_constant_with_idempotent_morphing.take() { - let mut context_with_idempotent_morphing = context; - context_with_idempotent_morphing.finalize_program(); - let result_with_constrains = - Self::execute_and_compare(context_with_idempotent_morphing, initial_witness); - if non_constant_result.is_some() { - assert_eq!( - non_constant_result.as_ref().unwrap().get_return_value(), - result_with_constrains?.get_return_value(), - "Non-constant and idempotent morphing contexts should return the same result" - ); + if results_set.len() != 1 { + let mut panic_string = String::new(); + for (mode, result) in execution_results { + if let Some(result) = result { + panic_string + .push_str(&format!("Mode {mode:?}: {:?}\n", result.get_return_value())); + } else { + panic_string.push_str(&format!("Mode {mode:?} failed\n")); + } } + panic!("Fuzzer modes returned different results:\n{panic_string}"); } - non_constant_result + execution_results.values().next().unwrap().clone() } fn execute_and_compare( diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs index dc28f9dcd07..37bb808e7c4 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs @@ -108,29 +108,42 @@ impl Default for FuzzerCommandOptions { } } +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub(crate) enum FuzzerMode { + /// Standard mode + NonConstant, + /// Every argument of the program changed to its value (for constant folding) + #[allow(dead_code)] + Constant, + /// Standard mode with idempotent operations (e.g. res = a + b - b) + #[allow(dead_code)] + NonConstantWithIdempotentMorphing, + /// Standard mode without DIE SSA passes + #[allow(dead_code)] + NonConstantWithoutDIE, +} + #[derive(Clone)] pub(crate) struct FuzzerOptions { - pub(crate) constrain_idempotent_morphing_enabled: bool, - pub(crate) constant_execution_enabled: bool, pub(crate) compile_options: CompileOptions, pub(crate) max_ssa_blocks_num: usize, pub(crate) max_instructions_num: usize, pub(crate) max_iterations_num: usize, pub(crate) instruction_options: InstructionOptions, pub(crate) fuzzer_command_options: FuzzerCommandOptions, + pub(crate) modes: Vec, } impl Default for FuzzerOptions { fn default() -> Self { Self { - constrain_idempotent_morphing_enabled: false, - constant_execution_enabled: false, compile_options: CompileOptions { show_ssa: false, ..Default::default() }, max_ssa_blocks_num: 100, max_instructions_num: 1000, max_iterations_num: 1000, instruction_options: InstructionOptions::default(), fuzzer_command_options: FuzzerCommandOptions::default(), + modes: vec![FuzzerMode::NonConstant], } } } diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/program_context.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/program_context.rs index 9f94df8fb7b..942ca0033e9 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/program_context.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/program_context.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use super::function_context::{FunctionData, FuzzerFunctionContext}; use super::instruction::{FunctionSignature, InstructionBlock}; -use super::options::FunctionContextOptions; +use super::options::{FunctionContextOptions, FuzzerMode, FuzzerOptions}; use acvm::FieldElement; use noir_ssa_fuzzer::{ builder::{FuzzerBuilder, FuzzerBuilderError}, @@ -41,14 +41,17 @@ pub(crate) struct FuzzerProgramContext { /// /// Used for the constant mode (to replace variables in the main function with constants) values: Vec, + + mode: FuzzerMode, } impl FuzzerProgramContext { /// Creates a new FuzzerProgramContext - pub(crate) fn new( + fn new( program_context_options: FunctionContextOptions, instruction_blocks: Vec, values: Vec, + mode: FuzzerMode, ) -> Self { let acir_builder = FuzzerBuilder::new_acir(); let brillig_builder = FuzzerBuilder::new_brillig(); @@ -63,14 +66,16 @@ impl FuzzerProgramContext { instruction_blocks, is_main_initialized: false, values, + mode, } } /// Creates a new FuzzerProgramContext where all inputs are constants - pub(crate) fn new_constant_context( + fn new_constant_context( program_context_options: FunctionContextOptions, instruction_blocks: Vec, values: Vec, + mode: FuzzerMode, ) -> Self { let acir_builder = FuzzerBuilder::new_acir(); let brillig_builder = FuzzerBuilder::new_brillig(); @@ -85,6 +90,7 @@ impl FuzzerProgramContext { instruction_blocks, is_main_initialized: false, values, + mode, } } @@ -159,4 +165,60 @@ impl FuzzerProgramContext { self.brillig_builder.compile(self.program_context_options.compile_options), ) } + + pub(crate) fn get_mode(&self) -> FuzzerMode { + self.mode.clone() + } +} + +/// Creates [`FuzzerProgramContext`] from [`FuzzerMode`] +pub(crate) fn program_context_by_mode( + mode: FuzzerMode, + instruction_blocks: Vec, + values: Vec, + options: FuzzerOptions, +) -> FuzzerProgramContext { + match mode { + FuzzerMode::Constant => FuzzerProgramContext::new_constant_context( + FunctionContextOptions { + idempotent_morphing_enabled: false, + ..FunctionContextOptions::from(&options) + }, + instruction_blocks, + values, + mode, + ), + FuzzerMode::NonConstant => FuzzerProgramContext::new( + FunctionContextOptions { + idempotent_morphing_enabled: false, + ..FunctionContextOptions::from(&options) + }, + instruction_blocks, + values, + mode, + ), + FuzzerMode::NonConstantWithIdempotentMorphing => FuzzerProgramContext::new( + FunctionContextOptions { + idempotent_morphing_enabled: true, + ..FunctionContextOptions::from(&options) + }, + instruction_blocks, + values, + mode, + ), + FuzzerMode::NonConstantWithoutDIE => { + let mut options = options; + options.compile_options.skip_ssa_pass = + vec!["Dead Instruction Elimination".to_string()]; + FuzzerProgramContext::new( + FunctionContextOptions { + idempotent_morphing_enabled: true, + ..FunctionContextOptions::from(&options) + }, + instruction_blocks, + values, + mode, + ) + } + } } diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs index 3e1bd0909dc..c96df26b019 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs @@ -15,6 +15,8 @@ use rand::{SeedableRng, rngs::StdRng}; use sha1::{Digest, Sha1}; use utils::{push_fuzzer_output_to_redis_queue, redis}; +use crate::fuzz_lib::options::FuzzerMode; + const MAX_EXECUTION_TIME_TO_KEEP_IN_CORPUS: u64 = 3; libfuzzer_sys::fuzz_target!(|data: &[u8]| -> Corpus { @@ -42,10 +44,12 @@ libfuzzer_sys::fuzz_target!(|data: &[u8]| -> Corpus { let instruction_options = InstructionOptions { shl_enabled: false, shr_enabled: false, + alloc_enabled: false, ..InstructionOptions::default() }; + let modes = vec![FuzzerMode::NonConstant, FuzzerMode::NonConstantWithoutDIE]; let options = - FuzzerOptions { compile_options, instruction_options, ..FuzzerOptions::default() }; + FuzzerOptions { compile_options, instruction_options, modes, ..FuzzerOptions::default() }; let fuzzer_data = borrow_decode_from_slice(data, bincode::config::legacy()) .unwrap_or((FuzzerData::default(), 1337)) .0;