From 171bc0f3f3a8f340e388f2d81aa1e2095ea9e2e9 Mon Sep 17 00:00:00 2001 From: Addison Crump Date: Wed, 14 Jun 2023 12:01:35 +0200 Subject: [PATCH] Implement round-trip fuzzers for finding correctness bugs (#4559) * init fuzzers * correct corpus link * add more fuzzers * add formatter fuzzers * document formatter strategy * add fuzzer build to CI * better github workflow * whoops, need to specify where it runs * fix CI * address naming nit * add text diff to formatter * add linter checks to formatter output * correct diff args * use strip dead code (ew) to resolve the memory usage issue --- .github/workflows/pull_request.yml | 15 ++ fuzz/.gitignore | 5 + fuzz/Cargo.toml | 119 +++++++++++ fuzz/README.md | 126 ++++++++++++ fuzz/corpus/rome_parse_all | 1 + fuzz/corpus/rome_parse_d_ts | 1 + fuzz/corpus/rome_parse_json | 1 + fuzz/corpus/rome_parse_jsx | 1 + fuzz/corpus/rome_parse_module | 1 + fuzz/corpus/rome_parse_script | 1 + fuzz/corpus/rome_parse_tsx | 1 + fuzz/corpus/rome_parse_typescript | 1 + fuzz/fuzz_targets/rome_common.rs | 212 ++++++++++++++++++++ fuzz/fuzz_targets/rome_format_all.rs | 35 ++++ fuzz/fuzz_targets/rome_format_d_ts.rs | 15 ++ fuzz/fuzz_targets/rome_format_json.rs | 13 ++ fuzz/fuzz_targets/rome_format_jsx.rs | 15 ++ fuzz/fuzz_targets/rome_format_module.rs | 15 ++ fuzz/fuzz_targets/rome_format_script.rs | 15 ++ fuzz/fuzz_targets/rome_format_tsx.rs | 15 ++ fuzz/fuzz_targets/rome_format_typescript.rs | 15 ++ fuzz/fuzz_targets/rome_parse_all.rs | 35 ++++ fuzz/fuzz_targets/rome_parse_d_ts.rs | 15 ++ fuzz/fuzz_targets/rome_parse_json.rs | 13 ++ fuzz/fuzz_targets/rome_parse_jsx.rs | 15 ++ fuzz/fuzz_targets/rome_parse_module.rs | 15 ++ fuzz/fuzz_targets/rome_parse_script.rs | 15 ++ fuzz/fuzz_targets/rome_parse_tsx.rs | 15 ++ fuzz/fuzz_targets/rome_parse_typescript.rs | 15 ++ fuzz/init-fuzzer.sh | 39 ++++ fuzz/reinit-fuzzer.sh | 22 ++ 31 files changed, 822 insertions(+) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/README.md create mode 120000 fuzz/corpus/rome_parse_all create mode 120000 fuzz/corpus/rome_parse_d_ts create mode 120000 fuzz/corpus/rome_parse_json create mode 120000 fuzz/corpus/rome_parse_jsx create mode 120000 fuzz/corpus/rome_parse_module create mode 120000 fuzz/corpus/rome_parse_script create mode 120000 fuzz/corpus/rome_parse_tsx create mode 120000 fuzz/corpus/rome_parse_typescript create mode 100644 fuzz/fuzz_targets/rome_common.rs create mode 100644 fuzz/fuzz_targets/rome_format_all.rs create mode 100644 fuzz/fuzz_targets/rome_format_d_ts.rs create mode 100644 fuzz/fuzz_targets/rome_format_json.rs create mode 100644 fuzz/fuzz_targets/rome_format_jsx.rs create mode 100644 fuzz/fuzz_targets/rome_format_module.rs create mode 100644 fuzz/fuzz_targets/rome_format_script.rs create mode 100644 fuzz/fuzz_targets/rome_format_tsx.rs create mode 100644 fuzz/fuzz_targets/rome_format_typescript.rs create mode 100644 fuzz/fuzz_targets/rome_parse_all.rs create mode 100644 fuzz/fuzz_targets/rome_parse_d_ts.rs create mode 100644 fuzz/fuzz_targets/rome_parse_json.rs create mode 100644 fuzz/fuzz_targets/rome_parse_jsx.rs create mode 100644 fuzz/fuzz_targets/rome_parse_module.rs create mode 100644 fuzz/fuzz_targets/rome_parse_script.rs create mode 100644 fuzz/fuzz_targets/rome_parse_tsx.rs create mode 100644 fuzz/fuzz_targets/rome_parse_typescript.rs create mode 100644 fuzz/init-fuzzer.sh create mode 100644 fuzz/reinit-fuzzer.sh diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 04a64fbd1cb4..ff9ece5e0c57 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -6,6 +6,7 @@ on: - main paths: # Only run when changes are made to rust code or root Cargo - 'crates/**' + - 'fuzz/**' - 'xtask/**' - 'Cargo.toml' - 'Cargo.lock' @@ -83,6 +84,20 @@ jobs: - name: Run doctests run: cargo test --doc + fuzz-all: + name: Build and init fuzzers + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Install toolchain + uses: moonrepo/setup-rust@v0 + with: + bins: cargo-fuzz + - name: Run init-fuzzer + run: bash fuzz/init-fuzzer.sh + test-node-api: name: Test node.js API runs-on: ubuntu-latest diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 000000000000..83c6aae36aa7 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,5 @@ +artifacts/ +corpus/rome_format_all +corpus/rome_format_json +corpus/rome_format_css +Cargo.lock diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 000000000000..5cd9028c0678 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,119 @@ +[package] +name = "rome_fuzz" +version = "0.0.0" +authors = [ + "Addison Crump ", +] +publish = false +edition = "2021" + +[features] +default = ["libfuzzer"] +full-idempotency = [] +libfuzzer = ["libfuzzer-sys/link_libfuzzer"] +rome_all = [] + +[package.metadata] +cargo-fuzz = true + +[dependencies] +arbitrary = { version = "1.3.0", features = ["derive"] } +libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer", default-features = false } +rome_analyze = { path = "../crates/rome_analyze" } +rome_diagnostics = { path = "../crates/rome_diagnostics" } +rome_formatter = { path = "../crates/rome_formatter" } +rome_js_analyze = { path = "../crates/rome_js_analyze" } +rome_js_formatter = { path = "../crates/rome_js_formatter" } +rome_js_parser = { path = "../crates/rome_js_parser" } +rome_js_syntax = { path = "../crates/rome_js_syntax" } +rome_json_formatter = { path = "../crates/rome_json_formatter" } +rome_json_parser = { path = "../crates/rome_json_parser" } +rome_json_syntax = { path = "../crates/rome_json_syntax" } +rome_service = { path = "../crates/rome_service" } +similar = { version = "2.2.1" } + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "rome_parse_all" +path = "fuzz_targets/rome_parse_all.rs" +required-features = ["rome_all"] + +[[bin]] +name = "rome_parse_d_ts" +path = "fuzz_targets/rome_parse_d_ts.rs" + +[[bin]] +name = "rome_parse_json" +path = "fuzz_targets/rome_parse_json.rs" + +[[bin]] +name = "rome_parse_module" +path = "fuzz_targets/rome_parse_module.rs" + +[[bin]] +name = "rome_parse_script" +path = "fuzz_targets/rome_parse_script.rs" + +[[bin]] +name = "rome_parse_jsx" +path = "fuzz_targets/rome_parse_jsx.rs" + +[[bin]] +name = "rome_parse_tsx" +path = "fuzz_targets/rome_parse_tsx.rs" + +[[bin]] +name = "rome_parse_typescript" +path = "fuzz_targets/rome_parse_typescript.rs" + +[[bin]] +name = "rome_format_all" +path = "fuzz_targets/rome_format_all.rs" +required-features = ["rome_all"] + +[[bin]] +name = "rome_format_d_ts" +path = "fuzz_targets/rome_format_d_ts.rs" + +[[bin]] +name = "rome_format_json" +path = "fuzz_targets/rome_format_json.rs" + +[[bin]] +name = "rome_format_module" +path = "fuzz_targets/rome_format_module.rs" + +[[bin]] +name = "rome_format_script" +path = "fuzz_targets/rome_format_script.rs" + +[[bin]] +name = "rome_format_jsx" +path = "fuzz_targets/rome_format_jsx.rs" + +[[bin]] +name = "rome_format_tsx" +path = "fuzz_targets/rome_format_tsx.rs" + +[[bin]] +name = "rome_format_typescript" +path = "fuzz_targets/rome_format_typescript.rs" + +# enabling debug seems to cause a massive use of RAM (>12GB) +[profile.release] +opt-level = 3 +#debug = true +debug = false + +[profile.dev] +opt-level = 3 +#debug = true +debug = false + +[profile.test] +opt-level = 3 +#debug = true +debug = false diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 000000000000..58efc1bb2111 --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,126 @@ +# rome-fuzz + +Fuzzers and associated utilities for automatic testing of Rome. + +## Usage + +To use the fuzzers provided in this directory, start by invoking: + +```bash +./fuzz/init-fuzzers.sh +``` + +This will install [`cargo-fuzz`](https://github.com/rust-fuzz/cargo-fuzz) and optionally download +datasets which improve the efficacy of the testing. +**This step is necessary for initialising the corpus directory, as all fuzzers share a common +corpus.** +The dataset may take several hours to download and clean, so if you're just looking to try out the +fuzzers, skip the dataset download, though be warned that some features simply cannot be tested +without it (very unlikely for the fuzzer to generate valid python code from "thin air"). + +Once you have initialised the fuzzers, you can then execute any fuzzer with: + +```bash +cargo fuzz run --strip-dead-code -s none name_of_fuzzer -- -timeout=1 +``` + +**Users using Apple M1 devices must use a nightly compiler and omit the `-s none` portion of this +command, as this architecture does not support fuzzing without a sanitizer.** +You can view the names of the available fuzzers with `cargo fuzz list`. +For specific details about how each fuzzer works, please read this document in its entirety. + +**IMPORTANT: You should run `./reinit-fuzzer.sh` after adding more file-based testcases.** This will +allow the testing of new features that you've added unit tests for. + +### Debugging a crash + +Once you've found a crash, you'll need to debug it. +The easiest first step in this process is to minimise the input such that the crash is still +triggered with a smaller input. +`cargo-fuzz` supports this out of the box with: + +```bash +cargo fuzz tmin --strip-dead-code -s none name_of_fuzzer artifacts/name_of_fuzzer/crash-... +``` + +From here, you will need to analyse the input and potentially the behaviour of the program. +The debugging process from here is unfortunately less well-defined, so you will need to apply some +expertise here. +Happy hunting! + +## A brief introduction to fuzzers + +Fuzzing, or fuzz testing, is the process of providing generated data to a program under test. +The most common variety of fuzzers are mutational fuzzers; given a set of existing inputs (a +"corpus"), it will attempt to slightly change (or "mutate") these inputs into new inputs that cover +parts of the code that haven't yet been observed. +Using this strategy, we can quite efficiently generate testcases which cover significant portions of +the program, both with expected and unexpected data. +[This is really quite effective for finding bugs.](https://github.com/rust-fuzz/trophy-case) + +The fuzzers here use [`cargo-fuzz`](https://github.com/rust-fuzz/cargo-fuzz), a utility which allows +Rust to integrate with [libFuzzer](https://llvm.org/docs/LibFuzzer.html), the fuzzer library built +into LLVM. +Each source file present in [`fuzz_targets`](fuzz_targets) is a harness, which is, in effect, a unit +test which can handle different inputs. +When an input is provided to a harness, the harness processes this data and libFuzzer observes the +code coverage and any special values used in comparisons over the course of the run. +Special values are preserved for future mutations and inputs which cover new regions of code are +added to the corpus. + +## Each fuzzer harness in detail + +Each fuzzer harness is designed to test different aspects of Rome. +Since Rome's primary function is parsing, formatting, and linting, we can use fuzzing not only to +detect crashes or panics, but also to detect violations of guarantees of the crate. +This concept is used extensively throughout the fuzzers. + +### `rome_parse_*` + +Each of the `rome_parse_*` fuzz harnesses utilise the [round-trip +property](https://blog.ssanj.net/posts/2016-06-26-property-based-testing-patterns.html) of parsing +and unparsing; that is, given a particular input, if we parse some code successfully, we expect the +unparsed code to have the content as the original code. +If they do not match, then some details of the original input were not captured on the first parse. +The corpus for the JS-like parsers is based on unit tests and [a JS dataset for machine learning +training](https://www.sri.inf.ethz.ch/js150). + +Errata for specific fuzzers can be seen below. + +#### `rome_parse_json` + +Since JSON formats are distinct from JS source code and are a relatively simple format, it is not +strictly necessary to use the shared corpus. +[Fuzzbench](https://google.github.io/fuzzbench/) results consistently show that JSON parsers tend to +max out their coverage with minimal or no corpora. + +At time of writing (June 11, 2023), JSONC does not seem to be supported, so it is not fuzzed. + +#### `rome_parse_all` + +This fuzz harness merely merges all the JS parsers together to create a shared corpus. +It can be used in place of the parsers for d_ts, jsx, module, script, tsx, and typescript in +continuous integration. + +### `rome_format_*` + +These fuzzers use the same corpora as the fuzzers previously mentioned, but check the correctness of +the formatters as well. +We assume the following qualities of formatters: + - Formatters will not introduce syntax errors into the program + - Formatting code twice will have the same result as formatting code once + +In this way, we verify the [idempotency](https://en.wikipedia.org/wiki/Idempotence) and syntax +preservation property of formatting. + +Of particular note: these fuzzers may have false negative results if e.g. two tokens are turned into +one token and the reformatting result is the same. +Unfortunately, we can't necessarily control for this because the formatter may reorganise the +sequence of tokens. + +## Errata + +Unfortunately, `--strip-dead-code` is necessary to build the target with a suitable amount of +memory. +This seems to be caused by some issue in LLVM, but I haven't been able to spend the time to +investigate this fully yet. diff --git a/fuzz/corpus/rome_parse_all b/fuzz/corpus/rome_parse_all new file mode 120000 index 000000000000..27c9131818d5 --- /dev/null +++ b/fuzz/corpus/rome_parse_all @@ -0,0 +1 @@ +rome_format_all \ No newline at end of file diff --git a/fuzz/corpus/rome_parse_d_ts b/fuzz/corpus/rome_parse_d_ts new file mode 120000 index 000000000000..116f3e7db5d6 --- /dev/null +++ b/fuzz/corpus/rome_parse_d_ts @@ -0,0 +1 @@ +rome_parse_all \ No newline at end of file diff --git a/fuzz/corpus/rome_parse_json b/fuzz/corpus/rome_parse_json new file mode 120000 index 000000000000..893f57d8280b --- /dev/null +++ b/fuzz/corpus/rome_parse_json @@ -0,0 +1 @@ +rome_format_json \ No newline at end of file diff --git a/fuzz/corpus/rome_parse_jsx b/fuzz/corpus/rome_parse_jsx new file mode 120000 index 000000000000..116f3e7db5d6 --- /dev/null +++ b/fuzz/corpus/rome_parse_jsx @@ -0,0 +1 @@ +rome_parse_all \ No newline at end of file diff --git a/fuzz/corpus/rome_parse_module b/fuzz/corpus/rome_parse_module new file mode 120000 index 000000000000..116f3e7db5d6 --- /dev/null +++ b/fuzz/corpus/rome_parse_module @@ -0,0 +1 @@ +rome_parse_all \ No newline at end of file diff --git a/fuzz/corpus/rome_parse_script b/fuzz/corpus/rome_parse_script new file mode 120000 index 000000000000..116f3e7db5d6 --- /dev/null +++ b/fuzz/corpus/rome_parse_script @@ -0,0 +1 @@ +rome_parse_all \ No newline at end of file diff --git a/fuzz/corpus/rome_parse_tsx b/fuzz/corpus/rome_parse_tsx new file mode 120000 index 000000000000..116f3e7db5d6 --- /dev/null +++ b/fuzz/corpus/rome_parse_tsx @@ -0,0 +1 @@ +rome_parse_all \ No newline at end of file diff --git a/fuzz/corpus/rome_parse_typescript b/fuzz/corpus/rome_parse_typescript new file mode 120000 index 000000000000..116f3e7db5d6 --- /dev/null +++ b/fuzz/corpus/rome_parse_typescript @@ -0,0 +1 @@ +rome_parse_all \ No newline at end of file diff --git a/fuzz/fuzz_targets/rome_common.rs b/fuzz/fuzz_targets/rome_common.rs new file mode 100644 index 000000000000..158045afc7aa --- /dev/null +++ b/fuzz/fuzz_targets/rome_common.rs @@ -0,0 +1,212 @@ +//! Common functionality between different fuzzers. Look here if you need to inspect implementation +//! details for the fuzzer harnesses! + +#![allow(dead_code)] + +use libfuzzer_sys::Corpus; +use rome_analyze::{AnalysisFilter, AnalyzerOptions, ControlFlow, RuleFilter}; +use rome_diagnostics::Diagnostic; +use rome_formatter::format_node; +use rome_js_analyze::analyze; +use rome_js_formatter::context::JsFormatOptions; +use rome_js_formatter::JsFormatLanguage; +use rome_js_parser::parse; +use rome_js_syntax::JsFileSource; +use rome_json_formatter::context::JsonFormatOptions; +use rome_json_formatter::JsonFormatLanguage; +use rome_json_parser::parse_json; +use rome_service::Rules; +use similar::TextDiff; +use std::fmt::{Display, Formatter}; + +pub fn fuzz_js_parser_with_source_type(data: &[u8], source: JsFileSource) -> Corpus { + let Ok(code1) = std::str::from_utf8(data) else { return Corpus::Reject; }; + + let parse1 = parse(code1, source); + if !parse1.has_errors() { + let syntax1 = parse1.syntax(); + let code2 = syntax1.to_string(); + assert_eq!(code1, code2, "unparse output differed"); + } + + Corpus::Keep +} + +static mut ANALYSIS_RULES: Option = None; +static mut ANALYSIS_RULE_FILTERS: Option> = None; +static mut ANALYSIS_OPTIONS: Option = None; + +struct DiagnosticDescriptionExtractor<'a, D> { + diagnostic: &'a D, +} + +impl<'a, D> DiagnosticDescriptionExtractor<'a, D> { + pub fn new(diagnostic: &'a D) -> Self { + Self { diagnostic } + } +} + +impl<'a, D> Display for DiagnosticDescriptionExtractor<'a, D> +where + D: Diagnostic, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.diagnostic.description(f) + } +} + +pub fn fuzz_js_formatter_with_source_type(data: &[u8], source: JsFileSource) -> Corpus { + let Ok(code1) = std::str::from_utf8(data) else { return Corpus::Reject; }; + + // TODO: replace with OnceLock when upgrading to 1.70 + let rule_filters = if let Some(rules) = unsafe { ANALYSIS_RULE_FILTERS.as_ref() } { + rules + } else { + let rules = unsafe { + ANALYSIS_RULES.get_or_insert_with(|| Rules { + all: Some(true), + ..Default::default() + }) + }; + let rules = rules.as_enabled_rules().into_iter().collect::>(); + unsafe { + ANALYSIS_RULE_FILTERS = Some(rules); + ANALYSIS_RULE_FILTERS.as_ref().unwrap_unchecked() + } + }; + let options = unsafe { ANALYSIS_OPTIONS.get_or_insert_with(AnalyzerOptions::default) }; + + let parse1 = parse(code1, source); + if !parse1.has_errors() { + let language = JsFormatLanguage::new(JsFormatOptions::new(source)); + let tree1 = parse1.tree(); + let mut linter_errors = Vec::new(); + let _ = analyze( + &tree1, + AnalysisFilter::from_enabled_rules(Some(rule_filters)), + options, + source, + |e| -> ControlFlow<()> { + if let Some(diagnostic) = e.diagnostic() { + linter_errors + .push(DiagnosticDescriptionExtractor::new(&diagnostic).to_string()); + } + + ControlFlow::Continue(()) + }, + ); + let syntax1 = parse1.syntax(); + if let Ok(formatted1) = format_node(&syntax1, language.clone()) { + if let Ok(printed1) = formatted1.print() { + let code2 = printed1.as_code(); + let parse2 = parse(code2, source); + assert!( + !parse2.has_errors(), + "formatter introduced errors:\n{}", + TextDiff::from_lines(code1, code2) + .unified_diff() + .header("original code", "formatted") + ); + let tree2 = parse2.tree(); + let (maybe_diagnostic, _) = analyze( + &tree2, + AnalysisFilter::from_enabled_rules(Some(rule_filters)), + options, + source, + |e| { + if let Some(diagnostic) = e.diagnostic() { + let new_error = + DiagnosticDescriptionExtractor::new(&diagnostic).to_string(); + if let Some(idx) = linter_errors.iter().position(|e| *e == new_error) { + linter_errors.remove(idx); + } else { + return ControlFlow::Break(new_error); + } + } + + ControlFlow::Continue(()) + }, + ); + if let Some(diagnostic) = maybe_diagnostic { + panic!( + "formatter introduced linter failure: {} (expected one of: {})\n{}", + diagnostic, + linter_errors.join(", "), + TextDiff::from_lines(code1, code2) + .unified_diff() + .header("original code", "formatted") + ); + } + let syntax2 = parse2.syntax(); + let formatted2 = format_node(&syntax2, language) + .expect("formatted code could not be reformatted"); + let printed2 = formatted2 + .print() + .expect("reformatted code could not be printed"); + let code3 = printed2.as_code(); + assert_eq!( + code2, + code3, + "format results differ:\n{}", + TextDiff::from_lines(code2, code3) + .unified_diff() + .header("formatted", "reformatted") + ) + } + } + } + + Corpus::Keep +} + +pub fn fuzz_json_parser(data: &[u8]) -> Corpus { + let Ok(code1) = std::str::from_utf8(data) else { return Corpus::Reject; }; + + let parse1 = parse_json(code1); + if !parse1.has_errors() { + let syntax1 = parse1.syntax(); + let code2 = syntax1.to_string(); + assert_eq!(code1, code2, "unparse output differed"); + } + + Corpus::Keep +} + +pub fn fuzz_json_formatter(data: &[u8]) -> Corpus { + let Ok(code1) = std::str::from_utf8(data) else { return Corpus::Reject; }; + + let parse1 = parse_json(code1); + if !parse1.has_errors() { + let language = JsonFormatLanguage::new(JsonFormatOptions::default()); + let syntax1 = parse1.syntax(); + if let Ok(formatted1) = format_node(&syntax1, language.clone()) { + if let Ok(printed1) = formatted1.print() { + let code2 = printed1.as_code(); + let parse2 = parse_json(code2); + assert!( + !parse2.has_errors(), + "formatter introduced errors:\n{}", + TextDiff::from_lines(code1, code2) + .unified_diff() + .header("original code", "formatted") + ); + let syntax2 = parse2.syntax(); + let formatted2 = format_node(&syntax2, language) + .expect("formatted code could not be reformatted"); + let printed2 = formatted2 + .print() + .expect("reformatted code could not be printed"); + assert_eq!( + code2, + printed2.as_code(), + "format results differ:\n{}", + TextDiff::from_lines(code1, code2) + .unified_diff() + .header("formatted", "reformatted") + ) + } + } + } + + Corpus::Keep +} diff --git a/fuzz/fuzz_targets/rome_format_all.rs b/fuzz/fuzz_targets/rome_format_all.rs new file mode 100644 index 000000000000..6cb50f82f175 --- /dev/null +++ b/fuzz/fuzz_targets/rome_format_all.rs @@ -0,0 +1,35 @@ +#![no_main] + +mod rome_format_d_ts; +mod rome_format_jsx; +mod rome_format_module; +mod rome_format_script; +mod rome_format_tsx; +mod rome_format_typescript; + +use libfuzzer_sys::{fuzz_target, Corpus}; + +fn do_fuzz(data: &[u8]) -> Corpus { + let mut keep = Corpus::Reject; + if let Corpus::Keep = rome_format_d_ts::do_fuzz(data) { + keep = Corpus::Keep; + } + if let Corpus::Keep = rome_format_jsx::do_fuzz(data) { + keep = Corpus::Keep; + } + if let Corpus::Keep = rome_format_module::do_fuzz(data) { + keep = Corpus::Keep; + } + if let Corpus::Keep = rome_format_script::do_fuzz(data) { + keep = Corpus::Keep; + } + if let Corpus::Keep = rome_format_tsx::do_fuzz(data) { + keep = Corpus::Keep; + } + if let Corpus::Keep = rome_format_typescript::do_fuzz(data) { + keep = Corpus::Keep; + } + keep +} + +fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_format_d_ts.rs b/fuzz/fuzz_targets/rome_format_d_ts.rs new file mode 100644 index 000000000000..721965fe2ffa --- /dev/null +++ b/fuzz/fuzz_targets/rome_format_d_ts.rs @@ -0,0 +1,15 @@ +#![cfg_attr(not(feature = "rome_all"), no_main)] + +#[path = "rome_common.rs"] +mod rome_common; + +use libfuzzer_sys::Corpus; +use rome_js_syntax::JsFileSource; + +pub fn do_fuzz(case: &[u8]) -> Corpus { + let parse_type = JsFileSource::d_ts(); + rome_common::fuzz_js_formatter_with_source_type(case, parse_type) +} + +#[cfg(not(feature = "rome_all"))] +libfuzzer_sys::fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_format_json.rs b/fuzz/fuzz_targets/rome_format_json.rs new file mode 100644 index 000000000000..3810d5949e9e --- /dev/null +++ b/fuzz/fuzz_targets/rome_format_json.rs @@ -0,0 +1,13 @@ +#![cfg_attr(not(feature = "rome_all"), no_main)] + +#[path = "rome_common.rs"] +mod rome_common; + +use libfuzzer_sys::Corpus; + +pub fn do_fuzz(case: &[u8]) -> Corpus { + rome_common::fuzz_json_formatter(case) +} + +#[cfg(not(feature = "rome_all"))] +libfuzzer_sys::fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_format_jsx.rs b/fuzz/fuzz_targets/rome_format_jsx.rs new file mode 100644 index 000000000000..e1547aa49c64 --- /dev/null +++ b/fuzz/fuzz_targets/rome_format_jsx.rs @@ -0,0 +1,15 @@ +#![cfg_attr(not(feature = "rome_all"), no_main)] + +#[path = "rome_common.rs"] +mod rome_common; + +use libfuzzer_sys::Corpus; +use rome_js_syntax::JsFileSource; + +pub fn do_fuzz(case: &[u8]) -> Corpus { + let parse_type = JsFileSource::jsx(); + rome_common::fuzz_js_formatter_with_source_type(case, parse_type) +} + +#[cfg(not(feature = "rome_all"))] +libfuzzer_sys::fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_format_module.rs b/fuzz/fuzz_targets/rome_format_module.rs new file mode 100644 index 000000000000..13a253bfea74 --- /dev/null +++ b/fuzz/fuzz_targets/rome_format_module.rs @@ -0,0 +1,15 @@ +#![cfg_attr(not(feature = "rome_all"), no_main)] + +#[path = "rome_common.rs"] +mod rome_common; + +use libfuzzer_sys::Corpus; +use rome_js_syntax::JsFileSource; + +pub fn do_fuzz(case: &[u8]) -> Corpus { + let parse_type = JsFileSource::js_module(); + rome_common::fuzz_js_formatter_with_source_type(case, parse_type) +} + +#[cfg(not(feature = "rome_all"))] +libfuzzer_sys::fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_format_script.rs b/fuzz/fuzz_targets/rome_format_script.rs new file mode 100644 index 000000000000..738fb5c81e72 --- /dev/null +++ b/fuzz/fuzz_targets/rome_format_script.rs @@ -0,0 +1,15 @@ +#![cfg_attr(not(feature = "rome_all"), no_main)] + +#[path = "rome_common.rs"] +mod rome_common; + +use libfuzzer_sys::Corpus; +use rome_js_syntax::JsFileSource; + +pub fn do_fuzz(case: &[u8]) -> Corpus { + let parse_type = JsFileSource::js_script(); + rome_common::fuzz_js_formatter_with_source_type(case, parse_type) +} + +#[cfg(not(feature = "rome_all"))] +libfuzzer_sys::fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_format_tsx.rs b/fuzz/fuzz_targets/rome_format_tsx.rs new file mode 100644 index 000000000000..e5ca74dd8a3c --- /dev/null +++ b/fuzz/fuzz_targets/rome_format_tsx.rs @@ -0,0 +1,15 @@ +#![cfg_attr(not(feature = "rome_all"), no_main)] + +#[path = "rome_common.rs"] +mod rome_common; + +use libfuzzer_sys::Corpus; +use rome_js_syntax::JsFileSource; + +pub fn do_fuzz(case: &[u8]) -> Corpus { + let parse_type = JsFileSource::tsx(); + rome_common::fuzz_js_formatter_with_source_type(case, parse_type) +} + +#[cfg(not(feature = "rome_all"))] +libfuzzer_sys::fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_format_typescript.rs b/fuzz/fuzz_targets/rome_format_typescript.rs new file mode 100644 index 000000000000..c3c352b404a9 --- /dev/null +++ b/fuzz/fuzz_targets/rome_format_typescript.rs @@ -0,0 +1,15 @@ +#![cfg_attr(not(feature = "rome_all"), no_main)] + +#[path = "rome_common.rs"] +mod rome_common; + +use libfuzzer_sys::Corpus; +use rome_js_syntax::JsFileSource; + +pub fn do_fuzz(case: &[u8]) -> Corpus { + let parse_type = JsFileSource::ts(); + rome_common::fuzz_js_formatter_with_source_type(case, parse_type) +} + +#[cfg(not(feature = "rome_all"))] +libfuzzer_sys::fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_parse_all.rs b/fuzz/fuzz_targets/rome_parse_all.rs new file mode 100644 index 000000000000..51293d253e84 --- /dev/null +++ b/fuzz/fuzz_targets/rome_parse_all.rs @@ -0,0 +1,35 @@ +#![no_main] + +mod rome_parse_d_ts; +mod rome_parse_jsx; +mod rome_parse_module; +mod rome_parse_script; +mod rome_parse_tsx; +mod rome_parse_typescript; + +use libfuzzer_sys::{fuzz_target, Corpus}; + +fn do_fuzz(data: &[u8]) -> Corpus { + let mut keep = Corpus::Reject; + if let Corpus::Keep = rome_parse_d_ts::do_fuzz(data) { + keep = Corpus::Keep; + } + if let Corpus::Keep = rome_parse_jsx::do_fuzz(data) { + keep = Corpus::Keep; + } + if let Corpus::Keep = rome_parse_module::do_fuzz(data) { + keep = Corpus::Keep; + } + if let Corpus::Keep = rome_parse_script::do_fuzz(data) { + keep = Corpus::Keep; + } + if let Corpus::Keep = rome_parse_tsx::do_fuzz(data) { + keep = Corpus::Keep; + } + if let Corpus::Keep = rome_parse_typescript::do_fuzz(data) { + keep = Corpus::Keep; + } + keep +} + +fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_parse_d_ts.rs b/fuzz/fuzz_targets/rome_parse_d_ts.rs new file mode 100644 index 000000000000..fc9f1e4704c4 --- /dev/null +++ b/fuzz/fuzz_targets/rome_parse_d_ts.rs @@ -0,0 +1,15 @@ +#![cfg_attr(not(feature = "rome_all"), no_main)] + +#[path = "rome_common.rs"] +mod rome_common; + +use libfuzzer_sys::Corpus; +use rome_js_syntax::JsFileSource; + +pub fn do_fuzz(case: &[u8]) -> Corpus { + let parse_type = JsFileSource::d_ts(); + rome_common::fuzz_js_parser_with_source_type(case, parse_type) +} + +#[cfg(not(feature = "rome_all"))] +libfuzzer_sys::fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_parse_json.rs b/fuzz/fuzz_targets/rome_parse_json.rs new file mode 100644 index 000000000000..de6ce143fe69 --- /dev/null +++ b/fuzz/fuzz_targets/rome_parse_json.rs @@ -0,0 +1,13 @@ +#![cfg_attr(not(feature = "rome_all"), no_main)] + +#[path = "rome_common.rs"] +mod rome_common; + +use libfuzzer_sys::Corpus; + +pub fn do_fuzz(case: &[u8]) -> Corpus { + rome_common::fuzz_json_parser(case) +} + +#[cfg(not(feature = "rome_all"))] +libfuzzer_sys::fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_parse_jsx.rs b/fuzz/fuzz_targets/rome_parse_jsx.rs new file mode 100644 index 000000000000..4413536869f2 --- /dev/null +++ b/fuzz/fuzz_targets/rome_parse_jsx.rs @@ -0,0 +1,15 @@ +#![cfg_attr(not(feature = "rome_all"), no_main)] + +#[path = "rome_common.rs"] +mod rome_common; + +use libfuzzer_sys::Corpus; +use rome_js_syntax::JsFileSource; + +pub fn do_fuzz(case: &[u8]) -> Corpus { + let parse_type = JsFileSource::jsx(); + rome_common::fuzz_js_parser_with_source_type(case, parse_type) +} + +#[cfg(not(feature = "rome_all"))] +libfuzzer_sys::fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_parse_module.rs b/fuzz/fuzz_targets/rome_parse_module.rs new file mode 100644 index 000000000000..e935489b2e65 --- /dev/null +++ b/fuzz/fuzz_targets/rome_parse_module.rs @@ -0,0 +1,15 @@ +#![cfg_attr(not(feature = "rome_all"), no_main)] + +#[path = "rome_common.rs"] +mod rome_common; + +use libfuzzer_sys::Corpus; +use rome_js_syntax::JsFileSource; + +pub fn do_fuzz(case: &[u8]) -> Corpus { + let parse_type = JsFileSource::js_module(); + rome_common::fuzz_js_parser_with_source_type(case, parse_type) +} + +#[cfg(not(feature = "rome_all"))] +libfuzzer_sys::fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_parse_script.rs b/fuzz/fuzz_targets/rome_parse_script.rs new file mode 100644 index 000000000000..36fed6dd8d1d --- /dev/null +++ b/fuzz/fuzz_targets/rome_parse_script.rs @@ -0,0 +1,15 @@ +#![cfg_attr(not(feature = "rome_all"), no_main)] + +#[path = "rome_common.rs"] +mod rome_common; + +use libfuzzer_sys::Corpus; +use rome_js_syntax::JsFileSource; + +pub fn do_fuzz(case: &[u8]) -> Corpus { + let parse_type = JsFileSource::js_script(); + rome_common::fuzz_js_parser_with_source_type(case, parse_type) +} + +#[cfg(not(feature = "rome_all"))] +libfuzzer_sys::fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_parse_tsx.rs b/fuzz/fuzz_targets/rome_parse_tsx.rs new file mode 100644 index 000000000000..3dc732c14a01 --- /dev/null +++ b/fuzz/fuzz_targets/rome_parse_tsx.rs @@ -0,0 +1,15 @@ +#![cfg_attr(not(feature = "rome_all"), no_main)] + +#[path = "rome_common.rs"] +mod rome_common; + +use libfuzzer_sys::Corpus; +use rome_js_syntax::JsFileSource; + +pub fn do_fuzz(case: &[u8]) -> Corpus { + let parse_type = JsFileSource::tsx(); + rome_common::fuzz_js_parser_with_source_type(case, parse_type) +} + +#[cfg(not(feature = "rome_all"))] +libfuzzer_sys::fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/fuzz_targets/rome_parse_typescript.rs b/fuzz/fuzz_targets/rome_parse_typescript.rs new file mode 100644 index 000000000000..5a4d68c33407 --- /dev/null +++ b/fuzz/fuzz_targets/rome_parse_typescript.rs @@ -0,0 +1,15 @@ +#![cfg_attr(not(feature = "rome_all"), no_main)] + +#[path = "rome_common.rs"] +mod rome_common; + +use libfuzzer_sys::Corpus; +use rome_js_syntax::JsFileSource; + +pub fn do_fuzz(case: &[u8]) -> Corpus { + let parse_type = JsFileSource::ts(); + rome_common::fuzz_js_parser_with_source_type(case, parse_type) +} + +#[cfg(not(feature = "rome_all"))] +libfuzzer_sys::fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) }); diff --git a/fuzz/init-fuzzer.sh b/fuzz/init-fuzzer.sh new file mode 100644 index 000000000000..e1e208c27772 --- /dev/null +++ b/fuzz/init-fuzzer.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# https://stackoverflow.com/a/246128/3549270 +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +cd "$SCRIPT_DIR" + +if ! cargo fuzz --help >&/dev/null; then + cargo install --git https://github.com/rust-fuzz/cargo +stable-fuzz.git +fi + +if [ ! -d corpus/rome_format_all ]; then + mkdir -p corpus/rome_format_all + cd corpus/rome_format_all + if [ -z ${CI+x} ]; then + read -p "Would you like to build a corpus from a javascript source code dataset? (this will take a long time!) [Y/n] " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + curl -L http://files.srl.inf.ethz.ch/data/js_dataset.tar.gz | tar xzO data.tar.gz | tar xz + find . -type d -exec chmod 755 {} \; + find . -type f -exec chmod 644 {} \; + fi + fi + cp -r "../../../crates/rome_js_parser/test_data" . + find . -name \*.rast -delete + cd - + cargo fuzz cmin --strip-dead-code --features rome_all -s none rome_format_all +fi + +if [ ! -d corpus/rome_format_json ]; then + mkdir -p corpus/rome_format_json + cd corpus/rome_format_json + cp -r "../../../crates/rome_json_parser/tests/json_test_suite" . + find . -name \*.rast -delete + cd - + cargo fuzz cmin --strip-dead-code -s none rome_format_json +fi + +echo "Done! You are ready to fuzz." diff --git a/fuzz/reinit-fuzzer.sh b/fuzz/reinit-fuzzer.sh new file mode 100644 index 000000000000..378ea0f0026e --- /dev/null +++ b/fuzz/reinit-fuzzer.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# https://stackoverflow.com/a/246128/3549270 +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +cd "$SCRIPT_DIR" + +mkdir -p corpus/rome_format_all +cd corpus/rome_format_all +cp -r "../../../crates/rome_js_parser/test_data" . +find . -name \*.rast -delete +cd - +cargo fuzz cmin --strip-dead-code --features rome_all -s none rome_format_all + +mkdir -p corpus/rome_format_json +cd corpus/rome_format_json +cp -r "../../../crates/rome_json_parser/tests/json_test_suite" . +find . -name \*.rast -delete +cd - +cargo fuzz cmin --strip-dead-code -s none rome_format_json + +echo "Done! You are ready to fuzz."