diff --git a/flake.nix b/flake.nix index 5a0d702..ecc8d01 100644 --- a/flake.nix +++ b/flake.nix @@ -95,6 +95,10 @@ pkgs.cargo-flamegraph ]; }; + + devShells.fuzz = devShells.crate.overrideAttrs (prev: { + nativeBuildInputs = [ unstableRustTarget pkgs.cargo-fuzz ]; + }); } ); } diff --git a/fuzz/.envrc b/fuzz/.envrc new file mode 100644 index 0000000..76a5eb1 --- /dev/null +++ b/fuzz/.envrc @@ -0,0 +1 @@ +use flake .#fuzz \ No newline at end of file diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000..ad8248e --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,6 @@ +.direnv/ + +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock new file mode 100644 index 0000000..2b28a53 --- /dev/null +++ b/fuzz/Cargo.lock @@ -0,0 +1,277 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "annotate-snippets" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fc7650eedcb2fee505aad48491529e408f0e854c2d9f63eb86c1361b9b3f93" +dependencies = [ + "anstyle", + "memchr", + "unicode-width", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "nomo" +version = "0.1.0" +dependencies = [ + "annotate-snippets", + "displaydoc", + "serde", + "serde_json", + "thiserror", + "winnow", +] + +[[package]] +name = "nomo-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "nomo", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..e6af9c6 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "nomo-fuzz" +version = "0.0.0" +publish = false +edition = "2024" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.nomo] +path = ".." + +[[bin]] +name = "fuzz_target_1" +path = "fuzz_targets/fuzz_target_1.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/fuzz_target_1.rs b/fuzz/fuzz_targets/fuzz_target_1.rs new file mode 100644 index 0000000..56024b8 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_target_1.rs @@ -0,0 +1,18 @@ +#![no_main] + +use libfuzzer_sys::Corpus; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: String| -> Corpus { + let Ok(parsed) = nomo::parser::parse(data.into()) else { + return Corpus::Reject; + }; + + let Ok(ast) = nomo::ast::parse(parsed.tokens()) else { + return Corpus::Keep; + }; + + let _instructions = nomo::emit::emit_machine(ast); + + Corpus::Keep +}); diff --git a/src/ast/mod.rs b/src/ast/mod.rs index da68d0b..04a7e5d 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -345,39 +345,67 @@ fn parse_conditional_chain<'input>( chain.push(if_block); - loop { - let (content, end_block): (Vec<_>, _) = repeat_till( - 0.., - parse_ast, - trace( - "conditional_chain_else/end", - alt((parse_end, parse_conditional_else)), - ), - ) - .parse_next(input)?; + let content = resume_after_cut( + cut_err(inner_conditional_chain), + repeat_till(0.., any, parse_end).map(|((), _)| ()), + ) + .parse_next(input)?; - chain.push(TemplateAstExpr::ConditionalContent { content }); - - let is_end = if let TemplateAstExpr::Block { ref expression, .. } = end_block - && let TemplateAstExpr::EndBlock = &**expression - { - true - } else { - false - }; - - chain.push(end_block); - - if is_end { - break; - } - } + chain.extend(content.into_iter().flatten()); Ok(TemplateAstExpr::ConditionalChain { chain }) }) .parse_next(input) } +fn inner_conditional_chain<'input>( + input: &mut Input<'input>, +) -> Result>, AstError> { + let mut needs_end = false; + + let mut chain = vec![]; + + loop { + let (content, end_block): (Vec<_>, _) = repeat_till( + 0.., + parse_ast, + trace( + "conditional_chain_else/end", + alt((parse_end, parse_conditional_else)), + ), + ) + .parse_next(input)?; + + chain.push(TemplateAstExpr::ConditionalContent { content }); + + let is_end = if let TemplateAstExpr::Block { ref expression, .. } = end_block + && let TemplateAstExpr::EndBlock = &**expression + { + true + } else { + false + }; + + if !is_end && needs_end { + return Err(AstError::from_input(input)); + } + + if let TemplateAstExpr::Block { expression, .. } = &end_block + && let TemplateAstExpr::ElseConditional { expression: None } = &**expression + { + needs_end = true; + } + + chain.push(end_block); + + if is_end { + break; + } + } + + Ok(chain) +} + fn parse_conditional_if<'input>( input: &mut Input<'input>, ) -> Result, AstError> { diff --git a/src/emit/mod.rs b/src/emit/mod.rs index 11985e8..daf8e0d 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -123,14 +123,16 @@ fn emit_ast_expr( { previous_post_whitespace_content = post_whitespace_content; if let Some(ws) = prev_whitespace_content { + let idx = end_indices.last().copied(); eval.insert( - eval.len() - 2, + idx.unwrap_or(eval.len()), Instruction::AppendContent { content: ws.source().clone(), }, ); - let index_index = end_indices.len() - 1; - end_indices[index_index] += 1; + if let Some(idx) = end_indices.last_mut() { + *idx += 1; + } } if let TemplateAstExpr::IfConditional { expression } = &**expression { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 20f2a14..a9171f0 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -383,10 +383,10 @@ fn parse_block_token<'input>(input: &mut Input<'input>) -> PResult<'input, Templ "parse_block_token", alt(( parse_ident, - parse_literal, - parse_condition_if, - parse_condition_else, - parse_end, + terminated(parse_literal, ident_terminator_check), + terminated(parse_condition_if, ident_terminator_check), + terminated(parse_condition_else, ident_terminator_check), + terminated(parse_end, ident_terminator_check), parse_whitespace, )), ) diff --git a/tests/checks.rs b/tests/checks.rs new file mode 100644 index 0000000..56c736a --- /dev/null +++ b/tests/checks.rs @@ -0,0 +1,18 @@ +#[test] +fn check_files() { + let files = std::fs::read_dir("tests/checks/").unwrap(); + + for file in files { + let input = std::fs::read_to_string(file.unwrap().path()).unwrap(); + + let Ok(parsed) = nomo::parser::parse(input.into()) else { + continue; + }; + + let Ok(ast) = nomo::ast::parse(parsed.tokens()) else { + continue; + }; + + let _emit = nomo::emit::emit_machine(ast); + } +} diff --git a/tests/checks/long.nomo b/tests/checks/long.nomo new file mode 100644 index 0000000..b2b5c2c Binary files /dev/null and b/tests/checks/long.nomo differ diff --git a/tests/checks/minimized-from-20a1f84df1a8d2c7ef7e9d538d6270e0379ab83e b/tests/checks/minimized-from-20a1f84df1a8d2c7ef7e9d538d6270e0379ab83e new file mode 100644 index 0000000..94cc156 --- /dev/null +++ b/tests/checks/minimized-from-20a1f84df1a8d2c7ef7e9d538d6270e0379ab83e @@ -0,0 +1,2 @@ + +{{if en}}{{ end}} \ No newline at end of file diff --git a/tests/checks/minimized-from-288cd3d11ffabe7f439c235924d88509deb93bc4 b/tests/checks/minimized-from-288cd3d11ffabe7f439c235924d88509deb93bc4 new file mode 100644 index 0000000..6405ddf --- /dev/null +++ b/tests/checks/minimized-from-288cd3d11ffabe7f439c235924d88509deb93bc4 @@ -0,0 +1,3 @@ +{{ if t }} +{{else}}{{ else }} +{{end }} \ No newline at end of file diff --git a/tests/file_tests.rs b/tests/file_tests.rs index 85049e8..3acb9ab 100644 --- a/tests/file_tests.rs +++ b/tests/file_tests.rs @@ -31,7 +31,13 @@ fn check_cases() { insta::assert_debug_snapshot!("1-parsed", parsed); - let ast = nomo::ast::parse(parsed.tokens()).unwrap(); + let ast = match nomo::ast::parse(parsed.tokens()) { + Ok(ast) => ast, + Err(err) => { + eprintln!("{}", err.to_report(input)); + panic!("Could not evaluate ast"); + } + }; insta::assert_debug_snapshot!("2-ast", ast);