Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions tooling/ssa_fuzzer/fuzzer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
115 changes: 37 additions & 78 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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 {
Expand All @@ -47,11 +48,10 @@ impl Default for FuzzerData {
}

pub(crate) struct Fuzzer {
pub(crate) context_non_constant: Option<FuzzerProgramContext>,
pub(crate) context_non_constant_with_idempotent_morphing: Option<FuzzerProgramContext>,
pub(crate) context_constant: Option<FuzzerProgramContext>,
pub(crate) contexts: Vec<FuzzerProgramContext>,
}

#[derive(Clone, Debug)]
pub(crate) struct FuzzerOutput {
pub(crate) witness_stack: WitnessStack<FieldElement>,
pub(crate) program: CompiledProgram,
Expand All @@ -70,96 +70,55 @@ impl Fuzzer {
values: Vec<FieldElement>,
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<ValueType>) {
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<FieldElement>,
) -> Option<FuzzerOutput> {
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<FuzzerMode, Option<FuzzerOutput>> = 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<FieldElement> { result.as_ref().map(|r| r.get_return_value()) })
.collect::<HashSet<_>>();

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(
Expand Down
21 changes: 17 additions & 4 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<FuzzerMode>,
}

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],
}
}
}
Expand Down
68 changes: 65 additions & 3 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/program_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -41,14 +41,17 @@ pub(crate) struct FuzzerProgramContext {
///
/// Used for the constant mode (to replace variables in the main function with constants)
values: Vec<FieldElement>,

mode: FuzzerMode,
}

impl FuzzerProgramContext {
/// Creates a new FuzzerProgramContext
pub(crate) fn new(
fn new(
program_context_options: FunctionContextOptions,
instruction_blocks: Vec<InstructionBlock>,
values: Vec<FieldElement>,
mode: FuzzerMode,
) -> Self {
let acir_builder = FuzzerBuilder::new_acir();
let brillig_builder = FuzzerBuilder::new_brillig();
Expand All @@ -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<InstructionBlock>,
values: Vec<FieldElement>,
mode: FuzzerMode,
) -> Self {
let acir_builder = FuzzerBuilder::new_acir();
let brillig_builder = FuzzerBuilder::new_brillig();
Expand All @@ -85,6 +90,7 @@ impl FuzzerProgramContext {
instruction_blocks,
is_main_initialized: false,
values,
mode,
}
}

Expand Down Expand Up @@ -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<InstructionBlock>,
values: Vec<FieldElement>,
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,
)
}
}
}
6 changes: 5 additions & 1 deletion tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
Loading