Skip to content

Commit 0ebf1fe

Browse files
authored
chore: allow passing custom executors to fuzzer (#5710)
# Description ## Problem\* Resolves <!-- Link to GitHub Issue --> ## Summary\* This PR pays off some tech debt by removing the fuzzer's dependency on the `nargo` package. This allows us to pass in custom foreign call executors and blackbox solvers, etc. It also sorts some circular dependencies which were preventing us from integrating it cleanly into the test harness function. ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings.
1 parent 3c6b998 commit 0ebf1fe

9 files changed

Lines changed: 115 additions & 126 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"foralls",
8989
"formatcp",
9090
"frontends",
91+
"fuzzer",
9192
"fxhash",
9293
"getrandom",
9394
"gloo",

tooling/fuzzer/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ license.workspace = true
1111

1212
[dependencies]
1313
acvm.workspace = true
14-
nargo.workspace = true
1514
noirc_artifacts.workspace = true
1615
noirc_abi.workspace = true
1716
proptest.workspace = true

tooling/fuzzer/src/lib.rs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
//!
44
//! Code is used under the MIT license.
55
6-
use acvm::{blackbox_solver::StubbedBlackBoxSolver, FieldElement};
6+
use acvm::{
7+
acir::{
8+
circuit::Program,
9+
native_types::{WitnessMap, WitnessStack},
10+
},
11+
FieldElement,
12+
};
713
use dictionary::build_dictionary_from_program;
814
use noirc_abi::InputMap;
915
use proptest::test_runner::{TestCaseError, TestError, TestRunner};
@@ -16,25 +22,32 @@ use types::{CaseOutcome, CounterExampleOutcome, FuzzOutcome, FuzzTestResult};
1622

1723
use noirc_artifacts::program::ProgramArtifact;
1824

19-
use nargo::ops::{execute_program, DefaultForeignCallExecutor};
20-
2125
/// An executor for Noir programs which which provides fuzzing support using [`proptest`].
2226
///
2327
/// After instantiation, calling `fuzz` will proceed to hammer the program with
2428
/// inputs, until it finds a counterexample. The provided [`TestRunner`] contains all the
2529
/// configuration which can be overridden via [environment variables](proptest::test_runner::Config)
26-
pub struct FuzzedExecutor {
30+
pub struct FuzzedExecutor<E> {
2731
/// The program to be fuzzed
2832
program: ProgramArtifact,
2933

34+
/// A function which executes the programs with a given set of inputs
35+
executor: E,
36+
3037
/// The fuzzer
3138
runner: TestRunner,
3239
}
3340

34-
impl FuzzedExecutor {
41+
impl<
42+
E: Fn(
43+
&Program<FieldElement>,
44+
WitnessMap<FieldElement>,
45+
) -> Result<WitnessStack<FieldElement>, String>,
46+
> FuzzedExecutor<E>
47+
{
3548
/// Instantiates a fuzzed executor given a testrunner
36-
pub fn new(program: ProgramArtifact, runner: TestRunner) -> Self {
37-
Self { program, runner }
49+
pub fn new(program: ProgramArtifact, executor: E, runner: TestRunner) -> Self {
50+
Self { program, executor, runner }
3851
}
3952

4053
/// Fuzzes the provided program.
@@ -76,19 +89,14 @@ impl FuzzedExecutor {
7689
/// or a `CounterExampleOutcome`
7790
pub fn single_fuzz(&self, input_map: InputMap) -> Result<FuzzOutcome, TestCaseError> {
7891
let initial_witness = self.program.abi.encode(&input_map, None).unwrap();
79-
let result = execute_program(
80-
&self.program.bytecode,
81-
initial_witness,
82-
&StubbedBlackBoxSolver,
83-
&mut DefaultForeignCallExecutor::<FieldElement>::new(false, None),
84-
);
92+
let result = (self.executor)(&self.program.bytecode, initial_witness);
8593

8694
// TODO: Add handling for `vm.assume` equivalent
8795

8896
match result {
8997
Ok(_) => Ok(FuzzOutcome::Case(CaseOutcome { case: input_map })),
9098
Err(err) => Ok(FuzzOutcome::CounterExample(CounterExampleOutcome {
91-
exit_reason: err.to_string(),
99+
exit_reason: err,
92100
counterexample: input_map,
93101
})),
94102
}

tooling/nargo/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ jsonrpc.workspace = true
2525
rand.workspace = true
2626
serde.workspace = true
2727

28+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
29+
noir_fuzzer.workspace = true
30+
proptest.workspace = true
31+
2832
[dev-dependencies]
2933
# TODO: This dependency is used to generate unit tests for `get_all_paths_in_dir`
3034
# TODO: once that method is moved to nargo_cli, we can move this dependency to nargo_cli

tooling/nargo/src/ops/test.rs

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,75 @@ pub fn run_test<B: BlackBoxFunctionSolver<FieldElement>>(
3131
foreign_call_resolver_url: Option<&str>,
3232
config: &CompileOptions,
3333
) -> TestStatus {
34-
let compiled_program = compile_no_check(context, config, test_function.get_id(), None, false);
35-
match compiled_program {
34+
let test_function_has_no_arguments = context
35+
.def_interner
36+
.function_meta(&test_function.get_id())
37+
.function_signature()
38+
.0
39+
.is_empty();
40+
41+
match compile_no_check(context, config, test_function.get_id(), None, false) {
3642
Ok(compiled_program) => {
37-
// Run the backend to ensure the PWG evaluates functions like std::hash::pedersen,
38-
// otherwise constraints involving these expressions will not error.
39-
let circuit_execution = execute_program(
40-
&compiled_program.program,
41-
WitnessMap::new(),
42-
blackbox_solver,
43-
&mut DefaultForeignCallExecutor::new(show_output, foreign_call_resolver_url),
44-
);
45-
test_status_program_compile_pass(
46-
test_function,
47-
compiled_program.abi,
48-
compiled_program.debug,
49-
circuit_execution,
50-
)
43+
if test_function_has_no_arguments {
44+
// Run the backend to ensure the PWG evaluates functions like std::hash::pedersen,
45+
// otherwise constraints involving these expressions will not error.
46+
let circuit_execution = execute_program(
47+
&compiled_program.program,
48+
WitnessMap::new(),
49+
blackbox_solver,
50+
&mut DefaultForeignCallExecutor::new(show_output, foreign_call_resolver_url),
51+
);
52+
test_status_program_compile_pass(
53+
test_function,
54+
compiled_program.abi,
55+
compiled_program.debug,
56+
circuit_execution,
57+
)
58+
} else {
59+
#[cfg(target_arch = "wasm32")]
60+
{
61+
// We currently don't support fuzz testing on wasm32 as the u128 strategies do not exist on this platform.
62+
TestStatus::Fail {
63+
message: "Fuzz tests are not supported on wasm32".to_string(),
64+
error_diagnostic: None,
65+
}
66+
}
67+
68+
#[cfg(not(target_arch = "wasm32"))]
69+
{
70+
use acvm::acir::circuit::Program;
71+
use noir_fuzzer::FuzzedExecutor;
72+
use proptest::test_runner::TestRunner;
73+
let runner = TestRunner::default();
74+
75+
let executor =
76+
|program: &Program<FieldElement>,
77+
initial_witness: WitnessMap<FieldElement>|
78+
-> Result<WitnessStack<FieldElement>, String> {
79+
execute_program(
80+
program,
81+
initial_witness,
82+
blackbox_solver,
83+
&mut DefaultForeignCallExecutor::<FieldElement>::new(
84+
false,
85+
foreign_call_resolver_url,
86+
),
87+
)
88+
.map_err(|err| err.to_string())
89+
};
90+
let fuzzer = FuzzedExecutor::new(compiled_program.into(), executor, runner);
91+
92+
let result = fuzzer.fuzz();
93+
if result.success {
94+
TestStatus::Pass
95+
} else {
96+
TestStatus::Fail {
97+
message: result.reason.unwrap_or_default(),
98+
error_diagnostic: None,
99+
}
100+
}
101+
}
102+
}
51103
}
52104
Err(err) => test_status_program_compile_fail(err, test_function),
53105
}

tooling/nargo_cli/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ noirc_driver.workspace = true
3232
noirc_frontend = { workspace = true, features = ["bn254"] }
3333
noirc_abi.workspace = true
3434
noirc_errors.workspace = true
35-
noir_fuzzer.workspace = true
3635
noirc_artifacts.workspace = true
3736
acvm = { workspace = true, features = ["bn254"] }
3837
bn254_blackbox_solver.workspace = true
@@ -51,7 +50,6 @@ color-eyre.workspace = true
5150
tokio = { version = "1.0", features = ["io-std", "rt"] }
5251
dap.workspace = true
5352
clap-markdown = { git = "https://github.com/noir-lang/clap-markdown", rev = "450d759532c88f0dba70891ceecdbc9ff8f25d2b", optional = true }
54-
proptest.workspace = true
5553

5654
notify = "6.1.1"
5755
notify-debouncer-full = "0.3.1"

tooling/nargo_cli/src/cli/test_cmd.rs

Lines changed: 9 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ use nargo::{
1010
};
1111
use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection};
1212
use noirc_driver::{
13-
check_crate, compile_no_check, file_manager_with_stdlib, CompileOptions,
14-
NOIR_ARTIFACT_VERSION_STRING,
13+
check_crate, file_manager_with_stdlib, CompileOptions, NOIR_ARTIFACT_VERSION_STRING,
1514
};
1615
use noirc_frontend::{
1716
graph::CrateName,
@@ -180,47 +179,14 @@ fn run_test<S: BlackBoxFunctionSolver<FieldElement> + Default>(
180179

181180
let blackbox_solver = S::default();
182181

183-
let test_function_has_no_arguments = context
184-
.def_interner
185-
.function_meta(&test_function.get_id())
186-
.function_signature()
187-
.0
188-
.is_empty();
189-
190-
if test_function_has_no_arguments {
191-
nargo::ops::run_test(
192-
&blackbox_solver,
193-
&mut context,
194-
test_function,
195-
show_output,
196-
foreign_call_resolver_url,
197-
compile_options,
198-
)
199-
} else {
200-
use noir_fuzzer::FuzzedExecutor;
201-
use proptest::test_runner::TestRunner;
202-
203-
let compiled_program =
204-
compile_no_check(&mut context, compile_options, test_function.get_id(), None, false);
205-
match compiled_program {
206-
Ok(compiled_program) => {
207-
let runner = TestRunner::default();
208-
209-
let fuzzer = FuzzedExecutor::new(compiled_program.into(), runner);
210-
211-
let result = fuzzer.fuzz();
212-
if result.success {
213-
TestStatus::Pass
214-
} else {
215-
TestStatus::Fail {
216-
message: result.reason.unwrap_or_default(),
217-
error_diagnostic: None,
218-
}
219-
}
220-
}
221-
Err(err) => TestStatus::CompileError(err.into()),
222-
}
223-
}
182+
nargo::ops::run_test(
183+
&blackbox_solver,
184+
&mut context,
185+
test_function,
186+
show_output,
187+
foreign_call_resolver_url,
188+
compile_options,
189+
)
224190
}
225191

226192
fn get_tests_in_package(

tooling/nargo_cli/tests/stdlib-tests.rs

Lines changed: 9 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::io::Write;
22
use std::{collections::BTreeMap, path::PathBuf};
33

44
use fm::FileManager;
5-
use noirc_driver::{check_crate, compile_no_check, file_manager_with_stdlib, CompileOptions};
5+
use noirc_driver::{check_crate, file_manager_with_stdlib, CompileOptions};
66
use noirc_frontend::hir::FunctionNameMatch;
77

88
use nargo::{
@@ -47,52 +47,14 @@ fn run_stdlib_tests() {
4747
let test_report: Vec<(String, TestStatus)> = test_functions
4848
.into_iter()
4949
.map(|(test_name, test_function)| {
50-
let test_function_has_no_arguments = context
51-
.def_interner
52-
.function_meta(&test_function.get_id())
53-
.function_signature()
54-
.0
55-
.is_empty();
56-
57-
let status = if test_function_has_no_arguments {
58-
run_test(
59-
&bn254_blackbox_solver::Bn254BlackBoxSolver,
60-
&mut context,
61-
&test_function,
62-
false,
63-
None,
64-
&CompileOptions::default(),
65-
)
66-
} else {
67-
use noir_fuzzer::FuzzedExecutor;
68-
use proptest::test_runner::TestRunner;
69-
70-
let compiled_program = compile_no_check(
71-
&mut context,
72-
&CompileOptions::default(),
73-
test_function.get_id(),
74-
None,
75-
false,
76-
);
77-
match compiled_program {
78-
Ok(compiled_program) => {
79-
let runner = TestRunner::default();
80-
81-
let fuzzer = FuzzedExecutor::new(compiled_program.into(), runner);
82-
83-
let result = fuzzer.fuzz();
84-
if result.success {
85-
TestStatus::Pass
86-
} else {
87-
TestStatus::Fail {
88-
message: result.reason.unwrap_or_default(),
89-
error_diagnostic: None,
90-
}
91-
}
92-
}
93-
Err(err) => TestStatus::CompileError(err.into()),
94-
}
95-
};
50+
let status = run_test(
51+
&bn254_blackbox_solver::Bn254BlackBoxSolver,
52+
&mut context,
53+
&test_function,
54+
false,
55+
None,
56+
&CompileOptions::default(),
57+
);
9658
(test_name, status)
9759
})
9860
.collect();

0 commit comments

Comments
 (0)