Skip to content
Open
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
34 changes: 34 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["harper-cli", "harper-core", "harper-ls", "harper-comments", "harper-wasm", "harper-tree-sitter", "harper-html", "harper-literate-haskell", "harper-typst", "harper-stats", "harper-pos-utils", "harper-brill", "harper-ink", "harper-python"]
members = [ "harper-cli", "harper-core", "harper-ls", "harper-comments", "harper-wasm", "harper-tree-sitter", "harper-html", "harper-literate-haskell", "harper-typst", "fuzz" , "harper-stats", "harper-pos-utils", "harper-brill", "harper-ink", "harper-python"]
resolver = "2"

# Comment out the below lines if you plan to use a debugger.
Expand Down
4 changes: 4 additions & 0 deletions fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
target
corpus
artifacts
coverage
52 changes: 52 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[package]
name = "fuzz"
version = "0.0.0"
publish = false
edition = "2024"

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer-sys = "0.4"

harper-core = { path = "../harper-core" }
harper-typst = { path = "../harper-typst" }
harper-literate-haskell = { path = "../harper-literate-haskell" }
harper-html = { path = "../harper-html" }
harper-comments = { path = "../harper-comments" }

[[bin]]
name = "fuzz_harper_typst"
path = "fuzz_targets/fuzz_harper_typst.rs"
test = false
doc = false
bench = false

[[bin]]
name = "fuzz_harper_literate_haskell"
path = "fuzz_targets/fuzz_harper_literate_haskell.rs"
test = false
doc = false
bench = false

[[bin]]
name = "fuzz_harper_html"
path = "fuzz_targets/fuzz_harper_html.rs"
test = false
doc = false
bench = false

[[bin]]
name = "fuzz_harper_comment"
path = "fuzz_targets/fuzz_harper_comment.rs"
test = false
doc = false
bench = false

[[bin]]
name = "fuzz_harper_core_markdown"
path = "fuzz_targets/fuzz_harper_core_markdown.rs"
test = false
doc = false
bench = false
39 changes: 39 additions & 0 deletions fuzz/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# cargo-fuzz targets

## Setup

Follow the rust-fuzz [setup guide](https://rust-fuzz.github.io/book/cargo-fuzz/setup.html).
You need a nightly toolchain and the cargo-fuzz plugin.

Simple installation steps:

- `rustup install nightly`
- `cargo install cargo-fuzz`

## Adding a new fuzzing target

To add a new target, run `cargo fuzz add $TARGET_NAME`


## Doing a fuzzing run

First, make sure that lto is turned off, otherwise you'll encounter linker errors.
You can do this via the workspace `Cargo.toml` or the `CARGO_PROFILE_RELEASE_LTO` environment variable.

If possible, prefill the `fuzz/corpus/$TARGET_NAME` directory with appropriate examples to speed up fuzzing.
The fuzzer should be coverage aware, so providing a well formed input document to fuzzing targets only expecting a string as input can speed things up a lot.

Then, run `CARGO_PROFILE_RELEASE_LTO=false cargo +nightly fuzz run $TARGET_NAME -- -timeout=$TIMEOUT`

The timeout flag accepts a timeout in seconds, after which a long-running test case will be aborted.
This should be set to a low number to quickly report endless loops / deep recursion in parsers.

The normal fuzzing run will continue until a crash is found.

## Minifying a test case

Once the fuzzer finds a crash, we probably want to minify the result.
This can be done with `CARGO_PROFILE_RELEASE_LTO=false cargo +nightly fuzz tmin $TARGET $TEST_CASE_PATH`



76 changes: 76 additions & 0 deletions fuzz/fuzz_targets/fuzz_harper_comment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#![no_main]

use harper_core::parsers::{MarkdownOptions, StrParser};
use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured};
use libfuzzer_sys::fuzz_target;

#[derive(Debug)]
struct Language(String);

const LANGUAGES: [&str; 32] = [
"cmake",
"cpp",
"csharp",
"c",
"dart",
"go",
"haskell",
"javascriptreact",
"javascript",
"java",
"kotlin",
"lua",
"nix",
"php",
"python",
"ruby",
"rust",
"scala",
"shellscript",
"solidity",
"swift",
"toml",
"typescriptreact",
"typescript",
"clojure",
"go",
"lua",
"java",
"javascriptreact",
"typescript",
"typescriptreact",
"solidity",
];

impl<'a> Arbitrary<'a> for Language {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let &lang = u.choose(&LANGUAGES)?;
Ok(Language(lang.to_owned()))
}
}

#[derive(Debug)]
struct Input {
language: Language,
text: String,
}

impl<'a> Arbitrary<'a> for Input {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let (language, text) = Arbitrary::arbitrary(u)?;
Ok(Input { language, text })
}

fn arbitrary_take_rest(u: Unstructured<'a>) -> Result<Self> {
let (language, text) = Arbitrary::arbitrary_take_rest(u)?;
Ok(Input { language, text })
}
}

fuzz_target!(|data: Input| {
let opts = MarkdownOptions::default();
let parser = harper_comments::CommentParser::new_from_language_id(&data.language.0, opts);
if let Some(parser) = parser {
let _res = parser.parse_str(&data.text);
}
});
10 changes: 10 additions & 0 deletions fuzz/fuzz_targets/fuzz_harper_core_markdown.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#![no_main]

use harper_core::parsers::{Markdown, MarkdownOptions, StrParser};
use libfuzzer_sys::fuzz_target;

fuzz_target!(|data: &str| {
let opts = MarkdownOptions::default();
let parser = Markdown::new(opts);
let _res = parser.parse_str(data);
});
9 changes: 9 additions & 0 deletions fuzz/fuzz_targets/fuzz_harper_html.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#![no_main]

use harper_core::parsers::StrParser;
use libfuzzer_sys::fuzz_target;

fuzz_target!(|data: &str| {
let parser = harper_html::HtmlParser::default();
let _res = parser.parse_str(data);
});
9 changes: 9 additions & 0 deletions fuzz/fuzz_targets/fuzz_harper_literate_haskell.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#![no_main]

// use harper_core::parsers::StrParser;
use libfuzzer_sys::fuzz_target;

fuzz_target!(|_data: &str| {
// TODO: figure out how to create a literate haskell parser
// let _res = typst.parse_str(&data);
});
9 changes: 9 additions & 0 deletions fuzz/fuzz_targets/fuzz_harper_typst.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#![no_main]

use harper_core::parsers::StrParser;
use libfuzzer_sys::fuzz_target;

fuzz_target!(|data: &str| {
let typst = harper_typst::Typst;
let _res = typst.parse_str(data);
});
Loading