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
492 changes: 146 additions & 346 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/block_context.rs

Large diffs are not rendered by default.

39 changes: 6 additions & 33 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/function_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use libfuzzer_sys::arbitrary;
use libfuzzer_sys::arbitrary::Arbitrary;
use noir_ssa_fuzzer::{
builder::FuzzerBuilder,
r#type::{NumericType, Type, TypedValue},
typed_value::{NumericType, Type, TypedValue},
};
use noirc_evaluator::ssa::ir::{basic_block::BasicBlockId, function::Function, map::Id};
use serde::{Deserialize, Serialize};
Expand All @@ -25,42 +25,14 @@ const NUMBER_OF_BLOCKS_INSERTING_IN_LOOP: usize = 4;

pub(crate) type ValueWithType = (FieldElement, NumericType);

/// Field modulus has 254 bits, and FieldElement::from supports u128, so we use two unsigned integers to represent a field element
/// field = low + high * 2^128
#[derive(Debug, Clone, Copy, Hash, Arbitrary, Serialize, Deserialize)]
pub(crate) struct FieldRepresentation {
pub(crate) high: u128,
pub(crate) low: u128,
}

impl From<&FieldRepresentation> for FieldElement {
fn from(field: &FieldRepresentation) -> FieldElement {
let lower = FieldElement::from(field.low);
let upper = FieldElement::from(field.high);
lower + upper * (FieldElement::from(u128::MAX) + FieldElement::from(1_u128))
}
}

#[derive(Debug, Clone, Copy, Hash, Arbitrary, Serialize, Deserialize)]
pub(crate) enum WitnessValue {
Field(FieldRepresentation),
U64(u64),
Boolean(bool),
I64(u64),
I32(u32),
}

impl Default for WitnessValue {
fn default() -> Self {
WitnessValue::Field(FieldRepresentation { high: 0, low: 0 })
}
}

