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
20 changes: 20 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,23 @@ jobs:
uses: dtolnay/rust-toolchain@stable
- run: rustup target add wasm32-unknown-unknown
- run: cargo check --target wasm32-unknown-unknown

Fuzz:
name: Check Fuzz
needs: Prepare
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- name: "Checkout repo"
uses: actions/checkout@v4

- name: "Select nightly toolchain"
uses: dtolnay/rust-toolchain@v1
with:
toolchain: ${{ needs.Prepare.outputs.nightly_version }}

- name: "Check fuzz crate"
run: |
cargo check --manifest-path=fuzz/Cargo.toml

40 changes: 39 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ clap = "4.5.37"
getrandom = { version = "0.2", features = ["js"] }

[workspace]
members = ["codegen"]
exclude = ["fuzz", "bitcoind-tests"]
members = ["codegen", "fuzz"]
exclude = ["bitcoind-tests"]

[lints.clippy]
# Exclude lints we don't think are valuable.
Expand Down
6 changes: 6 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ simplicityhl = { path = "..", features = ["arbitrary", "serde"] }
itertools = "0.13.0"
serde_json = "1.0.105"

[dev-dependencies]
base64 = "0.22.1"

[lints.rust]
unexpected_cfgs = { level = "deny", check-cfg = ['cfg(fuzzing)'] }

[[bin]]
name = "compile_text"
path = "fuzz_targets/compile_text.rs"
Expand Down
36 changes: 27 additions & 9 deletions fuzz/fuzz_targets/compile_parse_tree.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#![no_main]
#![cfg_attr(fuzzing, no_main)]

