mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
Add fuzz testing and parser edge case regression tests
Fuzz targets (cargo +nightly fuzz):
- fuzz_lexer: lexer must never panic on arbitrary input
- fuzz_parser: parser must never panic on arbitrary input
- fuzz_compile: full pipeline (parse → analyze → IR → optimize →
codegen → assemble → link) must never panic
Results: 3.3M iterations across all targets, zero crashes.
Seed corpus from all 7 examples + crafted edge cases.
11 new parser edge case tests:
- draw_followed_by_statement: regression for keyword-arg lookahead bug
- draw_with_frame_followed_by_statement: same with frame: arg
- nested_if_else_chain: 3-deep else-if chain
- deeply_nested_blocks: 4-deep nested if blocks
- empty_function_body: fun noop() {}
- empty_state_handlers: on enter/exit/frame {}
- button_start_in_condition: "start" keyword as button name
- button_select_in_condition: "select" keyword as button name
- multiple_draws_in_sequence: 3 draws followed by assignment
- parser_no_panic_on_garbage: 25 malformed inputs
- lexer_no_panic_on_garbage: 10 malformed inputs
221 tests total, all pre-commit checks pass.
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
parent
d7092f703d
commit
07dd5bd7e8
8 changed files with 781 additions and 0 deletions
4
fuzz/.gitignore
vendored
Normal file
4
fuzz/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
target
|
||||||
|
corpus
|
||||||
|
artifacts
|
||||||
|
coverage
|
||||||
438
fuzz/Cargo.lock
generated
Normal file
438
fuzz/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,438 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ariadne"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44055e597c674aef7cb903b2b9f6e4cba1277ed0d2d61dae7cd52d7ffa81f8e2"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-width",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder-lite"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.2.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
|
||||||
|
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 = "clap"
|
||||||
|
version = "4.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fdeflate"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "find-msvc-tools"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.25.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder-lite",
|
||||||
|
"moxcms",
|
||||||
|
"num-traits",
|
||||||
|
"png",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
|
[[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.184"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
|
||||||
|
|
||||||
|
[[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 = "miniz_oxide"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "moxcms"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"pxfm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nescript"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ariadne",
|
||||||
|
"clap",
|
||||||
|
"image",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nescript-fuzz"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"libfuzzer-sys",
|
||||||
|
"nescript",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "pxfm"
|
||||||
|
version = "0.1.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d"
|
||||||
|
|
||||||
|
[[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 = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[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 = "unicode-ident"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[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 = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yansi"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||||
42
fuzz/Cargo.toml
Normal file
42
fuzz/Cargo.toml
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
[package]
|
||||||
|
name = "nescript-fuzz"
|
||||||
|
version = "0.0.0"
|
||||||
|
publish = false
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
cargo-fuzz = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libfuzzer-sys = "0.4"
|
||||||
|
|
||||||
|
[dependencies.nescript]
|
||||||
|
path = ".."
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "fuzz_target_1"
|
||||||
|
path = "fuzz_targets/fuzz_target_1.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
bench = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "fuzz_lexer"
|
||||||
|
path = "fuzz_targets/fuzz_lexer.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
bench = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "fuzz_parser"
|
||||||
|
path = "fuzz_targets/fuzz_parser.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
bench = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "fuzz_compile"
|
||||||
|
path = "fuzz_targets/fuzz_compile.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
bench = false
|
||||||
33
fuzz/fuzz_targets/fuzz_compile.rs
Normal file
33
fuzz/fuzz_targets/fuzz_compile.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
fuzz_target!(|data: &[u8]| {
|
||||||
|
// The full compilation pipeline must never panic — errors are reported
|
||||||
|
// via diagnostics, not panics.
|
||||||
|
if let Ok(source) = std::str::from_utf8(data) {
|
||||||
|
let (program, parse_diags) = nescript::parser::parse(source);
|
||||||
|
if parse_diags.iter().any(|d| d.is_error()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(program) = program else { return };
|
||||||
|
|
||||||
|
let analysis = nescript::analyzer::analyze(&program);
|
||||||
|
if analysis.diagnostics.iter().any(|d| d.is_error()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IR lowering + optimization
|
||||||
|
let mut ir = nescript::ir::lower(&program, &analysis);
|
||||||
|
nescript::optimizer::optimize(&mut ir);
|
||||||
|
|
||||||
|
// Codegen + assembly
|
||||||
|
let codegen =
|
||||||
|
nescript::codegen::CodeGen::new(&analysis.var_allocations, &program.constants);
|
||||||
|
let instructions = codegen.generate(&program);
|
||||||
|
|
||||||
|
// Linking
|
||||||
|
let linker = nescript::linker::Linker::new(program.game.mirroring);
|
||||||
|
let _rom = linker.link(&instructions);
|
||||||
|
}
|
||||||
|
});
|
||||||
10
fuzz/fuzz_targets/fuzz_lexer.rs
Normal file
10
fuzz/fuzz_targets/fuzz_lexer.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
fuzz_target!(|data: &[u8]| {
|
||||||
|
// The lexer must never panic on any input — always produce tokens or errors.
|
||||||
|
if let Ok(source) = std::str::from_utf8(data) {
|
||||||
|
let _ = nescript::lexer::lex(source);
|
||||||
|
}
|
||||||
|
});
|
||||||
10
fuzz/fuzz_targets/fuzz_parser.rs
Normal file
10
fuzz/fuzz_targets/fuzz_parser.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
fuzz_target!(|data: &[u8]| {
|
||||||
|
// The parser must never panic — always produce an AST or diagnostics.
|
||||||
|
if let Ok(source) = std::str::from_utf8(data) {
|
||||||
|
let _ = nescript::parser::parse(source);
|
||||||
|
}
|
||||||
|
});
|
||||||
7
fuzz/fuzz_targets/fuzz_target_1.rs
Normal file
7
fuzz/fuzz_targets/fuzz_target_1.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
fuzz_target!(|data: &[u8]| {
|
||||||
|
// fuzzed code goes here
|
||||||
|
});
|
||||||
|
|
@ -678,3 +678,240 @@ fn parse_bank_decl() {
|
||||||
assert_eq!(prog.banks[0].name, "Level1Data");
|
assert_eq!(prog.banks[0].name, "Level1Data");
|
||||||
assert_eq!(prog.banks[0].bank_type, BankType::Prg);
|
assert_eq!(prog.banks[0].bank_type, BankType::Prg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Edge cases and regression tests ──
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn draw_followed_by_statement() {
|
||||||
|
// Regression: draw keyword-arg parser used to consume the next statement's
|
||||||
|
// identifier as a keyword arg, causing "expected ':'" errors.
|
||||||
|
let src = r#"
|
||||||
|
game "T" { mapper: NROM }
|
||||||
|
var i: u8 = 0
|
||||||
|
on frame {
|
||||||
|
draw Sprite at: (0, 0)
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
start Main
|
||||||
|
"#;
|
||||||
|
let prog = parse_ok(src);
|
||||||
|
let frame = prog.states[0].on_frame.as_ref().unwrap();
|
||||||
|
assert_eq!(frame.statements.len(), 2); // draw + assignment
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn draw_with_frame_followed_by_statement() {
|
||||||
|
let src = r#"
|
||||||
|
game "T" { mapper: NROM }
|
||||||
|
var x: u8 = 0
|
||||||
|
on frame {
|
||||||
|
draw Sprite at: (0, 0) frame: 1
|
||||||
|
x = 5
|
||||||
|
}
|
||||||
|
start Main
|
||||||
|
"#;
|
||||||
|
let prog = parse_ok(src);
|
||||||
|
let frame = prog.states[0].on_frame.as_ref().unwrap();
|
||||||
|
assert_eq!(frame.statements.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_if_else_chain() {
|
||||||
|
let src = r#"
|
||||||
|
game "T" { mapper: NROM }
|
||||||
|
var x: u8 = 0
|
||||||
|
on frame {
|
||||||
|
if x == 0 {
|
||||||
|
x = 1
|
||||||
|
} else if x == 1 {
|
||||||
|
x = 2
|
||||||
|
} else if x == 2 {
|
||||||
|
x = 3
|
||||||
|
} else {
|
||||||
|
x = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start Main
|
||||||
|
"#;
|
||||||
|
let prog = parse_ok(src);
|
||||||
|
let frame = prog.states[0].on_frame.as_ref().unwrap();
|
||||||
|
match &frame.statements[0] {
|
||||||
|
Statement::If(_, _, else_ifs, else_block, _) => {
|
||||||
|
assert_eq!(else_ifs.len(), 2);
|
||||||
|
assert!(else_block.is_some());
|
||||||
|
}
|
||||||
|
other => panic!("expected If, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deeply_nested_blocks() {
|
||||||
|
let src = r#"
|
||||||
|
game "T" { mapper: NROM }
|
||||||
|
var x: u8 = 0
|
||||||
|
on frame {
|
||||||
|
if x == 0 {
|
||||||
|
if x == 0 {
|
||||||
|
if x == 0 {
|
||||||
|
if x == 0 {
|
||||||
|
x = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start Main
|
||||||
|
"#;
|
||||||
|
parse_ok(src); // should not stack overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_function_body() {
|
||||||
|
let src = r#"
|
||||||
|
game "T" { mapper: NROM }
|
||||||
|
fun noop() {}
|
||||||
|
on frame { wait_frame }
|
||||||
|
start Main
|
||||||
|
"#;
|
||||||
|
let prog = parse_ok(src);
|
||||||
|
assert_eq!(prog.functions.len(), 1);
|
||||||
|
assert!(prog.functions[0].body.statements.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_state_handlers() {
|
||||||
|
let src = r#"
|
||||||
|
game "T" { mapper: NROM }
|
||||||
|
state Main {
|
||||||
|
on enter {}
|
||||||
|
on exit {}
|
||||||
|
on frame {}
|
||||||
|
}
|
||||||
|
start Main
|
||||||
|
"#;
|
||||||
|
let prog = parse_ok(src);
|
||||||
|
assert!(prog.states[0]
|
||||||
|
.on_enter
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.statements
|
||||||
|
.is_empty());
|
||||||
|
assert!(prog.states[0]
|
||||||
|
.on_exit
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.statements
|
||||||
|
.is_empty());
|
||||||
|
assert!(prog.states[0]
|
||||||
|
.on_frame
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.statements
|
||||||
|
.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn button_start_in_condition() {
|
||||||
|
// "start" is a keyword but valid as a button name
|
||||||
|
let src = r#"
|
||||||
|
game "T" { mapper: NROM }
|
||||||
|
on frame {
|
||||||
|
if button.start { wait_frame }
|
||||||
|
}
|
||||||
|
start Main
|
||||||
|
"#;
|
||||||
|
let prog = parse_ok(src);
|
||||||
|
let frame = prog.states[0].on_frame.as_ref().unwrap();
|
||||||
|
match &frame.statements[0] {
|
||||||
|
Statement::If(Expr::ButtonRead(_, button, _), _, _, _, _) => {
|
||||||
|
assert_eq!(button, "start");
|
||||||
|
}
|
||||||
|
other => panic!("expected if with button.start, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn button_select_in_condition() {
|
||||||
|
let src = r#"
|
||||||
|
game "T" { mapper: NROM }
|
||||||
|
on frame {
|
||||||
|
if button.select { wait_frame }
|
||||||
|
}
|
||||||
|
start Main
|
||||||
|
"#;
|
||||||
|
parse_ok(src); // "select" is a keyword too — must parse as button name
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiple_draws_in_sequence() {
|
||||||
|
let src = r#"
|
||||||
|
game "T" { mapper: NROM }
|
||||||
|
var x: u8 = 0
|
||||||
|
on frame {
|
||||||
|
draw A at: (0, 0)
|
||||||
|
draw B at: (10, 10)
|
||||||
|
draw C at: (20, 20)
|
||||||
|
x += 1
|
||||||
|
}
|
||||||
|
start Main
|
||||||
|
"#;
|
||||||
|
let prog = parse_ok(src);
|
||||||
|
let frame = prog.states[0].on_frame.as_ref().unwrap();
|
||||||
|
assert_eq!(frame.statements.len(), 4); // 3 draws + 1 assignment
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parser_no_panic_on_garbage() {
|
||||||
|
// The parser should never panic, only return errors.
|
||||||
|
let garbage_inputs = [
|
||||||
|
"",
|
||||||
|
"{}{}{}",
|
||||||
|
"game",
|
||||||
|
"game \"\"",
|
||||||
|
"game \"\" {",
|
||||||
|
"game \"\" { mapper: }",
|
||||||
|
"var : = 0",
|
||||||
|
"fun () {}",
|
||||||
|
"if if if",
|
||||||
|
"while { }",
|
||||||
|
"draw",
|
||||||
|
"draw X",
|
||||||
|
"draw X at:",
|
||||||
|
"draw X at: (",
|
||||||
|
"draw X at: (0",
|
||||||
|
"draw X at: (0,",
|
||||||
|
"draw X at: (0, 0",
|
||||||
|
"sprite {",
|
||||||
|
"palette { colors: }",
|
||||||
|
"[[[",
|
||||||
|
"))))",
|
||||||
|
"0x 0b",
|
||||||
|
"\"unterminated",
|
||||||
|
"/* no block comments */",
|
||||||
|
"!@#$%",
|
||||||
|
&"x ".repeat(1000), // long input
|
||||||
|
];
|
||||||
|
for input in &garbage_inputs {
|
||||||
|
let _ = parse(input); // must not panic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lexer_no_panic_on_garbage() {
|
||||||
|
use crate::lexer::lex;
|
||||||
|
let garbage_inputs = [
|
||||||
|
"\0\0\0",
|
||||||
|
"\x01\x02\x03",
|
||||||
|
"\"\\",
|
||||||
|
"0x",
|
||||||
|
"0b",
|
||||||
|
"99999999999999999999",
|
||||||
|
"0xFFFFFFFFFF",
|
||||||
|
&"a".repeat(10000),
|
||||||
|
"!!!!!!",
|
||||||
|
"\t\r\n \t\r\n",
|
||||||
|
];
|
||||||
|
for input in &garbage_inputs {
|
||||||
|
let _ = lex(input); // must not panic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue