diff --git a/.gitignore b/.gitignore index 807922d..b936c0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ /target .idea/ .DS_Store + +# Fuzzing artifacts +hfuzz_workspace/ +hfuzz_target/ +fuzz/target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6a52b62..ca6e42c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + [[package]] name = "arrayref" version = "0.3.7" @@ -280,6 +286,19 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "honggfuzz" +version = "0.5.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e8319f3cc8fe416e7aa1ab95dcc04fd49f35397a47d0b2f0f225f6dba346a07" +dependencies = [ + "arbitrary", + "lazy_static", + "memmap2", + "rustc_version", + "semver", +] + [[package]] name = "indexmap" version = "2.0.0" @@ -307,9 +326,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" @@ -351,6 +370,15 @@ version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +[[package]] +name = "memmap2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92" +dependencies = [ + "libc", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -416,6 +444,14 @@ dependencies = [ "thiserror", ] +[[package]] +name = "pg_query-fuzz" +version = "0.0.0" +dependencies = [ + "honggfuzz", + "pg_query", +] + [[package]] name = "pretty_assertions" version = "1.4.0" @@ -579,6 +615,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.11" @@ -598,6 +643,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.188" diff --git a/Cargo.toml b/Cargo.toml index be3340f..03d3555 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,3 +42,6 @@ regex = "1.6.0" [[bench]] name = "parse_vs_summary" harness = false + +[workspace] +members = ["fuzz"] diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock new file mode 100644 index 0000000..3cc0eb6 --- /dev/null +++ b/fuzz/Cargo.lock @@ -0,0 +1,899 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "bindgen" +version = "0.66.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "honggfuzz" +version = "0.5.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e8319f3cc8fe416e7aa1ab95dcc04fd49f35397a47d0b2f0f225f6dba346a07" +dependencies = [ + "arbitrary", + "lazy_static", + "memmap2", + "rustc_version", + "semver", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.181" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pg_query" +version = "6.1.1" +dependencies = [ + "bindgen", + "cc", + "fs_extra", + "glob", + "itertools 0.10.5", + "prost", + "prost-build", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "pg_query-fuzz" +version = "0.0.0" +dependencies = [ + "honggfuzz", + "pg_query", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[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 = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +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 = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[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.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[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 = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[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.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[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 = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..aa661fc --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "pg_query-fuzz" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies] +honggfuzz = "=0.5.58" +pg_query = { path = ".." } + +[[bin]] +name = "fuzz_parse" +path = "src/fuzz_parse.rs" diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 0000000..b50a169 --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,54 @@ +# Fuzzing pg_query + +This directory contains fuzzing harnesses using [honggfuzz-rs](https://github.com/rust-fuzz/honggfuzz-rs). + +## Prerequisites + +Install honggfuzz: + +```bash +cargo install honggfuzz +``` + +You also need honggfuzz system dependencies. On Ubuntu/Debian: + +```bash +sudo apt-get install build-essential binutils-dev libunwind-dev libblocksruntime-dev liblzma-dev +``` + +## Running the fuzzer + +From the repository root: + +```bash +cargo hfuzz run fuzz_parse +``` + +The fuzzer will run indefinitely, saving crashes to `hfuzz_workspace/fuzz_parse/`. + +## Fuzzing targets + +The `fuzz_parse` harness tests the following functions with random byte input: + +- `pg_query::parse()` - Main SQL parser +- `pg_query::normalize()` - Query normalization +- `pg_query::fingerprint()` - Query fingerprinting +- `pg_query::scan()` - SQL tokenizer +- `pg_query::split_with_parser()` - Statement splitting (strict) +- `pg_query::split_with_scanner()` - Statement splitting (lenient) +- `pg_query::summary()` - Fast summary extraction + +When parsing succeeds, it also tests: +- `ParseResult::deparse()` - Round-trip back to SQL +- `ParseResult::tables()` - Table extraction +- `ParseResult::functions()` - Function extraction +- `ParseResult::statement_types()` - Statement classification +- `ParseResult::truncate()` - Query truncation + +## Reproducing crashes + +To reproduce a crash from a saved input file: + +```bash +cargo hfuzz run-debug fuzz_parse hfuzz_workspace/fuzz_parse/SIGABRT.PC.*.fuzz +``` diff --git a/fuzz/src/fuzz_parse.rs b/fuzz/src/fuzz_parse.rs new file mode 100644 index 0000000..6110229 --- /dev/null +++ b/fuzz/src/fuzz_parse.rs @@ -0,0 +1,40 @@ +use honggfuzz::fuzz; + +fn main() { + loop { + fuzz!(|data: &[u8]| { + // Convert bytes to string, skipping invalid UTF-8 + let Ok(input) = std::str::from_utf8(data) else { + return; + }; + + // Fuzz the main parse function + let _ = pg_query::parse(input); + + // Fuzz normalize + let _ = pg_query::normalize(input); + + // Fuzz fingerprint + let _ = pg_query::fingerprint(input); + + // Fuzz scanner + let _ = pg_query::scan(input); + + // Fuzz statement splitting + let _ = pg_query::split_with_parser(input); + let _ = pg_query::split_with_scanner(input); + + // Fuzz summary + let _ = pg_query::summary(input, 100); + + // If parse succeeds, also test deparse round-trip + if let Ok(result) = pg_query::parse(input) { + let _ = result.deparse(); + let _ = result.tables(); + let _ = result.functions(); + let _ = result.statement_types(); + let _ = result.truncate(50); + } + }); + } +} diff --git a/tests/summary_filter_column_tests.rs b/tests/summary_filter_column_tests.rs index d7f88e8..9ade864 100644 --- a/tests/summary_filter_column_tests.rs +++ b/tests/summary_filter_column_tests.rs @@ -11,182 +11,152 @@ use pg_query::FilterColumn; fn it_finds_unqualified_names() { let result = summary("SELECT * FROM x WHERE y = $1 AND z = 1", -1).unwrap(); let filter_columns: Vec = sorted(result.filter_columns).collect(); - assert_eq!( - filter_columns, - vec![ - FilterColumn { schema_name: None, table_name: None, column: "y".to_string() }, - FilterColumn { schema_name: None, table_name: None, column: "z".to_string() }, - ] - ); + assert_eq!(filter_columns, vec![FilterColumn { schema_name: None, table_name: None, column: "y".to_string() }, FilterColumn { + schema_name: None, + table_name: None, + column: "z".to_string() + },]); } #[test] fn it_finds_qualified_names() { let result = summary("SELECT * FROM x WHERE x.y = $1 AND x.z = 1", -1).unwrap(); let filter_columns: Vec = sorted(result.filter_columns).collect(); - assert_eq!( - filter_columns, - [ - FilterColumn { schema_name: None, table_name: Some("x".into()), column: "y".into() }, - FilterColumn { schema_name: None, table_name: Some("x".into()), column: "z".into() }, - ] - ); + assert_eq!(filter_columns, [FilterColumn { schema_name: None, table_name: Some("x".into()), column: "y".into() }, FilterColumn { + schema_name: None, + table_name: Some("x".into()), + column: "z".into() + },]); } #[test] fn it_traverses_into_ctes() { let result = summary("WITH a AS (SELECT * FROM x WHERE x.y = $1 AND x.z = 1) SELECT * FROM a WHERE b = 5", -1).unwrap(); let filter_columns: Vec = sorted(result.filter_columns).collect(); - assert_eq!( - filter_columns, - [ - FilterColumn { schema_name: None, table_name: None, column: "b".into() }, - FilterColumn { schema_name: None, table_name: Some("x".into()), column: "y".into() }, - FilterColumn { schema_name: None, table_name: Some("x".into()), column: "z".into() }, - ] - ); + assert_eq!(filter_columns, [ + FilterColumn { schema_name: None, table_name: None, column: "b".into() }, + FilterColumn { schema_name: None, table_name: Some("x".into()), column: "y".into() }, + FilterColumn { schema_name: None, table_name: Some("x".into()), column: "z".into() }, + ]); } #[test] fn it_recognizes_boolean_tests() { let result = summary("SELECT * FROM x WHERE x.y IS TRUE AND x.z IS NOT FALSE", -1).unwrap(); let filter_columns: Vec = sorted(result.filter_columns).collect(); - assert_eq!( - filter_columns, - [ - FilterColumn { schema_name: None, table_name: Some("x".into()), column: "y".into() }, - FilterColumn { schema_name: None, table_name: Some("x".into()), column: "z".into() }, - ] - ); + assert_eq!(filter_columns, [FilterColumn { schema_name: None, table_name: Some("x".into()), column: "y".into() }, FilterColumn { + schema_name: None, + table_name: Some("x".into()), + column: "z".into() + },]); } #[test] fn it_recognizes_null_tests() { let result = summary("SELECT * FROM x WHERE x.y IS NULL AND x.z IS NOT NULL", -1).unwrap(); let filter_columns: Vec = sorted(result.filter_columns).collect(); - assert_eq!( - filter_columns, - [ - FilterColumn { schema_name: None, table_name: Some("x".into()), column: "y".into() }, - FilterColumn { schema_name: None, table_name: Some("x".into()), column: "z".into() }, - ] - ); + assert_eq!(filter_columns, [FilterColumn { schema_name: None, table_name: Some("x".into()), column: "y".into() }, FilterColumn { + schema_name: None, + table_name: Some("x".into()), + column: "z".into() + },]); } #[test] fn it_finds_coalesce_argument_names() { let result = summary("SELECT * FROM x WHERE x.y = COALESCE(z.a, z.b)", -1).unwrap(); let filter_columns: Vec = sorted(result.filter_columns).collect(); - assert_eq!( - filter_columns, - [ - FilterColumn { schema_name: None, table_name: Some("x".into()), column: "y".into() }, - FilterColumn { schema_name: None, table_name: Some("z".into()), column: "a".into() }, - FilterColumn { schema_name: None, table_name: Some("z".into()), column: "b".into() }, - ] - ); + assert_eq!(filter_columns, [ + FilterColumn { schema_name: None, table_name: Some("x".into()), column: "y".into() }, + FilterColumn { schema_name: None, table_name: Some("z".into()), column: "a".into() }, + FilterColumn { schema_name: None, table_name: Some("z".into()), column: "b".into() }, + ]); } #[test] fn it_finds_unqualified_names_in_union_query() { let result = summary("SELECT * FROM x where y = $1 UNION SELECT * FROM x where z = $2", -1).unwrap(); let filter_columns: Vec = sorted(result.filter_columns).collect(); - assert_eq!( - filter_columns, - [ - FilterColumn { schema_name: None, table_name: None, column: "y".into() }, - FilterColumn { schema_name: None, table_name: None, column: "z".into() }, - ] - ); + assert_eq!(filter_columns, [FilterColumn { schema_name: None, table_name: None, column: "y".into() }, FilterColumn { + schema_name: None, + table_name: None, + column: "z".into() + },]); } #[test] fn it_finds_unqualified_names_in_union_all_query() { let result = summary("SELECT * FROM x where y = $1 UNION ALL SELECT * FROM x where z = $2", -1).unwrap(); let filter_columns: Vec = sorted(result.filter_columns).collect(); - assert_eq!( - filter_columns, - [ - FilterColumn { schema_name: None, table_name: None, column: "y".into() }, - FilterColumn { schema_name: None, table_name: None, column: "z".into() }, - ] - ); + assert_eq!(filter_columns, [FilterColumn { schema_name: None, table_name: None, column: "y".into() }, FilterColumn { + schema_name: None, + table_name: None, + column: "z".into() + },]); } #[test] fn it_finds_unqualified_names_in_except_query() { let result = summary("SELECT * FROM x where y = $1 EXCEPT SELECT * FROM x where z = $2", -1).unwrap(); let filter_columns: Vec = sorted(result.filter_columns).collect(); - assert_eq!( - filter_columns, - [ - FilterColumn { schema_name: None, table_name: None, column: "y".into() }, - FilterColumn { schema_name: None, table_name: None, column: "z".into() }, - ] - ); + assert_eq!(filter_columns, [FilterColumn { schema_name: None, table_name: None, column: "y".into() }, FilterColumn { + schema_name: None, + table_name: None, + column: "z".into() + },]); } #[test] fn it_finds_unqualified_names_in_except_all_query() { let result = summary("SELECT * FROM x where y = $1 EXCEPT ALL SELECT * FROM x where z = $2", -1).unwrap(); let filter_columns: Vec = sorted(result.filter_columns).collect(); - assert_eq!( - filter_columns, - [ - FilterColumn { schema_name: None, table_name: None, column: "y".into() }, - FilterColumn { schema_name: None, table_name: None, column: "z".into() }, - ] - ); + assert_eq!(filter_columns, [FilterColumn { schema_name: None, table_name: None, column: "y".into() }, FilterColumn { + schema_name: None, + table_name: None, + column: "z".into() + },]); } #[test] fn it_finds_unqualified_names_in_intersect_query() { let result = summary("SELECT * FROM x where y = $1 INTERSECT SELECT * FROM x where z = $2", -1).unwrap(); let filter_columns: Vec = sorted(result.filter_columns).collect(); - assert_eq!( - filter_columns, - [ - FilterColumn { schema_name: None, table_name: None, column: "y".into() }, - FilterColumn { schema_name: None, table_name: None, column: "z".into() }, - ] - ); + assert_eq!(filter_columns, [FilterColumn { schema_name: None, table_name: None, column: "y".into() }, FilterColumn { + schema_name: None, + table_name: None, + column: "z".into() + },]); } #[test] fn it_finds_unqualified_names_in_intersect_all_query() { let result = summary("SELECT * FROM x where y = $1 INTERSECT ALL SELECT * FROM x where z = $2", -1).unwrap(); let filter_columns: Vec = sorted(result.filter_columns).collect(); - assert_eq!( - filter_columns, - [ - FilterColumn { schema_name: None, table_name: None, column: "y".into() }, - FilterColumn { schema_name: None, table_name: None, column: "z".into() }, - ] - ); + assert_eq!(filter_columns, [FilterColumn { schema_name: None, table_name: None, column: "y".into() }, FilterColumn { + schema_name: None, + table_name: None, + column: "z".into() + },]); } #[test] fn it_ignores_target_list_columns() { let result = summary("SELECT a, y, z FROM x WHERE x.y = $1 AND x.z = 1", -1).unwrap(); let filter_columns: Vec = sorted(result.filter_columns).collect(); - assert_eq!( - filter_columns, - [ - FilterColumn { schema_name: None, table_name: Some("x".into()), column: "y".into() }, - FilterColumn { schema_name: None, table_name: Some("x".into()), column: "z".into() }, - ] - ); + assert_eq!(filter_columns, [FilterColumn { schema_name: None, table_name: Some("x".into()), column: "y".into() }, FilterColumn { + schema_name: None, + table_name: Some("x".into()), + column: "z".into() + },]); } #[test] fn it_ignores_order_by_columns() { let result = summary("SELECT * FROM x WHERE x.y = $1 AND x.z = 1 ORDER BY a, b", -1).unwrap(); let filter_columns: Vec = sorted(result.filter_columns).collect(); - assert_eq!( - filter_columns, - [ - FilterColumn { schema_name: None, table_name: Some("x".into()), column: "y".into() }, - FilterColumn { schema_name: None, table_name: Some("x".into()), column: "z".into() }, - ] - ); + assert_eq!(filter_columns, [FilterColumn { schema_name: None, table_name: Some("x".into()), column: "y".into() }, FilterColumn { + schema_name: None, + table_name: Some("x".into()), + column: "z".into() + },]); } diff --git a/tests/summary_tests.rs b/tests/summary_tests.rs index 96ca660..ea0d1a5 100644 --- a/tests/summary_tests.rs +++ b/tests/summary_tests.rs @@ -48,13 +48,11 @@ fn it_parses_query_with_nested_select_where() { assert_eq!(result.cte_names.len(), 0); assert_eq!(result.functions.len(), 0); let filter_columns: Vec<&FilterColumn> = sorted(&result.filter_columns).collect(); - assert_eq!( - filter_columns, - [ - &FilterColumn { schema_name: None, table_name: None, column: "col1".to_string() }, - &FilterColumn { schema_name: None, table_name: None, column: "col3".to_string() }, - ] - ); + assert_eq!(filter_columns, [&FilterColumn { schema_name: None, table_name: None, column: "col1".to_string() }, &FilterColumn { + schema_name: None, + table_name: None, + column: "col3".to_string() + },]); assert_eq!(result.statement_types(), ["SelectStmt"]); }