diff --git a/Cargo.lock b/Cargo.lock index 093424fe..44173ea7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,7 @@ name = "a-mir-formality" version = "0.1.0" dependencies = [ "anyhow", + "bolero", "clap", "expect-test", "formality-check", @@ -20,6 +21,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aho-corasick" version = "1.1.2" @@ -104,17 +120,124 @@ dependencies = [ "winapi", ] +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bitflags" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +[[package]] +name = "bolero" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449a08ce58d0325f2a1df902aa326a84ab49c6aa2844f1a88fe5e0fc23c75010" +dependencies = [ + "bolero-afl", + "bolero-engine", + "bolero-generator", + "bolero-honggfuzz", + "bolero-kani", + "bolero-libfuzzer", + "cfg-if", + "rand", +] + +[[package]] +name = "bolero-afl" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73c04ea97df540ec2224453cb006f4ab6cacc99636f9f6660b381970448bbaac" +dependencies = [ + "bolero-engine", + "cc", +] + +[[package]] +name = "bolero-engine" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2558810916445396d4fa12415183c96cfeac7c9e84ef230dc33e2e1f8beca41e" +dependencies = [ + "anyhow", + "backtrace", + "bolero-generator", + "lazy_static", + "pretty-hex", + "rand", + "rand_xoshiro", +] + +[[package]] +name = "bolero-generator" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c6b00e768523c6e13397f1e5e6cef7f8dbd01621ffd7aa6dec74b5ba3e1cf94" +dependencies = [ + "bolero-generator-derive", + "either", + "rand_core", +] + +[[package]] +name = "bolero-generator-derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea8182a94fa5c6d0db1666969d0094e79d6b93eb68689325a375b1d8804a12b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.102", +] + +[[package]] +name = "bolero-honggfuzz" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b744c6bda3711a1c88ab7cbd5053b53daca1c7380d05cfa3af39c99d0ec52c0" +dependencies = [ + "bolero-engine", +] + +[[package]] +name = "bolero-kani" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aee97a8044096c7b4a6fb4f5f5003544ad98277ebc7a65567a6c4ea912393388" +dependencies = [ + "bolero-engine", +] + +[[package]] +name = "bolero-libfuzzer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae3b3751a4b0dc94696063eace32fd9d3ddea904ae9e18d87c81de787023508" +dependencies = [ + "bolero-engine", + "cc", +] + [[package]] name = "cc" -version = "1.0.79" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d" [[package]] name = "cfg-if" @@ -241,6 +364,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.1" @@ -322,6 +451,7 @@ name = "formality-core" version = "0.1.1" dependencies = [ "anyhow", + "bolero", "contracts", "either", "env_logger", @@ -369,6 +499,7 @@ name = "formality-rust" version = "0.1.0" dependencies = [ "anyhow", + "bolero", "expect-test", "formality-core", "formality-macros", @@ -389,6 +520,7 @@ name = "formality-types" version = "0.1.0" dependencies = [ "anyhow", + "bolero", "contracts", "expect-test", "extension-trait", @@ -398,6 +530,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "heck" version = "0.4.0" @@ -425,6 +580,16 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is-terminal" version = "0.4.9" @@ -462,9 +627,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linux-raw-sys" @@ -496,6 +661,24 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "object" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.15.0" @@ -517,6 +700,18 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "pretty-hex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc83ee4a840062f368f9096d80077a9841ec117e17e7f700df81958f1451254" + [[package]] name = "pretty_assertions" version = "1.3.0" @@ -529,6 +724,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime", + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.66" @@ -556,6 +761,45 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core", +] + [[package]] name = "regex" version = "1.10.2" @@ -600,6 +844,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "rustix" version = "0.38.4" @@ -693,6 +943,23 @@ dependencies = [ "once_cell", ] +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.40" @@ -794,6 +1061,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi" version = "0.3.9" @@ -891,6 +1164,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 0c07551d..b1124cb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ formality-prove = { version = "0.1.0", path = "crates/formality-prove" } formality-core = { version = "0.1.0", path = "crates/formality-core" } formality-smir = { version = "0.1.0", path = "crates/formality-smir" } expect-test = "1.4.0" +bolero = "0.11.1" [workspace] members = [ diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index d5a59504..d197d473 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -10,4 +10,5 @@ - [Variables](./formality_core/variables.md) - [Collections](./formality_core/collections.md) - [Judgment functions and inference rules](./formality_core/judgment_fn.md) + - [Fuzzing](./formality_core/fuzzing.md) - [FAQ and troubleshooting](./formality_core/faq.md) diff --git a/book/src/formality_core/fuzzing.md b/book/src/formality_core/fuzzing.md new file mode 100644 index 00000000..afdcf135 --- /dev/null +++ b/book/src/formality_core/fuzzing.md @@ -0,0 +1,91 @@ +# Fuzzing + +Formality programs can be fuzzed with [bolero](https://crates.io/crates/bolero) but as of now the integration is fairly weak and requires some manual intervention. One of the challenges is that bolero's traits do not allow us to easily thread context through, so we must rely on static variables. The other challenge is that we wish to steer the fuzzer to generate "mostly valid" programs, and that means that the integration cannot be fully auto-generated. + +## Auto-derive and when to use it + +Enabling most types for use with bolero is as simple as annotating types with `#[derive(bolero::TypeGenerator)]`. +This works great as long as any value for the fields is potentialy valid. +But when you have fields that are meant to be references to other items in the program, you are likely to get nonsense: +not necessarily a *problem*, but likely a waste of fuzzing time and effort. +Navigating this requires writing custom fuzzing implementations. + +## Common reference types + +The two most common "references" are identifiers (e.g., names of structs) and type variables. +As these are part of formality-core we have some built-in support for generating them. + +### FuzzSingleton + +The `formality_core::fuzz::FuzzSingleton` is a useful type for setting up global context that can be accessed from `TypeGenerator` implementations. To use it, declare a static like + +```rust +static F: FuzzSingleton> = F::new(); +``` + +you can then use `F.get()` to read the current value (initialized with `Default::default`). + +You can modify the value with methods like `F.set()` and `F.push()`. These will return a "guard" value that resets the singleton back to its initial state. Make sure you drop that guard appropriately, typically at the end of the block: + +```rust +let _guard = F.set(new_value); +``` + +The `set` method is intended to be used once per fuzzing session and hence has an assertion that the value is currently at its default value. + +### Identifiers + +When you declare an identifier type `SomeId` with the `id!` macro, +it also creates an associated "fuzzing pool" accessible via `SomeId::fuzz_pool()`. +This is a static vector of identifiers you can use. +When an identifier is fuzzed, it will pick a value from the fuzzing pool. + +The fuzzing pool starts empty. You can add entries to it by invoking `push` on the fuzzing pool. + +A common pattern is to generate a set of identifiers early on (e.g., a set of struct names your program will have) and then set the fuzzing pool to contain those identifiers. + +### Bound variables + +The built-in variable types (universal, existential, etc) have fuzzing implementations. +The intention is to only have the fuzzer generate closed terms with no free variables. + +Every formality language `L` has a fuzzing pool of variables in scope that can be referenced. +This begins as empty. +You can push new entries onto it with `L::open_fuzz_binder(kinds)`, which will create a set of variables `V` with the given kinds. +It returns a guard that will remove those variables from scope; the guard also has a method `into_binder` +that can be used to close over the variables `V` and create a `Binder`. + +Fuzzing a variable generates a reference to one of the variables pushed by `open_fuzz_binder`. +These are `BoundVariable` elements with those kinds and with depth set to `None`, just as you get with `Binder::open`. +They are meant to be enclosed later in a binder with the `into_binder` method. + +When you fuzz a `Binder`, it follows this sequence: + +* Fuzz some set of kinds `K` +* Invoke `guard = L::open_fuzz_binder(K)` to push a set of variables `V` in scope +* Fuzz a `T` that will reference variables in `V` (and possibly others that are already in scope) +* Close over the `T` with `guard.into_binder(T)`, returning a `Binder` where each reference variable in `V` now refers to an element in the binder. `V` are also removed from scope. + +If you wish to fuzz a value that references a known set of variables, you can do so by invoking `L::open_fuzz_binder` yourself. + +## How formality-Rust fuzzing works + +The formality Rust fuzzer tries to generate "mostly well-kinded" programs. The pattern for generating structs is as follows: + +* Generate a set of struct names and their generic parameters, effeciively a set of `(String, Vec)` tuples. + * Note that a **string** is used to let the fuzzer generate fresh names. +* Push each of the struct names into `AdtId::fuzz_pool()` as an available name for reference. + * Also store the kinds into a `FuzzSingleton>>` +* Generate struct definitions as follows. For each defined struct `(S, K)` with name `S` and kinds `K`: + * Manually invoke `let guard = L::open_fuzz_binder(K)` to bring those parameters into scope + * Generate the "body" of the struct (`StructBoundData`) + * Close over `guard.into_binder(body)` to get a `Binder` that can be used in the struct definition +* Generate references to structs as follows. Whenever we fuzz a type, provide a custom `TypeGenerator` impl that will + * Fuzz an `AdtId`, picking a struct name from the availabel list + * Look up its kinds from the map + * Generate a suitable set of parameters that match those kinds + + + + + diff --git a/crates/formality-core/Cargo.toml b/crates/formality-core/Cargo.toml index c30bb99e..7387f487 100644 --- a/crates/formality-core/Cargo.toml +++ b/crates/formality-core/Cargo.toml @@ -25,6 +25,7 @@ itertools = "0.12.0" either = "1.9.0" expect-test = "1.4.1" regex = "1.10.2" +bolero = "0.11.1" [dev-dependencies] expect-test = "1.4.1" diff --git a/crates/formality-core/src/binder.rs b/crates/formality-core/src/binder.rs index 733f862b..58b9adef 100644 --- a/crates/formality-core/src/binder.rs +++ b/crates/formality-core/src/binder.rs @@ -16,6 +16,8 @@ use crate::{ Fallible, }; +pub mod fuzz; + #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct CoreBinder { kinds: Vec>, @@ -282,3 +284,19 @@ where } } } + +/// Creates a fresh bound var of the given kind that is not yet part of a binder. +/// You can put this into a term and then use `Binder::new`. +pub fn fresh_bound_var(kind: L::Kind) -> CoreBoundVar { + lazy_static! { + static ref COUNTER: AtomicUsize = AtomicUsize::new(0); + } + + let index = COUNTER.fetch_add(1, Ordering::SeqCst); + let var_index = VarIndex { index }; + CoreBoundVar { + debruijn: None, + var_index, + kind, + } +} diff --git a/crates/formality-core/src/binder/fuzz.rs b/crates/formality-core/src/binder/fuzz.rs new file mode 100644 index 00000000..c4f66f90 --- /dev/null +++ b/crates/formality-core/src/binder/fuzz.rs @@ -0,0 +1,60 @@ +use crate::{fold::CoreFold, fuzz::PushGuard, language::Language, variable::CoreBoundVar}; + +use super::{fresh_bound_var, CoreBinder}; + +/// Brings new variables into scope for fuzzing. +/// Don't invoke directly, instead call `L::open_fuzz_binder`. +pub(crate) fn open_fuzz_binder_impl(kinds: &[L::Kind]) -> PushKindGuard +where + L: Language, +{ + let variables: Vec<_> = kinds.iter().map(|k| fresh_bound_var(*k)).collect(); + PushKindGuard { + guard: L::fuzz_free_variables().fuzz_push(variables.clone()), + variables, + } +} + +/// The guard returned when you open a binder for fuzzing. +/// It references a set of variables `V` that were brought into scope. +/// You should invoke `self.into_binder(t)` on the fuzzed term `t` to create a `Binder` +/// where each variable in `V` is converted to a reference to the binder. +/// +/// See the Formality Book [chapter on fuzzing][f] for more details. +/// +/// [f]: https://rust-lang.github.io/a-mir-formality/formality_core/fuzzing.html +#[must_use] +pub struct PushKindGuard { + #[allow(dead_code)] // the point of this field is to run the destructor + guard: PushGuard<'static, CoreBoundVar>, + variables: Vec>, +} + +impl PushKindGuard { + /// Access the variables that were brought into scope. + pub fn variables(&self) -> &Vec> { + &self.variables + } + + /// Convert into a binder. + pub fn into_binder(self, bound_term: T) -> CoreBinder + where + T: CoreFold, + { + CoreBinder::new(self.variables, bound_term) + } +} + +impl bolero::TypeGenerator for CoreBinder +where + T: bolero::TypeGenerator + CoreFold, + L::Kind: bolero::TypeGenerator, +{ + /// Generate a binder with some fresh data inside. + fn generate(driver: &mut D) -> Option { + let kinds: Vec = driver.gen()?; + let guard = L::open_fuzz_binder(&kinds); + let bound_term: T = driver.gen()?; + Some(guard.into_binder(bound_term)) + } +} diff --git a/crates/formality-core/src/fuzz.rs b/crates/formality-core/src/fuzz.rs new file mode 100644 index 00000000..fcb35419 --- /dev/null +++ b/crates/formality-core/src/fuzz.rs @@ -0,0 +1,135 @@ +//! See the Formality Book [chapter on fuzzing][f] for more details. +//! +//! [f]: https://rust-lang.github.io/a-mir-formality/formality_core/fuzzing.html + +use std::fmt::Debug; +use std::sync::OnceLock; +use std::{ops::Deref, sync::RwLock}; + +use crate::Map; + +/// A global singleton accessible from anywhere. +/// Used to thread state for fuzzing. +/// +/// See the Formality Book [chapter on fuzzing][f] for more details. +/// +/// [f]: https://rust-lang.github.io/a-mir-formality/formality_core/fuzzing.html +pub struct FuzzSingleton { + data: OnceLock>, +} + +/// Trait for clearing the value of a singleton. +/// Not invoked from outside. +trait FuzzClear { + fn clear(&self); +} + +/// Guard that clears the singleton back to its default value after `set` is called. +pub struct SetGuard<'s> { + pool: &'s dyn FuzzClear, +} + +impl FuzzSingleton +where + I: Debug + Default + Eq, +{ + /// Create a new instance; this is a `const` so that the value can be stored in a `static`. + pub const fn new() -> Self { + Self { + data: OnceLock::new(), + } + } + + /// Internal method: access the data, initializing if needed. + fn data(&self) -> &RwLock { + self.data.get_or_init(|| RwLock::new(I::default())) + } + + /// Set the set of available names. + /// + /// This is intended to be called once per fuzzing session, + /// so it will panic if the value has already been "set" from its default value. + /// + /// Returns a guard that will restore the value to the default when dropped. + /// Once this guard is dropped, you can call `set` again. + pub fn set(&self, new_data: I) -> SetGuard<'_> { + let mut data = self.data().write().unwrap(); + let old_data = std::mem::replace(&mut *data, new_data); + assert!( + I::default() == old_data, + "cannot fuzz more than one program at a time, already fuzzing `{old_data:?}`" + ); + SetGuard { pool: self } + } + + /// Access the data. Will deadlock if you try to set before the guard is dropped. + pub fn get(&self) -> impl Deref + '_ { + self.data().read().unwrap() + } +} + +impl FuzzClear for FuzzSingleton +where + I: Debug + Default + Eq, +{ + /// Clear the set of available names. + /// Invoked by the guard returned from `set`. + fn clear(&self) { + *self.data().write().unwrap() = Default::default(); + } +} + +impl FuzzSingleton> +where + K: Debug + Ord, + V: Debug + Clone, +{ + pub fn get_key(&self, key: K) -> V { + self.get().get(&key).cloned().unwrap() + } +} + +impl FuzzSingleton> { + /// Pick one of the available names from the fuzzer, + /// returning `None` if there are no available names or the fuzzer + /// ran out of data. + pub fn fuzz_pick(&self, driver: &mut impl bolero::Driver) -> Option { + let data = self.get(); + if data.is_empty() { + return None; + } + let i = driver.gen_variant(data.len(), 0)?; + Some(data[i].clone()) + } + + /// Push names into the pool of available values that will be used later + /// by `fuzz_pick`. Returns a guard that will pop them from the pool. + pub fn fuzz_push(&self, values: Vec) -> PushGuard<'_, E> { + let mut data = self.data().write().unwrap(); + let len = data.len(); + data.extend(values); + PushGuard { pool: self, len } + } +} + +/// Guard to pop names added by `fuzz_push`. +pub struct PushGuard<'s, E: Clone + Debug + Eq> { + pool: &'s FuzzSingleton>, + len: usize, +} + +impl Drop for PushGuard<'_, E> +where + E: Clone + Debug + Eq, +{ + fn drop(&mut self) { + let mut data = self.pool.data().write().unwrap(); + data.truncate(self.len); + } +} + +impl Drop for SetGuard<'_> { + fn drop(&mut self) { + self.pool.clear(); + } +} diff --git a/crates/formality-core/src/language.rs b/crates/formality-core/src/language.rs index 02aeaaaf..9217104e 100644 --- a/crates/formality-core/src/language.rs +++ b/crates/formality-core/src/language.rs @@ -1,4 +1,6 @@ +use crate::binder::fuzz::{open_fuzz_binder_impl, PushKindGuard}; use crate::cast::UpcastFrom; +use crate::fuzz::FuzzSingleton; use crate::term::CoreTerm; use crate::variable::{CoreBoundVar, CoreExistentialVar, CoreUniversalVar, CoreVariable}; use std::fmt::Debug; @@ -33,6 +35,22 @@ pub trait Language: 'static + Copy + Ord + Hash + Debug + Default { /// but it requires custom parsing impls for your types. /// No fun. const KEYWORDS: &'static [&'static str]; + + /// Brings new variables into scope for fuzzing. + /// These new bound variables may be referenced when you fuzz a `Variable`. + /// To close the fuzz binder, use [`PushKindGuard::into_binder`](PushKindGuard::into_binder) + /// method. + /// + /// See the Formality Book [chapter on fuzzing][f] for more details. + /// + /// [f]: https://rust-lang.github.io/a-mir-formality/formality_core/fuzzing.html + fn open_fuzz_binder(kinds: &[Self::Kind]) -> PushKindGuard { + open_fuzz_binder_impl::(kinds) + } + + /// Access the data for bound variables in scope. + /// Not normally used directly, instead invoke `push_fuzz_variables`. + fn fuzz_free_variables() -> &'static FuzzSingleton>>; } /// For consistency with types like `CoreVariable`, we write `CoreKind` instead of `Kind`. diff --git a/crates/formality-core/src/lib.rs b/crates/formality-core/src/lib.rs index 3b3ae856..686ce0dc 100644 --- a/crates/formality-core/src/lib.rs +++ b/crates/formality-core/src/lib.rs @@ -11,6 +11,7 @@ extern crate self as formality_core; // in their Cargo.toml. pub use anyhow::anyhow; pub use anyhow::bail; +pub use bolero; pub use contracts::requires; pub use tracing::debug; pub use tracing::instrument; @@ -29,6 +30,7 @@ mod cast; mod collections; pub mod fixed_point; pub mod fold; +pub mod fuzz; pub mod judgment; pub mod language; pub mod parse; @@ -121,6 +123,14 @@ macro_rules! declare_language { const BINDING_OPEN: char = $binding_open; const BINDING_CLOSE: char = $binding_close; const KEYWORDS: &'static [&'static str] = &[$($kw),*]; + fn fuzz_free_variables() -> &'static $crate::fuzz::FuzzSingleton< + Vec<$crate::variable::CoreBoundVar> + > { + static FUZZ_POOL: $crate::fuzz::FuzzSingleton< + Vec<$crate::variable::CoreBoundVar> + > = $crate::fuzz::FuzzSingleton::new(); + &FUZZ_POOL + } } } @@ -200,6 +210,7 @@ macro_rules! id { const _: () = { use $crate::fold::{self, CoreFold}; + use $crate::fuzz::FuzzSingleton; use $crate::parse::{self, CoreParse}; use $crate::variable::CoreVariable; use $crate::visit::CoreVisit; @@ -212,6 +223,17 @@ macro_rules! id { data: std::sync::Arc::new(s.to_string()), } } + + pub fn fuzz_pool() -> &'static FuzzSingleton> { + static FUZZ_POOL: FuzzSingleton> = FuzzSingleton::new(); + &FUZZ_POOL + } + } + + impl $crate::bolero::TypeGenerator for $n { + fn generate(driver: &mut D) -> Option { + Self::fuzz_pool().fuzz_pick(driver) + } } impl std::ops::Deref for $n { diff --git a/crates/formality-core/src/variable.rs b/crates/formality-core/src/variable.rs index 4e3d7ff5..ae11b844 100644 --- a/crates/formality-core/src/variable.rs +++ b/crates/formality-core/src/variable.rs @@ -210,5 +210,18 @@ impl std::ops::Add for VarIndex { } } +impl bolero::TypeGenerator for CoreBoundVar { + fn generate(driver: &mut D) -> Option { + L::fuzz_free_variables().fuzz_pick(driver) + } +} + +impl bolero::TypeGenerator for CoreVariable { + fn generate(driver: &mut D) -> Option { + let v: CoreBoundVar = driver.gen()?; + Some(v.upcast()) + } +} + mod cast_impls; mod debug_impls; diff --git a/crates/formality-rust/Cargo.toml b/crates/formality-rust/Cargo.toml index e20aa1fa..fe26832d 100644 --- a/crates/formality-rust/Cargo.toml +++ b/crates/formality-rust/Cargo.toml @@ -12,6 +12,7 @@ formality-core = { path = "../formality-core" } formality-prove = { path = "../formality-prove" } anyhow = "1.0.66" tracing = "0.1" +bolero = "0.11.1" [dev-dependencies] expect-test = "1.4.0" diff --git a/crates/formality-rust/src/fuzz.rs b/crates/formality-rust/src/fuzz.rs new file mode 100644 index 00000000..2cdddf8e --- /dev/null +++ b/crates/formality-rust/src/fuzz.rs @@ -0,0 +1,107 @@ +//! Generate "mostly valid" Rust programs. +//! +//! See the Formality Book [chapter on fuzzing][f] for more details. +//! +//! [f]: https://rust-lang.github.io/a-mir-formality/formality_core/fuzzing.html + +use bolero::Driver; +use formality_core::{language::Language, Map, Upcast}; +use formality_types::{ + fuzz::FuzzCx, + grammar::{AdtId, CrateId, ParameterKind, TraitId}, +}; + +use crate::grammar::{AdtBoundData, Crate, CrateItem, Enum, Program, Struct, StructBoundData}; + +/// Set of items that we wll declare in our program. +/// We begin by fuzzing an instance of this type. +#[derive(bolero::TypeGenerator)] +struct FuzzItems { + adts: Vec, + traits: Vec, +} + +/// An ADT that will be declared along with its parameter kinds. +/// Its name uses a `String` so that the fuzzer can generate arbitrary names. +#[derive(bolero::TypeGenerator)] +struct FuzzAdt { + name: String, + arity: Vec, +} + +/// A trait that will be declared along with its parameter kinds. +/// Its name uses a `String` so that the fuzzer can generate arbitrary names. +#[derive(bolero::TypeGenerator)] +struct FuzzTrait { + name: String, + arity: Vec, +} + +/// The bound data for an ADT definition (fuzzer can choose between struct/enum). +#[derive(bolero::TypeGenerator)] +enum FuzzAdtBoundData { + Struct(StructBoundData), + Enum(AdtBoundData), +} + +impl bolero::TypeGenerator for Program { + fn generate(driver: &mut D) -> Option { + // First we determine what kind of items there will be. + let items: FuzzItems = driver.gen()?; + + // Then bring those items into scope for what follows. + let _guard = FuzzCx { + adt_kinds: items + .adts + .into_iter() + .map(|f| (AdtId::new(&f.name), f.arity)) + .collect(), + trait_kinds: items + .traits + .into_iter() + .map(|f| (TraitId::new(&f.name), f.arity)) + .collect(), + associated_items: Map::default(), + } + .install(); + + // Now generate the bodies of the type declarations. + let adt_decls: Vec = FuzzCx::adt_id_map() + .get() + .iter() + .map(|(adt_id, kinds)| -> Option { + let guard = crate::FormalityLang::open_fuzz_binder(kinds); + let adt_decl: FuzzAdtBoundData = driver.gen()?; + match adt_decl { + FuzzAdtBoundData::Struct(s) => Some( + Struct { + id: adt_id.clone(), + binder: guard.into_binder(s), + } + .upcast(), + ), + FuzzAdtBoundData::Enum(s) => Some( + Enum { + id: adt_id.clone(), + binder: guard.into_binder(s), + } + .upcast(), + ), + } + }) + .collect::>()?; + + // Now generate the bodies of the traits. + let trait_decls: Vec = vec![/*TODO*/]; + + // Now wrap it up in a crate. + let krate = Crate { + id: CrateId::new("fuzz"), + items: adt_decls.into_iter().chain(trait_decls).collect(), + }; + + Some(Program { + crates: vec![krate], + }) + } +} diff --git a/crates/formality-rust/src/grammar.rs b/crates/formality-rust/src/grammar.rs index 56928abf..71ff10ad 100644 --- a/crates/formality-rust/src/grammar.rs +++ b/crates/formality-rust/src/grammar.rs @@ -111,18 +111,21 @@ impl Struct { } #[term($:where $,where_clauses { $,fields })] +#[derive(bolero::TypeGenerator)] pub struct StructBoundData { pub where_clauses: Vec, pub fields: Vec, } #[term($name : $ty)] +#[derive(bolero::TypeGenerator)] pub struct Field { pub name: FieldName, pub ty: Ty, } #[term] +#[derive(bolero::TypeGenerator)] pub enum FieldName { #[cast] Id(FieldId), @@ -163,12 +166,14 @@ pub struct Adt { } #[term($:where $,where_clauses { $,variants })] +#[derive(bolero::TypeGenerator)] pub struct AdtBoundData { pub where_clauses: Vec, pub variants: Vec, } #[term($name { $,fields })] +#[derive(bolero::TypeGenerator)] pub struct Variant { pub name: VariantId, pub fields: Vec, @@ -324,6 +329,7 @@ pub struct AssociatedTyValueBoundData { } #[term($data)] +#[derive(bolero::TypeGenerator)] pub struct WhereClause { pub data: Arc, } @@ -354,6 +360,7 @@ impl WhereClause { } #[term] +#[derive(bolero::TypeGenerator)] pub enum WhereClauseData { #[grammar($v0 : $v1 $)] IsImplemented(Ty, TraitId, Vec), diff --git a/crates/formality-rust/src/lib.rs b/crates/formality-rust/src/lib.rs index e56d3c4f..28cb53e2 100644 --- a/crates/formality-rust/src/lib.rs +++ b/crates/formality-rust/src/lib.rs @@ -7,3 +7,4 @@ pub mod grammar; pub mod prove; mod test; mod trait_binder; +mod fuzz; \ No newline at end of file diff --git a/crates/formality-types/Cargo.toml b/crates/formality-types/Cargo.toml index 898bc5e9..6ad94328 100644 --- a/crates/formality-types/Cargo.toml +++ b/crates/formality-types/Cargo.toml @@ -13,6 +13,7 @@ tracing = "0.1" contracts = "0.6.3" stacker = "0.1.15" extension-trait = "1.0.1" +bolero = "0.11.1" [dev-dependencies] expect-test = "1.4.0" diff --git a/crates/formality-types/src/fuzz.rs b/crates/formality-types/src/fuzz.rs new file mode 100644 index 00000000..146f937f --- /dev/null +++ b/crates/formality-types/src/fuzz.rs @@ -0,0 +1,80 @@ +//! Fuzzing context for generating "well kinded" Rust types. +//! +//! See the Formality Book [chapter on fuzzing][f] for more details. +//! +//! [f]: https://rust-lang.github.io/a-mir-formality/formality_core/fuzzing.html + +use formality_core::{ + fuzz::{FuzzSingleton, SetGuard}, + Map, +}; + +use crate::grammar::{AdtId, AssociatedItemId, ParameterKind, TraitId}; + +/// The "Fuzz Context" stores the set of ADTs/traits/associated types and their associated kinds. +/// It can be "installed" using `FuzzCz::install` and then when we generate types we will reference it. +/// +/// See the Formality Book [chapter on fuzzing][f] for more details. +/// +/// [f]: https://rust-lang.github.io/a-mir-formality/formality_core/fuzzing.html +pub struct FuzzCx { + pub adt_kinds: Map>, + pub trait_kinds: Map>, + pub associated_items: Map)>, +} + +/// Map from each `AdtId` to its parameter kinds. Modified by `FuzzCx::install` and referenced in the other methods. +static ADT_ID_MAP: FuzzSingleton>> = FuzzSingleton::new(); + +/// Map from each `TraitId` to its parameter kinds. Modified by `FuzzCx::install` and referenced in the other methods. +static TRAIT_ID_MAP: FuzzSingleton>> = FuzzSingleton::new(); + +/// Map from each `AssociatedItemId` to its parameter kinds. Modified by `FuzzCx::install` and referenced in the other methods. +static ASSOCIATED_ID_MAP: FuzzSingleton)>> = + FuzzSingleton::new(); + +/// Guard returned by `FuzzCx::install` to remove this context from scope. +pub struct FuzzCxGuard { + guards: Vec>, +} + +impl FuzzCx { + /// Setup a fuzzing session for fuzzing Rust types. + /// Returns a guard to uninstall the context. + /// + /// Trying to fuzz a type instance will panic if this method has not been + /// called or if the returned guard has been dropped. + /// + /// The returned guard must be dropped before `FuzzCx::install` can be used again. + pub fn install(self) -> FuzzCxGuard { + let mut guards: Vec> = vec![]; + + let adt_ids: Vec = self.adt_kinds.iter().map(|(k, _v)| k).cloned().collect(); + guards.push(AdtId::fuzz_pool().set(adt_ids)); + + let trait_ids: Vec = self.trait_kinds.iter().map(|(k, _v)| k).cloned().collect(); + guards.push(TraitId::fuzz_pool().set(trait_ids)); + + guards.push(ADT_ID_MAP.set(self.adt_kinds)); + + guards.push(TRAIT_ID_MAP.set(self.trait_kinds)); + + FuzzCxGuard { guards } + } + + /// Access the installed map from adt-id to associated parameter kinds. + pub fn adt_id_map() -> &'static FuzzSingleton>> { + &ADT_ID_MAP + } + + /// Access the installed map from trait-id to associated parameter kinds. + pub fn trait_id_map() -> &'static FuzzSingleton>> { + &TRAIT_ID_MAP + } + + /// Access the installed map from associated item id to associated parameter kinds. + pub fn associated_id_map( + ) -> &'static FuzzSingleton)>> { + &ASSOCIATED_ID_MAP + } +} diff --git a/crates/formality-types/src/grammar/binder.rs b/crates/formality-types/src/grammar/binder.rs index 60bc8d53..2028192b 100644 --- a/crates/formality-types/src/grammar/binder.rs +++ b/crates/formality-types/src/grammar/binder.rs @@ -164,22 +164,6 @@ impl Binder { } } -/// Creates a fresh bound var of the given kind that is not yet part of a binder. -/// You can put this into a term and then use `Binder::new`. -pub fn fresh_bound_var(kind: ParameterKind) -> BoundVar { - lazy_static! { - static ref COUNTER: AtomicUsize = AtomicUsize::new(0); - } - - let index = COUNTER.fetch_add(1, Ordering::SeqCst); - let var_index = VarIndex { index }; - BoundVar { - debruijn: None, - var_index, - kind, - } -} - impl Visit for Binder { fn free_variables(&self) -> Vec { self.term.free_variables() diff --git a/crates/formality-types/src/grammar/consts.rs b/crates/formality-types/src/grammar/consts.rs index 4b46910f..9a07af32 100644 --- a/crates/formality-types/src/grammar/consts.rs +++ b/crates/formality-types/src/grammar/consts.rs @@ -8,6 +8,7 @@ pub use valtree::*; #[term] #[cast] #[customize(constructors)] // FIXME: figure out upcasts with arc or special-case +#[derive(bolero::TypeGenerator)] pub struct Const { data: Arc, } @@ -43,6 +44,7 @@ impl Const { #[term] #[customize(parse)] +#[derive(bolero::TypeGenerator)] pub enum ConstData { Value(ValTree, Ty), diff --git a/crates/formality-types/src/grammar/consts/valtree.rs b/crates/formality-types/src/grammar/consts/valtree.rs index d40da934..578251de 100644 --- a/crates/formality-types/src/grammar/consts/valtree.rs +++ b/crates/formality-types/src/grammar/consts/valtree.rs @@ -4,7 +4,7 @@ use formality_core::{Upcast, UpcastFrom}; use super::Bool; -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Visit)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Visit, bolero::TypeGenerator)] pub enum ValTree { Leaf(Scalar), Branches(Vec), @@ -19,7 +19,7 @@ impl std::fmt::Debug for ValTree { } } -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Visit)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Visit, bolero::TypeGenerator)] pub struct Scalar { bits: u128, } diff --git a/crates/formality-types/src/grammar/ty.rs b/crates/formality-types/src/grammar/ty.rs index 8284e10e..bb290174 100644 --- a/crates/formality-types/src/grammar/ty.rs +++ b/crates/formality-types/src/grammar/ty.rs @@ -1,7 +1,7 @@ use formality_core::{cast_impl, term}; use std::sync::Arc; - mod debug_impls; +mod fuzz_impls; mod parse_impls; mod term_impls; use formality_core::{DowncastTo, To, Upcast, UpcastFrom}; @@ -13,6 +13,7 @@ use super::{ #[term] #[cast] +#[derive(bolero::TypeGenerator)] #[customize(constructors)] // FIXME: figure out upcasts with arc or special-case pub struct Ty { data: Arc, @@ -100,6 +101,7 @@ impl DowncastTo for Ty { // NB: TyData doesn't implement Fold; you fold types, not TyData, // because variables might not map to the same variant. #[term] +#[derive(bolero::TypeGenerator)] pub enum TyData { #[cast] RigidTy(RigidTy), @@ -142,6 +144,7 @@ impl DowncastTo for RigidTy { } #[term] +#[derive(bolero::TypeGenerator)] pub enum RigidName { #[grammar((adt $v0))] #[cast] @@ -158,12 +161,14 @@ pub enum RigidName { } #[term] +#[derive(bolero::TypeGenerator)] pub enum RefKind { Shared, Mut, } #[term] +#[derive(bolero::TypeGenerator)] pub enum ScalarId { #[grammar(u8)] U8, @@ -191,6 +196,7 @@ pub enum ScalarId { #[term((alias $name $*parameters))] #[customize(parse, debug)] +#[derive(bolero::TypeGenerator)] pub struct AliasTy { pub name: AliasName, pub parameters: Parameters, @@ -218,6 +224,7 @@ impl AliasTy { } #[term] +#[derive(bolero::TypeGenerator)] pub enum AliasName { #[cast] AssociatedTyId(AssociatedTyName), @@ -236,11 +243,13 @@ pub struct AssociatedTyName { } #[term] +#[derive(bolero::TypeGenerator)] pub enum PredicateTy { ForAll(Binder), } #[term] +#[derive(bolero::TypeGenerator)] pub enum Parameter { #[cast] Ty(Ty), @@ -275,7 +284,7 @@ impl Parameter { pub type Parameters = Vec; #[term] -#[derive(Copy)] +#[derive(Copy, bolero::TypeGenerator)] pub enum ParameterKind { Ty, Lt, @@ -283,7 +292,7 @@ pub enum ParameterKind { } #[term] -#[derive(Copy)] +#[derive(Copy, bolero::TypeGenerator)] pub enum Variance { #[grammar(+)] Covariant, @@ -295,6 +304,7 @@ pub enum Variance { #[term] #[cast] +#[derive(bolero::TypeGenerator)] #[customize(constructors)] // FIXME: figure out upcasts with arc or special-case pub struct Lt { data: Arc, @@ -336,6 +346,7 @@ impl DowncastTo for Lt { } #[term] +#[derive(bolero::TypeGenerator)] pub enum LtData { Static, diff --git a/crates/formality-types/src/grammar/ty/fuzz_impls.rs b/crates/formality-types/src/grammar/ty/fuzz_impls.rs new file mode 100644 index 00000000..eb5f3c79 --- /dev/null +++ b/crates/formality-types/src/grammar/ty/fuzz_impls.rs @@ -0,0 +1,45 @@ +use formality_core::Upcast; + +use crate::{ + fuzz::FuzzCx, + grammar::{AssociatedItemId, Const}, +}; + +use super::{AssociatedTyName, Lt, ParameterKind, RigidName, RigidTy, Ty}; + +impl bolero::TypeGenerator for RigidTy { + fn generate(driver: &mut D) -> Option { + // This is customized so that we can be sure to generate types + // with property arity. + let name: RigidName = driver.gen()?; + let parameter_kinds = match &name { + RigidName::AdtId(adt_id) => FuzzCx::adt_id_map().get().get(adt_id)?.clone(), + RigidName::ScalarId(_) => vec![], + RigidName::Ref(_) => vec![ParameterKind::Lt, ParameterKind::Ty], + RigidName::Tuple(arity) => vec![ParameterKind::Ty; *arity], + RigidName::FnPtr(_) => return None, // FIXME + RigidName::FnDef(_) => return None, // FIXME + }; + let parameters = parameter_kinds + .iter() + .map(|k| match k { + ParameterKind::Ty => driver.gen::().upcast(), + ParameterKind::Lt => driver.gen::().upcast(), + ParameterKind::Const => driver.gen::().upcast(), + }) + .collect::>()?; + Some(RigidTy { name, parameters }) + } +} + +impl bolero::TypeGenerator for AssociatedTyName { + fn generate(driver: &mut D) -> Option { + let item_id: AssociatedItemId = driver.gen()?; + let (trait_id, parameter_kinds) = FuzzCx::associated_id_map().get().get(&item_id)?.clone(); + Some(AssociatedTyName { + trait_id, + item_id, + item_arity: parameter_kinds.len(), + }) + } +} diff --git a/crates/formality-types/src/lib.rs b/crates/formality-types/src/lib.rs index 89adb25c..2d55e83a 100644 --- a/crates/formality-types/src/lib.rs +++ b/crates/formality-types/src/lib.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] +pub mod fuzz; pub mod grammar; // ANCHOR: declare_rust_language diff --git a/src/lib.rs b/src/lib.rs index 31304af4..33773e41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,12 +38,34 @@ struct Args { #[arg(long)] out_dir: Option, - input_path: String, + #[arg(long, default_value = "0")] + generate_fuzzed_programs: u32, + + input_paths: Vec, } pub fn main() -> anyhow::Result<()> { let args = Args::parse(); - let input: String = std::fs::read_to_string(&args.input_path)?; + + // Check any of the input paths for correctness. + for input_path in &args.input_paths { + match check_input(&args, input_path) { + Ok(()) => {} + Err(err) => { + eprintln!("path {} failed to compile:\n{:#?}", input_path, err) + } + } + } + + for _ in 0..args.generate_fuzzed_programs { + generate_fuzzed_program(); + } + + Ok(()) +} + +fn check_input(args: &Args, input_path: &str) -> anyhow::Result<()> { + let input: String = std::fs::read_to_string(input_path)?; let program: Program = try_term(&input)?; if args.print_rust { @@ -53,6 +75,14 @@ pub fn main() -> anyhow::Result<()> { check_all_crates(&program) } +fn generate_fuzzed_program() { + use bolero::check; + + check!() + .with_type::() + .for_each(|p| eprintln!("{p:?}")); +} + #[macro_export] macro_rules! assert_ok { ($input:tt $expect:expr) => {{