commit
e458955d00
11 changed files with 270 additions and 49 deletions
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
|
@ -77,10 +77,13 @@ jobs:
|
||||||
rustup update nightly
|
rustup update nightly
|
||||||
rustup default nightly
|
rustup default nightly
|
||||||
cargo install afl
|
cargo install afl
|
||||||
- name: "Fuzz"
|
- name: "Fuzz parser"
|
||||||
run: |
|
run: |
|
||||||
echo core | sudo tee /proc/sys/kernel/core_pattern
|
echo core | sudo tee /proc/sys/kernel/core_pattern
|
||||||
make afl_quick
|
AFL_TARGET=parse make afl_quick
|
||||||
|
- name: "Fuzz html"
|
||||||
|
run: |
|
||||||
|
AFL_TARGET=html make afl_quick
|
||||||
bench:
|
bench:
|
||||||
name: Benchmark
|
name: Benchmark
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
21
Makefile
21
Makefile
|
@ -53,14 +53,14 @@ bench:
|
||||||
cov: suite suite_bench
|
cov: suite suite_bench
|
||||||
LLVM_COV=llvm-cov LLVM_PROFDATA=llvm-profdata cargo llvm-cov --features=suite,suite_bench --workspace --html --ignore-run-fail
|
LLVM_COV=llvm-cov LLVM_PROFDATA=llvm-profdata cargo llvm-cov --features=suite,suite_bench --workspace --html --ignore-run-fail
|
||||||
|
|
||||||
AFL_TARGET?=gen
|
AFL_TARGET?=parse
|
||||||
AFL_JOBS?=1
|
AFL_JOBS?=1
|
||||||
AFL_TARGET_CRASH?=crashes
|
AFL_TARGET_CRASH?=crashes
|
||||||
|
|
||||||
afl:
|
afl:
|
||||||
rm -rf tests/afl/out
|
rm -rf tests/afl/out
|
||||||
(cd tests/afl && \
|
(cd tests/afl && \
|
||||||
cargo afl build --release --config profile.release.debug-assertions=true && \
|
cargo afl build --no-default-features --release --config profile.release.debug-assertions=true && \
|
||||||
(AFL_NO_UI=1 cargo afl fuzz -i in -o out -Mm target/release/${AFL_TARGET} &) && \
|
(AFL_NO_UI=1 cargo afl fuzz -i in -o out -Mm target/release/${AFL_TARGET} &) && \
|
||||||
for i in $$(seq $$((${AFL_JOBS} - 1))); do \
|
for i in $$(seq $$((${AFL_JOBS} - 1))); do \
|
||||||
AFL_NO_UI=1 cargo afl fuzz -i in -o out -Ss$$i target/release/${AFL_TARGET} & \
|
AFL_NO_UI=1 cargo afl fuzz -i in -o out -Ss$$i target/release/${AFL_TARGET} & \
|
||||||
|
@ -71,24 +71,31 @@ afl:
|
||||||
afl_quick:
|
afl_quick:
|
||||||
rm -rf tests/afl/out
|
rm -rf tests/afl/out
|
||||||
(cd tests/afl && \
|
(cd tests/afl && \
|
||||||
cargo afl build --release --config profile.release.debug-assertions=true && \
|
cargo afl build --no-default-features --release --config profile.release.debug-assertions=true && \
|
||||||
AFL_NO_UI=1 AFL_BENCH_UNTIL_CRASH=1 \
|
AFL_NO_UI=1 AFL_BENCH_UNTIL_CRASH=1 \
|
||||||
cargo afl fuzz -i in -o out -V 60 target/release/${AFL_TARGET})
|
cargo afl fuzz -i in -o out -V 60 target/release/${AFL_TARGET})
|
||||||
|
|
||||||
afl_crash:
|
afl_crash:
|
||||||
set +e; \
|
set +e; \
|
||||||
for f in $$(find tests/afl/out -path '*/${AFL_TARGET_CRASH}/id*'); do \
|
failures="$$(find . -path './tmin/*') $$(find tests/afl/out -path '*/${AFL_TARGET_CRASH}/id*')"; \
|
||||||
echo "cat $$f | RUST_BACKTRACE=1 cargo run"; \
|
for f in $$failures; do \
|
||||||
out=$$(cat $$f | RUST_BACKTRACE=1 cargo run 2>&1); \
|
echo $$f; \
|
||||||
|
out=$$(cat $$f | (cd tests/afl && RUST_BACKTRACE=1 cargo run ${AFL_TARGET} 2>&1)); \
|
||||||
if [ $$? -ne 0 ]; then \
|
if [ $$? -ne 0 ]; then \
|
||||||
echo; \
|
echo; \
|
||||||
echo "FAIL"; \
|
echo "FAIL"; \
|
||||||
echo "$$out"; \
|
echo "$$out"; \
|
||||||
echo "cat $$f | RUST_BACKTRACE=1 cargo run"; \
|
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi; \
|
fi; \
|
||||||
done
|
done
|
||||||
|
|
||||||
|
afl_tmin:
|
||||||
|
rm -rf tmin
|
||||||
|
mkdir tmin
|
||||||
|
for f in $$(find tests/afl/out -path '*/${AFL_TARGET_CRASH}/id*'); do \
|
||||||
|
cargo afl tmin -i $$f -o tmin/$$(basename $$f) tests/afl/target/release/${AFL_TARGET}; \
|
||||||
|
done
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
cargo clean
|
cargo clean
|
||||||
git submodule deinit -f --all
|
git submodule deinit -f --all
|
||||||
|
|
|
@ -262,7 +262,7 @@ impl<I: Iterator<Item = char>> Parser<I> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s @ (ClassFirst | IdentifierFirst) => {
|
s @ (ClassFirst | IdentifierFirst) => {
|
||||||
if is_name_start(c) {
|
if is_name(c) {
|
||||||
match s {
|
match s {
|
||||||
ClassFirst => Class,
|
ClassFirst => Class,
|
||||||
IdentifierFirst => Identifier,
|
IdentifierFirst => Identifier,
|
||||||
|
@ -344,12 +344,8 @@ impl<I: Iterator<Item = char>> Parser<I> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_name_start(c: char) -> bool {
|
|
||||||
c.is_ascii_alphanumeric() || matches!(c, '_' | ':')
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_name(c: char) -> bool {
|
pub fn is_name(c: char) -> bool {
|
||||||
is_name_start(c) || c.is_ascii_digit() || matches!(c, '-')
|
c.is_ascii_alphanumeric() || matches!(c, ':' | '_' | '-')
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Element {
|
enum Element {
|
||||||
|
|
|
@ -734,9 +734,8 @@ impl IdentifiedBlock {
|
||||||
f @ ('`' | ':' | '~') => {
|
f @ ('`' | ':' | '~') => {
|
||||||
let fence_length = 1 + (&mut chars).take_while(|c| *c == f).count();
|
let fence_length = 1 + (&mut chars).take_while(|c| *c == f).count();
|
||||||
let spec = &line_t[fence_length..].trim_start();
|
let spec = &line_t[fence_length..].trim_start();
|
||||||
let valid_spec = if f == ':' && !spec.starts_with('=') {
|
let valid_spec = if f == ':' {
|
||||||
spec.chars().next().map_or(true, attr::is_name_start)
|
spec.chars().all(attr::is_name)
|
||||||
&& spec.chars().skip(1).all(attr::is_name)
|
|
||||||
} else {
|
} else {
|
||||||
!spec.chars().any(char::is_whitespace) && !spec.chars().any(|c| c == '`')
|
!spec.chars().any(char::is_whitespace) && !spec.chars().any(|c| c == '`')
|
||||||
};
|
};
|
||||||
|
|
43
src/html.rs
43
src/html.rs
|
@ -67,7 +67,7 @@ struct Writer<'s, I: Iterator<Item = Event<'s>>, W> {
|
||||||
events: std::iter::Peekable<FilteredEvents<I>>,
|
events: std::iter::Peekable<FilteredEvents<I>>,
|
||||||
out: W,
|
out: W,
|
||||||
raw: Raw,
|
raw: Raw,
|
||||||
text_only: bool,
|
img_alt_text: usize,
|
||||||
list_tightness: Vec<bool>,
|
list_tightness: Vec<bool>,
|
||||||
encountered_footnote: bool,
|
encountered_footnote: bool,
|
||||||
footnote_number: Option<std::num::NonZeroUsize>,
|
footnote_number: Option<std::num::NonZeroUsize>,
|
||||||
|
@ -81,7 +81,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
|
||||||
events: FilteredEvents { events }.peekable(),
|
events: FilteredEvents { events }.peekable(),
|
||||||
out,
|
out,
|
||||||
raw: Raw::None,
|
raw: Raw::None,
|
||||||
text_only: false,
|
img_alt_text: 0,
|
||||||
list_tightness: Vec::new(),
|
list_tightness: Vec::new(),
|
||||||
encountered_footnote: false,
|
encountered_footnote: false,
|
||||||
footnote_number: None,
|
footnote_number: None,
|
||||||
|
@ -97,7 +97,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
|
||||||
if c.is_block() && !self.first_line {
|
if c.is_block() && !self.first_line {
|
||||||
self.out.write_char('\n')?;
|
self.out.write_char('\n')?;
|
||||||
}
|
}
|
||||||
if self.text_only && !matches!(c, Container::Image(..)) {
|
if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match &c {
|
match &c {
|
||||||
|
@ -171,8 +171,12 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Container::Image(..) => {
|
Container::Image(..) => {
|
||||||
self.text_only = true;
|
self.img_alt_text += 1;
|
||||||
self.out.write_str("<img")?;
|
if self.img_alt_text == 1 {
|
||||||
|
self.out.write_str("<img")?;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Container::Verbatim => self.out.write_str("<code")?,
|
Container::Verbatim => self.out.write_str("<code")?,
|
||||||
Container::RawBlock { format } | Container::RawInline { format } => {
|
Container::RawBlock { format } | Container::RawInline { format } => {
|
||||||
|
@ -283,7 +287,9 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Container::Image(..) => {
|
Container::Image(..) => {
|
||||||
self.out.write_str(r#" alt=""#)?;
|
if self.img_alt_text == 1 {
|
||||||
|
self.out.write_str(r#" alt=""#)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Container::Math { display } => {
|
Container::Math { display } => {
|
||||||
self.out
|
self.out
|
||||||
|
@ -296,7 +302,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
|
||||||
if c.is_block_container() && !matches!(c, Container::Footnote { .. }) {
|
if c.is_block_container() && !matches!(c, Container::Footnote { .. }) {
|
||||||
self.out.write_char('\n')?;
|
self.out.write_char('\n')?;
|
||||||
}
|
}
|
||||||
if self.text_only && !matches!(c, Container::Image(..)) {
|
if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match c {
|
match c {
|
||||||
|
@ -360,12 +366,14 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
|
||||||
Container::Span => self.out.write_str("</span>")?,
|
Container::Span => self.out.write_str("</span>")?,
|
||||||
Container::Link(..) => self.out.write_str("</a>")?,
|
Container::Link(..) => self.out.write_str("</a>")?,
|
||||||
Container::Image(src, ..) => {
|
Container::Image(src, ..) => {
|
||||||
self.text_only = false;
|
if self.img_alt_text == 1 {
|
||||||
if src.is_empty() {
|
if !src.is_empty() {
|
||||||
|
self.out.write_str(r#"" src=""#)?;
|
||||||
|
self.write_attr(&src)?;
|
||||||
|
}
|
||||||
self.out.write_str(r#"">"#)?;
|
self.out.write_str(r#"">"#)?;
|
||||||
} else {
|
|
||||||
write!(self.out, r#"" src="{}">"#, src)?;
|
|
||||||
}
|
}
|
||||||
|
self.img_alt_text -= 1;
|
||||||
}
|
}
|
||||||
Container::Verbatim => self.out.write_str("</code>")?,
|
Container::Verbatim => self.out.write_str("</code>")?,
|
||||||
Container::Math { display } => {
|
Container::Math { display } => {
|
||||||
|
@ -388,16 +396,19 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Str(s) => match self.raw {
|
Event::Str(s) => match self.raw {
|
||||||
|
Raw::None if self.img_alt_text > 0 => self.write_attr(&s)?,
|
||||||
Raw::None => self.write_text(&s)?,
|
Raw::None => self.write_text(&s)?,
|
||||||
Raw::Html => self.out.write_str(&s)?,
|
Raw::Html => self.out.write_str(&s)?,
|
||||||
Raw::Other => {}
|
Raw::Other => {}
|
||||||
},
|
},
|
||||||
Event::FootnoteReference(_tag, number) => {
|
Event::FootnoteReference(_tag, number) => {
|
||||||
write!(
|
if self.img_alt_text == 0 {
|
||||||
self.out,
|
write!(
|
||||||
r##"<a id="fnref{}" href="#fn{}" role="doc-noteref"><sup>{}</sup></a>"##,
|
self.out,
|
||||||
number, number, number
|
r##"<a id="fnref{}" href="#fn{}" role="doc-noteref"><sup>{}</sup></a>"##,
|
||||||
)?;
|
number, number, number
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Event::Symbol(sym) => write!(self.out, ":{}:", sym)?,
|
Event::Symbol(sym) => write!(self.out, ":{}:", sym)?,
|
||||||
Event::LeftSingleQuote => self.out.write_str("‘")?,
|
Event::LeftSingleQuote => self.out.write_str("‘")?,
|
||||||
|
|
|
@ -2,11 +2,25 @@
|
||||||
name = "jotdown-afl"
|
name = "jotdown-afl"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
default-run = "main"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
afl = "0.11"
|
afl = "0.11"
|
||||||
jotdown = { path = "../../", features = ["deterministic"] }
|
jotdown = { path = "../../", features = ["deterministic"] }
|
||||||
|
html5ever = "0.26"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "gen"
|
name = "main"
|
||||||
path = "src/gen.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "parse"
|
||||||
|
path = "src/parse.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "html"
|
||||||
|
path = "src/html.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["debug"]
|
||||||
|
debug = []
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
use afl::fuzz;
|
|
||||||
|
|
||||||
use jotdown::Render;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
fuzz!(|data: &[u8]| {
|
|
||||||
if let Ok(s) = std::str::from_utf8(data) {
|
|
||||||
let p = jotdown::Parser::new(s);
|
|
||||||
let mut output = String::new();
|
|
||||||
jotdown::html::Renderer.push(p, &mut output).unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
3
tests/afl/src/html.rs
Normal file
3
tests/afl/src/html.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
afl::fuzz!(|data: &[u8]| { jotdown_afl::html(data) });
|
||||||
|
}
|
180
tests/afl/src/lib.rs
Normal file
180
tests/afl/src/lib.rs
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
use jotdown::Render;
|
||||||
|
|
||||||
|
use html5ever::tendril;
|
||||||
|
use html5ever::tendril::TendrilSink;
|
||||||
|
use html5ever::tokenizer;
|
||||||
|
use html5ever::tree_builder;
|
||||||
|
|
||||||
|
pub fn parse(data: &[u8]) {
|
||||||
|
if let Ok(s) = std::str::from_utf8(data) {
|
||||||
|
jotdown::Parser::new(s).last();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn html(data: &[u8]) {
|
||||||
|
if data.iter().any(|i| *i == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Ok(s) = std::str::from_utf8(data) {
|
||||||
|
if !s.contains("=html") {
|
||||||
|
let p = jotdown::Parser::new(s);
|
||||||
|
let mut html = "<!DOCTYPE html>\n".to_string();
|
||||||
|
jotdown::html::Renderer.push(p, &mut html).unwrap();
|
||||||
|
validate_html(&html);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_html(html: &str) {
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
let mut has_error = false;
|
||||||
|
|
||||||
|
html5ever::parse_document(
|
||||||
|
Dom {
|
||||||
|
names: Vec::new(),
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
has_error: &mut has_error,
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
line_no: 1,
|
||||||
|
#[cfg(not(feature = "debug"))]
|
||||||
|
_lifetime: std::marker::PhantomData,
|
||||||
|
},
|
||||||
|
html5ever::ParseOpts {
|
||||||
|
tokenizer: tokenizer::TokenizerOpts {
|
||||||
|
exact_errors: true,
|
||||||
|
..tokenizer::TokenizerOpts::default()
|
||||||
|
},
|
||||||
|
tree_builder: tree_builder::TreeBuilderOpts {
|
||||||
|
exact_errors: true,
|
||||||
|
scripting_enabled: false,
|
||||||
|
..tree_builder::TreeBuilderOpts::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.from_utf8()
|
||||||
|
.read_from(&mut std::io::Cursor::new(html))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
if has_error {
|
||||||
|
eprintln!("html:");
|
||||||
|
html.split('\n').enumerate().for_each(|(i, l)| {
|
||||||
|
eprintln!("{:>2}:{}", i + 1, l);
|
||||||
|
});
|
||||||
|
eprintln!("\n");
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Dom<'a> {
|
||||||
|
names: Vec<html5ever::QualName>,
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
has_error: &'a mut bool,
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
line_no: u64,
|
||||||
|
#[cfg(not(feature = "debug"))]
|
||||||
|
_lifetime: std::marker::PhantomData<&'a ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> tree_builder::TreeSink for Dom<'a> {
|
||||||
|
type Handle = usize;
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn get_document(&mut self) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(self) -> Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn same_node(&self, x: &usize, y: &usize) -> bool {
|
||||||
|
x == y
|
||||||
|
}
|
||||||
|
|
||||||
|
fn elem_name(&self, i: &usize) -> html5ever::ExpandedName {
|
||||||
|
self.names[i - 1].expanded()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_element(
|
||||||
|
&mut self,
|
||||||
|
name: html5ever::QualName,
|
||||||
|
_: Vec<html5ever::Attribute>,
|
||||||
|
_: tree_builder::ElementFlags,
|
||||||
|
) -> usize {
|
||||||
|
self.names.push(name);
|
||||||
|
self.names.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_error(&mut self, msg: std::borrow::Cow<'static, str>) {
|
||||||
|
let whitelist = &[
|
||||||
|
"Bad character", // bad characters in input will pass through
|
||||||
|
"Duplicate attribute", // djot is case-sensitive while html is not
|
||||||
|
// tags may be nested incorrectly, e.g. <a> within <a>
|
||||||
|
"Unexpected token Tag",
|
||||||
|
"Found special tag while closing generic tag",
|
||||||
|
"Formatting element not current node",
|
||||||
|
"Formatting element not open",
|
||||||
|
// FIXME bug caused by empty table at end of list
|
||||||
|
"No matching tag to close",
|
||||||
|
"Unexpected open element while closing",
|
||||||
|
];
|
||||||
|
if !whitelist.iter().any(|e| msg.starts_with(e)) {
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
{
|
||||||
|
*self.has_error = true;
|
||||||
|
eprintln!("{}: {}\n", self.line_no, msg);
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "debug"))]
|
||||||
|
{
|
||||||
|
panic!("invalid html");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_quirks_mode(&mut self, _: tree_builder::QuirksMode) {}
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
fn set_current_line(&mut self, l: u64) {
|
||||||
|
self.line_no = l;
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "debug"))]
|
||||||
|
fn set_current_line(&mut self, _: u64) {}
|
||||||
|
|
||||||
|
fn append(&mut self, _: &usize, _: tree_builder::NodeOrText<usize>) {}
|
||||||
|
fn append_before_sibling(&mut self, _: &usize, _: tree_builder::NodeOrText<usize>) {}
|
||||||
|
fn append_based_on_parent_node(
|
||||||
|
&mut self,
|
||||||
|
_: &usize,
|
||||||
|
_: &usize,
|
||||||
|
_: tree_builder::NodeOrText<usize>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn append_doctype_to_document(
|
||||||
|
&mut self,
|
||||||
|
_: tendril::StrTendril,
|
||||||
|
_: tendril::StrTendril,
|
||||||
|
_: tendril::StrTendril,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn remove_from_parent(&mut self, _: &usize) {}
|
||||||
|
fn reparent_children(&mut self, _: &usize, _: &usize) {}
|
||||||
|
|
||||||
|
fn mark_script_already_started(&mut self, _: &usize) {}
|
||||||
|
|
||||||
|
fn add_attrs_if_missing(&mut self, _: &usize, _: Vec<html5ever::Attribute>) {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_pi(&mut self, _: tendril::StrTendril, _: tendril::StrTendril) -> usize {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_template_contents(&mut self, _: &usize) -> usize {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_comment(&mut self, _: tendril::StrTendril) -> usize {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
}
|
18
tests/afl/src/main.rs
Normal file
18
tests/afl/src/main.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = std::env::args();
|
||||||
|
let _program = args.next();
|
||||||
|
let target = args.next().expect("no target");
|
||||||
|
assert_eq!(args.next(), None);
|
||||||
|
|
||||||
|
let f = match target.as_str() {
|
||||||
|
"parse" => jotdown_afl::parse,
|
||||||
|
"html" => jotdown_afl::html,
|
||||||
|
_ => panic!("unknown target '{}'", target),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut input = Vec::new();
|
||||||
|
std::io::stdin().read_to_end(&mut input).unwrap();
|
||||||
|
f(&input);
|
||||||
|
}
|
3
tests/afl/src/parse.rs
Normal file
3
tests/afl/src/parse.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
afl::fuzz!(|data: &[u8]| { jotdown_afl::parse(data) });
|
||||||
|
}
|
Loading…
Reference in a new issue