use arbitrary::Arbitrary;
use libfuzzer_sys::fuzz_target;
#[cfg(any(fuzzing, test))]
fn do_test(data: &[u8]) {
use arbitrary::Arbitrary;

use simplicityhl::error::WithFile;
use simplicityhl::{ast, named, parse, ArbitraryOfType, Arguments};
use simplicityhl::error::WithFile;
use simplicityhl::{ast, named, parse, ArbitraryOfType, Arguments};

fuzz_target!(|data: &[u8]| {
let mut u = arbitrary::Unstructured::new(data);
let parse_program = match parse::Program::arbitrary(&mut u) {
Ok(x) => x,
Expand All @@ -24,6 +24,24 @@ fuzz_target!(|data: &[u8]| {
.compile(arguments, false)
.with_file("")
.expect("AST should compile with given arguments");
let _simplicity_commit = named::to_commit_node(&simplicity_named_construct)
.expect("Conversion to commit node should never fail");
});
let _simplicity_commit = named::forget_names(&simplicity_named_construct);
}

#[cfg(fuzzing)]
libfuzzer_sys::fuzz_target!(|data| do_test(data));

#[cfg(not(fuzzing))]
fn main() {}

#[cfg(test)]
mod tests {
use base64::Engine;

#[test]
fn duplicate_crash() {
let data = base64::prelude::BASE64_STANDARD
.decode("Cg==")
.expect("base64 should be valid");
super::do_test(&data);
}
}
36 changes: 29 additions & 7 deletions fuzz/fuzz_targets/compile_text.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
#![no_main]

use arbitrary::Arbitrary;
use libfuzzer_sys::{fuzz_target, Corpus};

use simplicityhl::{ArbitraryOfType, Arguments};
#![cfg_attr(fuzzing, no_main)]

/// The PEST parser is slow for inputs with many open brackets.
/// Detect some of these inputs to reject them from the corpus.
///
/// ```text
/// fn n(){ { (s,(( (Ns,(s,(x,(((s,((s,(s,(s,(x,(( {5
/// ```
#[cfg(any(fuzzing, test))]
fn slow_input(program_text: &str) -> bool {
let mut consecutive_open_brackets = 0;

Expand All @@ -28,7 +24,12 @@ fn slow_input(program_text: &str) -> bool {
false
}

fuzz_target!(|data: &[u8]| -> Corpus {
#[cfg(any(fuzzing, test))]
fn do_test(data: &[u8]) -> libfuzzer_sys::Corpus {
use arbitrary::Arbitrary;
use libfuzzer_sys::Corpus;
use simplicityhl::{ArbitraryOfType, Arguments};

let mut u = arbitrary::Unstructured::new(data);

let program_text = match <String>::arbitrary(&mut u) {
Expand All @@ -49,4 +50,25 @@ fuzz_target!(|data: &[u8]| -> Corpus {
let _ = template.instantiate(arguments, false);

Corpus::Keep
}

#[cfg(fuzzing)]
libfuzzer_sys::fuzz_target!(|data: &[u8]| {
let _ = do_test(data);
});

#[cfg(not(fuzzing))]
fn main() {}

#[cfg(test)]
mod tests {
use base64::Engine;

#[test]
fn duplicate_crash() {
let data = base64::prelude::BASE64_STANDARD
.decode("Cg==")
.expect("base64 should be valid");
super::do_test(&data);
}
}
35 changes: 29 additions & 6 deletions fuzz/fuzz_targets/display_parse_tree.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
#![no_main]
#![cfg_attr(fuzzing, no_main)]

use libfuzzer_sys::fuzz_target;
#[cfg(any(fuzzing, test))]
fn do_test(parse_program: simplicityhl::parse::Program) {
use simplicityhl::parse::{ParseFromStr, Program};

use simplicityhl::parse::{self, ParseFromStr};

fuzz_target!(|parse_program: parse::Program| {
let program_text = parse_program.to_string();
let restored_parse_program = parse::Program::parse_from_str(program_text.as_str())
let restored_parse_program = Program::parse_from_str(program_text.as_str())
.expect("Output of fmt::Display should be parseable");
assert_eq!(
parse_program, restored_parse_program,
"Output of fmt::Display should parse to original program"
);
}

#[cfg(not(fuzzing))]
fn main() {}

#[cfg(fuzzing)]
libfuzzer_sys::fuzz_target!(|data: simplicityhl::parse::Program| {
do_test(data);
});

#[cfg(test)]
mod test {

use simplicityhl::parse::{ParseFromStr, Program};
#[test]
fn test() {
let program_test = r#"fn main() {
assert!(jet::eq_32(witness::A, witness::A));
}"#;

let program = Program::parse_from_str(program_test)
.expect("expected conversion to Program to be successfull");
super::do_test(program);
}
}
31 changes: 26 additions & 5 deletions fuzz/fuzz_targets/parse_value_rtt.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
#![no_main]
#![cfg_attr(fuzzing, no_main)]

use libfuzzer_sys::fuzz_target;
#[cfg(any(fuzzing, test))]
fn do_test(value: simplicityhl::value::Value) {
use simplicityhl::value::Value;

use simplicityhl::value::Value;

fuzz_target!(|value: Value| {
let value_string = value.to_string();
let parsed_value =
Value::parse_from_str(&value_string, value.ty()).expect("Value string should be parseable");
assert_eq!(
value, parsed_value,
"Value string should parse to original value"
);
}

#[cfg(not(fuzzing))]
fn main() {}

#[cfg(fuzzing)]
libfuzzer_sys::fuzz_target!(|data: simplicityhl::value::Value| {
do_test(data);
});

#[cfg(test)]
mod test {
use simplicityhl::{types::TypeConstructible, value::Value, ResolvedType};

use crate::do_test;
#[test]
fn test() {
let value = Value::parse_from_str("true", &ResolvedType::boolean())
.expect("should parse a valid value");

do_test(value);
}
}
34 changes: 27 additions & 7 deletions fuzz/fuzz_targets/parse_witness_json_rtt.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#![no_main]
#![cfg_attr(fuzzing, no_main)]

use libfuzzer_sys::fuzz_target;

use simplicityhl::WitnessValues;

fuzz_target!(|witness_values: WitnessValues| {
#[cfg(any(fuzzing, test))]
fn do_test(witness_values: simplicityhl::WitnessValues) {
let witness_text = serde_json::to_string(&witness_values)
.expect("Witness map should be convertible into JSON");
let parsed_witness_text =
Expand All @@ -13,4 +10,27 @@ fuzz_target!(|witness_values: WitnessValues| {
witness_values, parsed_witness_text,
"Witness JSON should parse to original witness map"
);
});
}

#[cfg(not(fuzzing))]
fn main() {}

#[cfg(fuzzing)]
libfuzzer_sys::fuzz_target!(|data: simplicityhl::WitnessValues| do_test(data));

#[cfg(test)]
mod test {
use simplicityhl::{parse::ParseFromStr, WitnessValues};
#[test]
fn test() {
let witness_text = r#"mod witness {
const A: u32 = 1;
const B: u32 = 2;
const C: u32 = 3;
}"#;

let witness_values = WitnessValues::parse_from_str(witness_text)
.expect("parsing of valid string should work");
super::do_test(witness_values);
}
}
Loading