fuzz: fuzz text filters

This commit is contained in:
René Kijewski 2024-08-18 05:37:35 +02:00
parent 1176c654ba
commit 33e80a2be4
9 changed files with 125 additions and 16 deletions

View File

@ -13,7 +13,7 @@ Then execute in this folder:
RUST_BACKTRACE=1 nice cargo +nightly fuzz run <fuzz_target>
```
`fuzz_target` is one out of `all`, `html` or `parser`.
`fuzz_target` is one out of `all`, `filters`, `html` or `parser`.
The execution won't stop, but continue until you kill it with ctrl+c.
Or until it finds a panic.

View File

@ -9,11 +9,13 @@ publish = false
cargo-fuzz = true
[dependencies]
rinja = { path = "../../rinja" }
rinja_parser = { path = "../../rinja_parser" }
arbitrary = { version = "1.3.2", features = ["derive"] }
html-escape = "0.2.13"
libfuzzer-sys = "0.4.7"
thiserror = "1.0.63"
[[bin]]
name = "all"
@ -21,6 +23,12 @@ path = "fuzz_targets/all.rs"
test = false
doc = false
[[bin]]
name = "filters"
path = "fuzz_targets/filters.rs"
test = false
doc = false
[[bin]]
name = "html"
path = "fuzz_targets/html.rs"

View File

@ -1,9 +1,5 @@
#![no_main]
use fuzz::Scenario;
libfuzzer_sys::fuzz_target!(|data: &[u8]| {
if let Ok(scenario) = fuzz::all::Scenario::new(data) {
let _ = scenario.run();
}
let _ = <fuzz::all::Scenario as fuzz::Scenario>::fuzz(data);
});

View File

@ -0,0 +1,5 @@
#![no_main]
libfuzzer_sys::fuzz_target!(|data: &[u8]| {
let _ = <fuzz::filters::Scenario as fuzz::Scenario>::fuzz(data);
});

View File

@ -1,9 +1,5 @@
#![no_main]
use fuzz::Scenario;
libfuzzer_sys::fuzz_target!(|data: &[u8]| {
if let Ok(scenario) = fuzz::html::Scenario::new(data) {
let _ = scenario.run();
}
let _ = <fuzz::html::Scenario as fuzz::Scenario>::fuzz(data);
});

View File

@ -1,9 +1,5 @@
#![no_main]
use fuzz::Scenario;
libfuzzer_sys::fuzz_target!(|data: &[u8]| {
if let Ok(scenario) = fuzz::parser::Scenario::new(data) {
let _ = scenario.run();
}
let _ = <fuzz::parser::Scenario as fuzz::Scenario>::fuzz(data);
});

View File

@ -65,6 +65,7 @@ macro_rules! this_file {
}
this_file! {
crate::filters::Filters;
crate::html::Html;
crate::parser::Parser;
}

View File

@ -0,0 +1,88 @@
use arbitrary::{Arbitrary, Unstructured};
use rinja::filters;
#[derive(Arbitrary, Debug, Clone, Copy)]
pub enum Scenario<'a> {
Text(Text<'a>),
}
impl<'a> super::Scenario<'a> for Scenario<'a> {
type RunError = rinja::Error;
fn new(data: &'a [u8]) -> Result<Self, arbitrary::Error> {
Self::arbitrary_take_rest(Unstructured::new(data))
}
fn run(&self) -> Result<(), Self::RunError> {
match *self {
Self::Text(text) => run_text(text),
}
}
}
fn run_text(filter: Text<'_>) -> Result<(), rinja::Error> {
let Text { input, filter } = filter;
let _ = match filter {
TextFilter::Capitalize => filters::capitalize(input)?.to_string(),
TextFilter::Center(a) => filters::center(input, a)?.to_string(),
TextFilter::Indent(a) => filters::indent(input, a)?.to_string(),
TextFilter::Linebreaks => filters::linebreaks(input)?.to_string(),
TextFilter::LinebreaksBr => filters::linebreaksbr(input)?.to_string(),
TextFilter::Lowercase => filters::lowercase(input)?.to_string(),
TextFilter::ParagraphBreaks => filters::paragraphbreaks(input)?.to_string(),
TextFilter::Safe(e) => match e {
Escaper::Html => filters::safe(input, filters::Html)?.to_string(),
Escaper::Text => filters::safe(input, filters::Text)?.to_string(),
},
TextFilter::Title => filters::title(input)?.to_string(),
TextFilter::Trim => filters::trim(input)?.to_string(),
TextFilter::Truncate(a) => filters::truncate(input, a)?.to_string(),
TextFilter::Uppercase => filters::uppercase(input)?.to_string(),
TextFilter::Urlencode => filters::urlencode(input)?.to_string(),
TextFilter::UrlencodeStrict => filters::urlencode_strict(input)?.to_string(),
};
Ok(())
}
#[derive(Arbitrary, Debug, Clone, Copy)]
pub struct Text<'a> {
input: &'a str,
filter: TextFilter,
}
#[derive(Arbitrary, Debug, Clone, Copy)]
enum TextFilter {
Capitalize,
Center(usize),
Indent(usize),
Linebreaks,
LinebreaksBr,
Lowercase,
ParagraphBreaks,
Safe(Escaper),
Title,
Trim,
Truncate(usize),
Uppercase,
Urlencode,
UrlencodeStrict,
}
#[derive(Arbitrary, Debug, Clone, Copy)]
enum Escaper {
Html,
Text,
}
// TODO:
// abs
// escape,
// filesizeformat
// fmt
// format
// into_f64
// into_isize
// join
// json
// json_pretty
// wordcount

View File

@ -1,4 +1,5 @@
pub mod all;
pub mod filters;
pub mod html;
pub mod parser;
@ -7,6 +8,9 @@ use std::fmt;
pub const TARGETS: &[(&str, TargetBuilder)] = &[
("all", |data| NamedTarget::new::<all::Scenario>(data)),
("filters", |data| {
NamedTarget::new::<filters::Scenario>(data)
}),
("html", |data| NamedTarget::new::<html::Scenario>(data)),
("parser", |data| NamedTarget::new::<parser::Scenario>(data)),
];
@ -16,10 +20,25 @@ pub type TargetBuilder = for<'a> fn(&'a [u8]) -> Result<NamedTarget<'a>, arbitra
pub trait Scenario<'a>: fmt::Debug + Sized {
type RunError: Error + Send + 'static;
fn fuzz(data: &'a [u8]) -> Result<(), FuzzError<Self::RunError>> {
Self::new(data)
.map_err(FuzzError::New)?
.run()
.map_err(FuzzError::Run)
}
fn new(data: &'a [u8]) -> Result<Self, arbitrary::Error>;
fn run(&self) -> Result<(), Self::RunError>;
}
#[derive(Debug, thiserror::Error)]
pub enum FuzzError<RunError: Error + Send + 'static> {
#[error("could not build scenario")]
New(#[source] arbitrary::Error),
#[error("could not run scenario")]
Run(#[source] RunError),
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DisplayTargets;