/// TODO(sn): initial_witness should be in ProgramData
/// Represents the data describing a function
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct FunctionData {
pub(crate) commands: Vec<FuzzerFunctionCommand>,
/// Input types of the function
///
/// Overwritten for main function by the types of the initial witness
pub(crate) input_types: Vec<Type>,
pub(crate) return_instruction_block_idx: usize,
pub(crate) return_type: Type,
}
Expand All @@ -69,6 +41,7 @@ impl Default for FunctionData {
fn default() -> Self {
FunctionData {
commands: vec![],
input_types: vec![Type::Numeric(NumericType::Field)],
return_instruction_block_idx: 0,
return_type: Type::Numeric(NumericType::Field),
}
Expand Down
62 changes: 22 additions & 40 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzz_target_lib.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,44 @@
use super::{
NUMBER_OF_PREDEFINED_VARIABLES, NUMBER_OF_VARIABLES_INITIAL,
function_context::WitnessValue,
fuzzer::{Fuzzer, FuzzerData, FuzzerOutput},
initial_witness::{ensure_boolean_defined_in_all_functions, initialize_witness_map},
options::FuzzerOptions,
};
use acvm::FieldElement;
use acvm::acir::native_types::{Witness, WitnessMap};
use noir_ssa_fuzzer::r#type::{NumericType, Type};
use noir_ssa_fuzzer::typed_value::Type;

fn initialize_witness_map(
initial_witness: &[WitnessValue;
(NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES) as usize],
) -> (WitnessMap<FieldElement>, Vec<FieldElement>, Vec<NumericType>) {
let mut witness_map = WitnessMap::new();
let mut values = vec![];
let mut types = vec![];
for (i, witness_value) in initial_witness.iter().enumerate() {
let (value, type_) = match witness_value {
WitnessValue::Field(field) => (FieldElement::from(field), NumericType::Field),
WitnessValue::U64(u64) => (FieldElement::from(*u64), NumericType::U64),
WitnessValue::Boolean(bool) => (FieldElement::from(*bool as u64), NumericType::Boolean),
WitnessValue::I64(i64) => (FieldElement::from(*i64), NumericType::I64),
WitnessValue::I32(i32) => (FieldElement::from(*i32 as u64), NumericType::I32),
};
witness_map.insert(Witness(i as u32), value);
values.push(value);
types.push(type_);
fn type_contains_slice_or_reference(type_: &Type) -> bool {
match type_ {
Type::Slice(_) => true,
Type::Reference(_) => true,
Type::Array(arr, _) => arr.iter().any(|t| type_contains_slice_or_reference(t)),
Type::Numeric(_) => false,
}
// insert true and false boolean values
witness_map.insert(
Witness(NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES),
FieldElement::from(1_u32),
);
values.push(FieldElement::from(1_u32));
types.push(NumericType::Boolean);
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(NumericType::Boolean);
(witness_map, values, types)
}

/// Creates ACIR and Brillig programs from the data, runs and compares them
pub(crate) fn fuzz_target(data: FuzzerData, options: FuzzerOptions) -> Option<FuzzerOutput> {
// to triage
if data.instruction_blocks.is_empty() {
return None;
}
if data.functions.is_empty() {
return None;
}
log::debug!("instruction_blocks: {:?}", data.instruction_blocks);
log::debug!("initial_witness: {:?}", data.initial_witness);
let (witness_map, values, types) = initialize_witness_map(&data.initial_witness);
let mut data = data;
data.functions[0].input_types = types;
ensure_boolean_defined_in_all_functions(&mut data);

if type_contains_slice_or_reference(&data.functions[0].return_type) {
// main cannot return a reference
data.functions[0].return_type = Type::default();
}

let mut fuzzer = Fuzzer::new(data.instruction_blocks, values, options);
for func in data.functions {
log::debug!("initial_witness: {witness_map:?}");
log::debug!("commands: {:?}", func.commands);
fuzzer.process_function(func, types.iter().map(|t| Type::Numeric(*t)).collect());
log::debug!("input_types: {:?}", func.input_types);
fuzzer.process_function(func.clone(), func.input_types.clone());
}
fuzzer.finalize_and_run(witness_map)
}
12 changes: 5 additions & 7 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
//! - If one program fails to compile but the other executes successfully

use super::{
NUMBER_OF_PREDEFINED_VARIABLES, NUMBER_OF_VARIABLES_INITIAL,
function_context::{FunctionData, WitnessValue},
function_context::FunctionData,
initial_witness::WitnessValue,
instruction::InstructionBlock,
options::{FuzzerMode, FuzzerOptions},
program_context::{FuzzerProgramContext, program_context_by_mode},
Expand All @@ -25,7 +25,7 @@ use acvm::acir::native_types::{WitnessMap, WitnessStack};
use noir_ssa_executor::runner::execute_single;
use noir_ssa_fuzzer::{
runner::{CompareResults, run_and_compare},
r#type::Type,
typed_value::Type,
};
use noirc_driver::CompiledProgram;
use serde::{Deserialize, Serialize};
Expand All @@ -34,17 +34,15 @@ use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct FuzzerData {
pub(crate) functions: Vec<FunctionData>,
pub(crate) initial_witness:
[WitnessValue; (NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES) as usize],
pub(crate) initial_witness: Vec<WitnessValue>,
pub(crate) instruction_blocks: Vec<InstructionBlock>,
}

impl Default for FuzzerData {
fn default() -> Self {
FuzzerData {
functions: vec![FunctionData::default()],
initial_witness: [WitnessValue::default();
(NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES) as usize],
initial_witness: vec![WitnessValue::default()],
instruction_blocks: vec![],
}
}
Expand Down
167 changes: 167 additions & 0 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/initial_witness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
//! This file describes initial witness passed to the program

use super::fuzzer::FuzzerData;
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::{NumericType, Type};
use serde::{Deserialize, Serialize};
use std::sync::Arc;

/// Field modulus has 254 bits, and FieldElement::from supports u128, so we use two unsigned integers to represent a field element
/// field = low + high * 2^128
#[derive(Debug, Clone, Copy, Hash, Arbitrary, Serialize, Deserialize)]
pub(crate) struct FieldRepresentation {
pub(crate) high: u128,
pub(crate) low: u128,
}

impl From<&FieldRepresentation> for FieldElement {
fn from(field: &FieldRepresentation) -> FieldElement {
let lower = FieldElement::from(field.low);
let upper = FieldElement::from(field.high);
lower + upper * (FieldElement::from(u128::MAX) + FieldElement::from(1_u128))
}
}

#[derive(Debug, Clone, Copy, Hash, Arbitrary, Serialize, Deserialize)]
pub(crate) enum WitnessValueNumeric {
Field(FieldRepresentation),
U128(u128),
U64(u64),
U32(u32),
U16(u16),
U8(u8),
Boolean(bool),
I64(u64),
I32(u32),
I16(u16),
I8(u8),
}

impl From<WitnessValueNumeric> for NumericType {
fn from(value: WitnessValueNumeric) -> Self {
match value {
WitnessValueNumeric::Field(_) => NumericType::Field,
WitnessValueNumeric::U128(_) => NumericType::U128,
WitnessValueNumeric::U64(_) => NumericType::U64,
WitnessValueNumeric::U32(_) => NumericType::U32,
WitnessValueNumeric::U16(_) => NumericType::U16,
WitnessValueNumeric::U8(_) => NumericType::U8,
WitnessValueNumeric::Boolean(_) => NumericType::Boolean,
WitnessValueNumeric::I64(_) => NumericType::I64,
WitnessValueNumeric::I32(_) => NumericType::I32,
WitnessValueNumeric::I16(_) => NumericType::I16,
WitnessValueNumeric::I8(_) => NumericType::I8,
}
}
}

impl From<WitnessValueNumeric> for FieldElement {
fn from(value: WitnessValueNumeric) -> Self {
match value {
WitnessValueNumeric::Field(field) => FieldElement::from(&field),
WitnessValueNumeric::U128(u128) => FieldElement::from(u128),
WitnessValueNumeric::U64(u64) => FieldElement::from(u64),
WitnessValueNumeric::U32(u32) => FieldElement::from(u32),
WitnessValueNumeric::U16(u16) => FieldElement::from(u16 as u64),
WitnessValueNumeric::U8(u8) => FieldElement::from(u8 as u64),
WitnessValueNumeric::Boolean(bool) => FieldElement::from(bool),
WitnessValueNumeric::I64(i64) => FieldElement::from(i64),
WitnessValueNumeric::I32(i32) => FieldElement::from(i32 as u64),
WitnessValueNumeric::I16(i16) => FieldElement::from(i16 as u64),
WitnessValueNumeric::I8(i8) => FieldElement::from(i8 as u64),
}
}
}

impl Default for WitnessValueNumeric {
fn default() -> Self {
WitnessValueNumeric::Field(FieldRepresentation { high: 0, low: 0 })
}
}

#[derive(Debug, Clone, Hash, Arbitrary, Serialize, Deserialize)]
pub(crate) enum WitnessValue {
Numeric(WitnessValueNumeric),
Array(Vec<WitnessValue>),
}

impl Default for WitnessValue {
fn default() -> Self {
WitnessValue::Numeric(WitnessValueNumeric::default())
}
}

impl WitnessValue {
fn to_ssa_type(&self) -> Type {
match self {
WitnessValue::Numeric(numeric) => Type::Numeric(NumericType::from(*numeric)),
WitnessValue::Array(arr) => {
let ssa_type = arr
.iter()
.map(|v| v.to_ssa_type())
.reduce(|a, b| {
assert_eq!(a, b, "All SSA types in the array must be the same");
a
})
.unwrap();
Type::Array(Arc::new(vec![ssa_type; 1]), arr.len() as u32)
}
}
}
}

fn initialize_witness_map_internal(witness: &[WitnessValue]) -> (Vec<FieldElement>, Vec<Type>) {
let mut types = vec![];
let mut witness_vec = vec![];
for witness_value in witness.iter() {
match witness_value {
WitnessValue::Numeric(numeric) => {
witness_vec.push(FieldElement::from(*numeric));
types.push(witness_value.to_ssa_type());
}
WitnessValue::Array(arr) => {
let type_ = witness_value.to_ssa_type();
types.push(type_);
for val in arr {
// types of inner arrays are ignored, because they are already added to the types vector
let (values, _types) = initialize_witness_map_internal(&[val.clone()]);
witness_vec.extend(values);
}
}
};
}
(witness_vec, types)
}

/// Initializes [`WitnessMap`] from [`WitnessValue`]
pub(crate) fn initialize_witness_map(
initial_witness: &[WitnessValue],
) -> (WitnessMap<FieldElement>, Vec<FieldElement>, Vec<Type>) {
let (mut witness_vec, mut types) = initialize_witness_map_internal(initial_witness);
// add true and false boolean values
witness_vec.push(FieldElement::from(1_u32));
types.push(Type::Numeric(NumericType::Boolean));
witness_vec.push(FieldElement::from(0_u32));
types.push(Type::Numeric(NumericType::Boolean));
let mut witness_map = WitnessMap::new();
for (i, value) in witness_vec.iter().enumerate() {
witness_map.insert(Witness(i as u32), *value);
}
(witness_map, witness_vec, types)
}

/// Ensures that boolean is defined in all functions
///
/// If boolean is not defined in the function, it is added to the input types
pub(crate) fn ensure_boolean_defined_in_all_functions(data: &mut FuzzerData) {
for func in &mut data.functions {
let boolean_presented_in_input_types =
func.input_types.iter().any(|t| t == &Type::Numeric(NumericType::Boolean));
if !boolean_presented_in_input_types {
func.input_types.push(Type::Numeric(NumericType::Boolean));
}
}
}
Loading
Loading