From 9aadb18186c0804f6cc6179bf8ea993337dc669d Mon Sep 17 00:00:00 2001 From: Nathaniel Brough Date: Tue, 5 Dec 2023 10:26:58 -0800 Subject: [PATCH] Add simple fuzz testing (#2581) --- .github/workflows/ci.yml | 12 +++++++ .gitignore | 4 +++ Cargo.lock | 38 ++++++++++++++++++++ Cargo.toml | 3 +- tests/fuzz/Cargo.toml | 27 ++++++++++++++ tests/fuzz/src/compile.rs | 74 +++++++++++++++++++++++++++++++++++++++ tests/fuzz/src/parse.rs | 8 +++++ 7 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 tests/fuzz/Cargo.toml create mode 100644 tests/fuzz/src/compile.rs create mode 100644 tests/fuzz/src/parse.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 366ceab75..c691869eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,3 +35,15 @@ jobs: - uses: dtolnay/rust-toolchain@1.70.0 - uses: Swatinem/rust-cache@v2 - run: cargo check --workspace + + fuzz: + name: Check fuzzers + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2023-09-13 + - uses: Swatinem/rust-cache@v2 + - run: cargo install cargo-fuzz + - run: cd tests/fuzz && cargo fuzz build --dev diff --git a/.gitignore b/.gitignore index 9a368e5e0..e3ba6a570 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ tests/png tests/pdf tests/svg tests/target +tests/fuzz/target +tests/fuzz/corpus +tests/fuzz/artifacts +tests/fuzz/coverage tarpaulin-report.html # Rust diff --git a/Cargo.lock b/Cargo.lock index b5a869b62..a9a3fde86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,6 +102,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "arrayref" version = "0.3.7" @@ -220,6 +226,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -1178,6 +1185,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "jpeg-decoder" version = "0.3.0" @@ -1252,6 +1268,17 @@ dependencies = [ "libdeflate-sys", ] +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "libm" version = "0.2.8" @@ -2730,6 +2757,17 @@ dependencies = [ "yaml-front-matter", ] +[[package]] +name = "typst-fuzz" +version = "0.10.0" +dependencies = [ + "comemo", + "libfuzzer-sys", + "typst", + "typst-render", + "typst-syntax", +] + [[package]] name = "typst-ide" version = "0.10.0" diff --git a/Cargo.toml b/Cargo.toml index b8359f7de..1ddd504fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["crates/*", "tests"] +members = ["crates/*", "tests", "tests/fuzz"] default-members = ["crates/typst-cli"] resolver = "2" @@ -58,6 +58,7 @@ include_dir = "0.7" indexmap = { version = "2", features = ["serde"] } inferno = "0.11.15" kurbo = "0.9" +libfuzzer-sys = "0.4" lipsum = "0.9" log = "0.4" miniz_oxide = "0.7" diff --git a/tests/fuzz/Cargo.toml b/tests/fuzz/Cargo.toml new file mode 100644 index 000000000..1b9e6b26b --- /dev/null +++ b/tests/fuzz/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "typst-fuzz" +version.workspace = true +edition.workspace = true +publish = false + +[package.metadata] +cargo-fuzz = true + +[dependencies] +typst = { workspace = true } +typst-render = { workspace = true } +typst-syntax = { workspace = true } +comemo = { workspace = true } +libfuzzer-sys = { workspace = true } + +[[bin]] +name = "parse" +path = "src/parse.rs" +test = false +doc = false + +[[bin]] +name = "compile" +path = "src/compile.rs" +test = false +doc = false diff --git a/tests/fuzz/src/compile.rs b/tests/fuzz/src/compile.rs new file mode 100644 index 000000000..deb717789 --- /dev/null +++ b/tests/fuzz/src/compile.rs @@ -0,0 +1,74 @@ +#![no_main] + +use comemo::Prehashed; +use libfuzzer_sys::fuzz_target; +use typst::diag::{FileError, FileResult}; +use typst::eval::Tracer; +use typst::foundations::{Bytes, Datetime}; +use typst::syntax::{FileId, Source}; +use typst::text::{Font, FontBook}; +use typst::visualize::Color; +use typst::{Library, World}; + +const FONT: &[u8] = include_bytes!("../../../assets/fonts/LinLibertine_R.ttf"); + +struct FuzzWorld { + library: Prehashed, + book: Prehashed, + font: Font, + source: Source, +} + +impl FuzzWorld { + fn new(text: &str) -> Self { + let font = Font::new(FONT.into(), 0).unwrap(); + let book = FontBook::from_fonts([&font]); + Self { + library: Prehashed::new(Library::build()), + book: Prehashed::new(book), + font, + source: Source::detached(text), + } + } +} + +impl World for FuzzWorld { + fn library(&self) -> &Prehashed { + &self.library + } + + fn book(&self) -> &Prehashed { + &self.book + } + + fn main(&self) -> Source { + self.source.clone() + } + + fn source(&self, src: FileId) -> FileResult { + Err(FileError::NotFound(src.vpath().as_rootless_path().into())) + } + + fn file(&self, src: FileId) -> FileResult { + Err(FileError::NotFound(src.vpath().as_rootless_path().into())) + } + + fn font(&self, _: usize) -> Option { + Some(self.font.clone()) + } + + fn today(&self, _: Option) -> Option { + None + } +} + +fuzz_target!(|text: &str| { + let world = FuzzWorld::new(text); + let mut tracer = Tracer::new(); + if let Ok(document) = typst::compile(&world, &mut tracer) { + if let Some(page) = document.pages.first() { + std::hint::black_box(typst_render::render(page, 1.0, Color::WHITE)); + } + } + comemo::evict(10); +}); diff --git a/tests/fuzz/src/parse.rs b/tests/fuzz/src/parse.rs new file mode 100644 index 000000000..f151661ab --- /dev/null +++ b/tests/fuzz/src/parse.rs @@ -0,0 +1,8 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use typst_syntax::parse; + +fuzz_target!(|text: &str| { + std::hint::black_box(parse(text)); +});