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
8 changes: 6 additions & 2 deletions tooling/ast_fuzzer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ cargo +nightly fuzz run acir_vs_brillig fuzz/artifacts/acir_vs_brillig/crash-927

Note that `cargo fuzz` requires `nightly` build, which can be either turned on with the `cargo +nightly` flag, or by running `rustup default nightly`. Also note that `cargo fuzz run` automatically creates a `--release` build, there is no need for an explicit flag to be passed.

The `NOIR_AST_FUZZER_SHOW_AST` env var can be used to print the AST before compilation, in case the compiler crashes on the generated program. Otherwise if the execution fails, the output will include the AST, the inputs, and the ACIR/Brillig opcodes.
If the execution fails, the output will include the AST, the inputs, and the ACIR/Brillig opcodes.

## `arbtest`

Expand All @@ -48,7 +48,11 @@ To get quick feedback about whether there are any easy-to-discover bugs, we can
cargo test -p noir_ast_fuzzer_fuzz arbtest
```

Unlike `cargo fuzz`, these don't "ramp up" the complexity of the code, but go full tilt from the beginning, and only run for a limited amount of time (e.g. 10 seconds). Upon failure they print a hexadecimal `seed`, which can be used with the `NOIR_AST_FUZZER_SEED` env var to replicate the error.
Unlike `cargo fuzz`, these don't "ramp up" the complexity of the code, but go full tilt from the beginning, and only run for a limited amount of time (e.g. 10 seconds).

Upon failure they print a hexadecimal `seed`, which can be used with the `NOIR_AST_FUZZER_SEED` env var to replicate the error.

If the compiler crashes during the generation of the SSA artifacts, the problematic program will be printed during attempts to reproduce the issue using a seed. The printing of all inputs can be turned on by setting `RUST_LOG=debug`.

## Minimizing Noir

Expand Down
80 changes: 52 additions & 28 deletions tooling/ast_fuzzer/fuzz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ fn bool_from_env(key: &str) -> bool {
std::env::var(key).map(|s| s == "1" || s == "true").unwrap_or_default()
}

// TODO(#7876): Allow specifying options on the command line.
fn show_ast() -> bool {
bool_from_env("NOIR_AST_FUZZER_SHOW_AST")
}

/// Show all SSA passes during compilation.
fn show_ssa() -> bool {
bool_from_env("NOIR_AST_FUZZER_SHOW_SSA")
}
Expand All @@ -46,40 +42,51 @@ pub fn default_ssa_options() -> SsaEvaluatorOptions {
}
}

/// Compile a [Program] into SSA or panic.
///
/// Prints the AST if `NOIR_AST_FUZZER_SHOW_AST` is set.
pub fn create_ssa_or_die(
/// Compile a monomorphized [Program] into circuit or panic.
pub fn compile_into_circuit_or_die(
program: Program,
options: &SsaEvaluatorOptions,
msg: Option<&str>,
) -> SsaProgramArtifact {
create_ssa_with_passes_or_die(program, options, &primary_passes(options), msg)
compile_into_circuit_with_ssa_passes_or_die(program, options, &primary_passes(options), msg)
}

/// Compile a [Program] into SSA using the given primary and secondary passes, or panic.
/// Compile a monomorphized [Program] into circuit using the given SSA passes or panic.
///
/// Prints the AST if `NOIR_AST_FUZZER_SHOW_AST` is set.
pub fn create_ssa_with_passes_or_die(
/// If there is a seed in the environment, then it prints the AST when an error is encountered.
pub fn compile_into_circuit_with_ssa_passes_or_die(
program: Program,
options: &SsaEvaluatorOptions,
primary: &[SsaPass],
msg: Option<&str>,
) -> SsaProgramArtifact {
// Unfortunately we can't use `std::panic::catch_unwind`
// and `std::panic::resume_unwind` to catch any panic
// and print the AST, then resume the panic, because
// `Program` has a `RefCell` in it, which is not unwind safe.
if show_ast() {
eprintln!("---\n{}\n---", DisplayAstAsNoir(&program));
}

ssa::create_program_with_passes(program, options, primary, None).unwrap_or_else(|e| {
panic!(
"failed to compile program: {}{e}",
msg.map(|s| format!("{s}: ")).unwrap_or_default()
// If we are using a seed, we are probably trying to reproduce some failure;
// in that case let's clone the program for printing if it panicked here,
// otherwise try to keep things faster by not cloning.
let for_print = std::env::var("NOIR_AST_FUZZER_SEED").is_ok().then(|| program.clone());

// We expect the programs generated should compile, but sometimes SSA passes panic, or return an error.
// Turn everything into a panic, catch it, print the AST, then resume panicking.
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
ssa::create_program_with_passes(program.clone(), options, primary, None).unwrap_or_else(
|e| {
panic!(
"failed to compile program: {}{e}",
msg.map(|s| format!("{s}: ")).unwrap_or_default()
)
},
)
})
}));

match result {
Ok(ssa) => ssa,
Err(payload) => {
if let Some(program) = for_print {
eprintln!("--- Failing AST:\n{}\n---", DisplayAstAsNoir(&program));
}
std::panic::resume_unwind(payload);
}
}
}

/// Compare the execution result and print the inputs if the result is a failure.
Expand Down Expand Up @@ -113,11 +120,28 @@ where
.serialize(&inputs.input_map, &inputs.abi)
.unwrap_or_else(|e| format!("failed to serialize inputs: {e}"))
);

// Display a Program without the Brillig opcodes, which are unreadable.
fn display_program(artifact: &SsaProgramArtifact) {
for (func_index, function) in artifact.program.functions.iter().enumerate() {
eprintln!("func {func_index}");
eprintln!("{function}");
}
for (func_index, function) in
artifact.program.unconstrained_functions.iter().enumerate()
{
eprintln!("unconstrained func {func_index}");
eprintln!("opcode count: {}", function.bytecode.len());
}
}

eprintln!("---\nOptions 1:\n{:?}", inputs.ssa1.options);
eprintln!("---\nProgram 1:\n{}", inputs.ssa1.artifact.program);
eprintln!("---\nProgram 1:");
display_program(&inputs.ssa1.artifact);

eprintln!("---\nOptions 2:\n{:?}", inputs.ssa2.options);
eprintln!("---\nProgram 2:\n{}", inputs.ssa2.artifact.program);
eprintln!("---\nProgram 2:");
display_program(&inputs.ssa1.artifact);

// Returning it as-is, so we can see the error message at the bottom as well.
Err(report)
Expand Down
12 changes: 7 additions & 5 deletions tooling/ast_fuzzer/fuzz/src/targets/acir_vs_brillig.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Compare the execution of random ASTs between the normal execution
//! vs when everything is forced to be Brillig.
use crate::targets::default_config;
use crate::{compare_results_compiled, create_ssa_or_die, default_ssa_options};
use crate::{compare_results_compiled, compile_into_circuit_or_die, default_ssa_options};
use arbitrary::Arbitrary;
use arbitrary::Unstructured;
use color_eyre::eyre;
Expand All @@ -16,13 +16,16 @@ pub fn fuzz(u: &mut Unstructured) -> eyre::Result<()> {
config,
|u, program| {
let options = CompareOptions::arbitrary(u)?;
let ssa =
create_ssa_or_die(program, &options.onto(default_ssa_options()), Some("acir"));
let ssa = compile_into_circuit_or_die(
program,
&options.onto(default_ssa_options()),
Some("acir"),
);
Ok((ssa, options))
},
|u, program| {
let options = CompareOptions::arbitrary(u)?;
let ssa = create_ssa_or_die(
let ssa = compile_into_circuit_or_die(
change_all_functions_into_unconstrained(program),
&options.onto(default_ssa_options()),
Some("brillig"),
Expand All @@ -41,7 +44,6 @@ mod tests {

/// ```ignore
/// NOIR_AST_FUZZER_SEED=0x6819c61400001000 \
/// NOIR_AST_FUZZER_SHOW_AST=1 \
/// cargo test -p noir_ast_fuzzer_fuzz acir_vs_brillig
/// ```
#[test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//! through nargo, which speeds up execution but also currently
//! has some issues (inability to use prints among others).
use crate::targets::default_config;
use crate::{compare_results_comptime, create_ssa_or_die, default_ssa_options};
use crate::{compare_results_comptime, compile_into_circuit_or_die, default_ssa_options};
use arbitrary::Unstructured;
use color_eyre::eyre;
use noir_ast_fuzzer::Config;
Expand Down Expand Up @@ -36,7 +36,7 @@ pub fn fuzz(u: &mut Unstructured) -> eyre::Result<()> {

let inputs = CompareComptime::arb(u, config, |program| {
let options = CompareOptions::default();
let ssa = create_ssa_or_die(
let ssa = compile_into_circuit_or_die(
change_all_functions_into_unconstrained(program),
&options.onto(default_ssa_options()),
Some("brillig"),
Expand All @@ -46,7 +46,7 @@ pub fn fuzz(u: &mut Unstructured) -> eyre::Result<()> {

let result = inputs.exec_direct(|program| {
let options = CompareOptions::default();
let ssa = create_ssa_or_die(
let ssa = compile_into_circuit_or_die(
program,
&options.onto(default_ssa_options()),
Some("comptime_result_wrapper"),
Expand All @@ -62,7 +62,6 @@ mod tests {

/// ```ignore
/// NOIR_AST_FUZZER_SEED=0x6819c61400001000 \
/// NOIR_AST_FUZZER_SHOW_AST=1 \
/// cargo test -p noir_ast_fuzzer_fuzz comptime_vs_brillig_direct
/// ```
#[test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//! but at the moment is more feature complete than using the interpreter
//! directly.
use crate::targets::default_config;
use crate::{compare_results_comptime, create_ssa_or_die, default_ssa_options};
use crate::{compare_results_comptime, compile_into_circuit_or_die, default_ssa_options};
use arbitrary::Unstructured;
use color_eyre::eyre;
use noir_ast_fuzzer::Config;
Expand All @@ -33,7 +33,7 @@ pub fn fuzz(u: &mut Unstructured) -> eyre::Result<()> {

let inputs = CompareComptime::arb(u, config, |program| {
let options = CompareOptions::default();
let ssa = create_ssa_or_die(
let ssa = compile_into_circuit_or_die(
change_all_functions_into_unconstrained(program),
&options.onto(default_ssa_options()),
Some("brillig"),
Expand All @@ -51,7 +51,6 @@ mod tests {

/// ```ignore
/// NOIR_AST_FUZZER_SEED=0x6819c61400001000 \
/// NOIR_AST_FUZZER_SHOW_AST=1 \
/// cargo test -p noir_ast_fuzzer_fuzz comptime_vs_brillig_nargo
/// ```
#[test]
Expand Down
13 changes: 8 additions & 5 deletions tooling/ast_fuzzer/fuzz/src/targets/min_vs_full.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
//! and the fully optimized version.
use crate::targets::default_config;
use crate::{
compare_results_compiled, create_ssa_or_die, create_ssa_with_passes_or_die, default_ssa_options,
compare_results_compiled, compile_into_circuit_or_die,
compile_into_circuit_with_ssa_passes_or_die, default_ssa_options,
};
use arbitrary::{Arbitrary, Unstructured};
use color_eyre::eyre;
Expand All @@ -22,7 +23,7 @@ pub fn fuzz(u: &mut Unstructured) -> eyre::Result<()> {
// We want to do the minimum possible amount of SSA passes. Brillig can get away with fewer than ACIR,
// because ACIR needs unrolling of loops for example, so we treat everything as Brillig.
let options = CompareOptions::default();
let ssa = create_ssa_with_passes_or_die(
let ssa = compile_into_circuit_with_ssa_passes_or_die(
change_all_functions_into_unconstrained(program),
&options.onto(default_ssa_options()),
&passes,
Expand All @@ -32,8 +33,11 @@ pub fn fuzz(u: &mut Unstructured) -> eyre::Result<()> {
},
|u, program| {
let options = CompareOptions::arbitrary(u)?;
let ssa =
create_ssa_or_die(program, &options.onto(default_ssa_options()), Some("final"));
let ssa = compile_into_circuit_or_die(
program,
&options.onto(default_ssa_options()),
Some("final"),
);
Ok((ssa, options))
},
)?;
Expand All @@ -51,7 +55,6 @@ pub fn fuzz(u: &mut Unstructured) -> eyre::Result<()> {
mod tests {
/// ```ignore
/// NOIR_AST_FUZZER_SEED=0x6819c61400001000 \
/// NOIR_AST_FUZZER_SHOW_AST=1 \
/// cargo test -p noir_ast_fuzzer_fuzz min_vs_full
/// ```
#[test]
Expand Down
1 change: 0 additions & 1 deletion tooling/ast_fuzzer/fuzz/src/targets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ mod tests {
/// Run it with for example:
/// ```ignore
/// NOIR_AST_FUZZER_SEED=0x6819c61400001000 \
/// NOIR_AST_FUZZER_SHOW_AST=1 \
/// cargo test -p noir_ast_fuzzer_fuzz acir_vs_brillig
/// ```
///
Expand Down
7 changes: 4 additions & 3 deletions tooling/ast_fuzzer/fuzz/src/targets/orig_vs_morph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use std::cell::{Cell, RefCell};

use crate::targets::default_config;
use crate::{compare_results_compiled, create_ssa_or_die, default_ssa_options};
use crate::{compare_results_compiled, compile_into_circuit_or_die, default_ssa_options};
use arbitrary::{Arbitrary, Unstructured};
use color_eyre::eyre;
use noir_ast_fuzzer::compare::{CompareMorph, CompareOptions};
Expand All @@ -28,7 +28,9 @@ pub fn fuzz(u: &mut Unstructured) -> eyre::Result<()> {
rewrite_program(u, &mut program, &rules, max_rewrites);
Ok((program, options))
},
|program, options| create_ssa_or_die(program, &options.onto(default_ssa_options()), None),
|program, options| {
compile_into_circuit_or_die(program, &options.onto(default_ssa_options()), None)
},
)?;

let result = inputs.exec()?;
Expand Down Expand Up @@ -820,7 +822,6 @@ mod helpers {
mod tests {
/// ```ignore
/// NOIR_AST_FUZZER_SEED=0xb2fb5f0b00100000 \
/// NOIR_AST_FUZZER_SHOW_AST=1 \
/// cargo test -p noir_ast_fuzzer_fuzz orig_vs_morph
/// ```
#[test]
Expand Down
20 changes: 12 additions & 8 deletions tooling/ast_fuzzer/src/compare/compiled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use noirc_abi::{Abi, InputMap, input_parser::InputValue};
use noirc_evaluator::{ErrorType, ssa::SsaProgramArtifact};
use noirc_frontend::monomorphization::ast::Program;

use crate::{Config, arb_inputs, arb_program, program_abi};
use crate::{Config, arb_inputs, arb_program, compare::logging, program_abi};

use super::{Comparable, CompareOptions, CompareResult, FailedOutput, HasPrograms, PassedOutput};

Expand Down Expand Up @@ -240,13 +240,6 @@ impl<P> CompareCompiled<P> {
let blackbox_solver = Bn254BlackBoxSolver(false);
let initial_witness = self.abi.encode(&self.input_map, None).wrap_err("abi::encode")?;

log::debug!(
"ABI input:\n{}",
noirc_abi::input_parser::Format::Toml
.serialize(&self.input_map, &self.abi)
.unwrap_or_else(|e| format!("failed to serialize inputs: {e}"))
);

let do_exec = |program| {
let mut print = Vec::new();

Expand Down Expand Up @@ -298,12 +291,17 @@ impl CompareCompiled<Program> {
) -> arbitrary::Result<Self> {
let program = arb_program(u, c)?;
let abi = program_abi(&program);
logging::log_program(&program, "");

let ssa1 = CompareArtifact::from(f(u, program.clone())?);
let ssa2 = CompareArtifact::from(g(u, program.clone())?);

logging::log_options(&ssa1.options, "1st");
logging::log_options(&ssa2.options, "2nd");

let input_program = &ssa1.artifact.program;
let input_map = arb_inputs(u, input_program, &abi)?;
logging::log_abi_inputs(&abi, &input_map);

Ok(Self { program, abi, input_map, ssa1, ssa2 })
}
Expand All @@ -327,14 +325,20 @@ impl CompareMorph {
g: impl Fn(Program, &CompareOptions) -> SsaProgramArtifact,
) -> arbitrary::Result<Self> {
let program1 = arb_program(u, c)?;
logging::log_program(&program1, "orig");

let (program2, options) = f(u, program1.clone())?;
logging::log_program(&program2, "morph");
logging::log_options(&options, "");

let abi = program_abi(&program1);

let ssa1 = g(program1.clone(), &options);
let ssa2 = g(program2.clone(), &options);

let input_program = &ssa1.program;
let input_map = arb_inputs(u, input_program, &abi)?;
logging::log_abi_inputs(&abi, &input_map);

Ok(Self {
program: (program1, program2),
Expand Down
Loading
Loading