diff --git a/rapx/Cargo.lock b/rapx/Cargo.lock index 5d1fffbd..3c5edce8 100644 --- a/rapx/Cargo.lock +++ b/rapx/Cargo.lock @@ -4,40 +4,28 @@ version = 4 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -59,9 +47,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -74,15 +62,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -107,17 +95,23 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -125,7 +119,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-link", ] [[package]] @@ -143,28 +137,43 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.100", + "syn 2.0.117", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "camino" -version = "1.1.9" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -192,10 +201,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.16" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ + "find-msvc-tools", "shlex", ] @@ -210,22 +220,21 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-link 0.1.0", + "windows-link", ] [[package]] @@ -241,9 +250,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -261,9 +270,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -273,21 +282,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "color-print" @@ -307,14 +316,14 @@ dependencies = [ "nom", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "colored" @@ -333,6 +342,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97af0562545a7d7f3d9222fcf909963bec36dcb502afaacab98c6ffac8da47ce" +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -341,9 +362,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "doc-comment" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" [[package]] name = "either" @@ -351,6 +372,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "equivalent" version = "1.0.2" @@ -367,6 +394,12 @@ dependencies = [ "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 = "fern" version = "0.6.2" @@ -377,6 +410,12 @@ dependencies = [ "log", ] +[[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" @@ -399,46 +438,63 @@ dependencies = [ "windows-sys 0.59.0", ] +[[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.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + [[package]] name = "getrandom" -version = "0.3.2" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", - "wasi", + "r-efi 6.0.0", + "wasip2", + "wasip3", ] [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "allocator-api2", - "equivalent", "foldhash", ] [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -454,20 +510,21 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -481,24 +538,43 @@ dependencies = [ "cc", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "if_chain" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" +checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb" [[package]] name = "indexmap" -version = "2.12.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] +[[package]] +name = "insta" +version = "1.46.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4" +dependencies = [ + "console", + "once_cell", + "serde", + "similar", + "tempfile", +] + [[package]] name = "intervals" version = "2.1.0" @@ -510,13 +586,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -545,15 +621,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -565,20 +641,26 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[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.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets", + "windows-link", ] [[package]] @@ -589,15 +671,15 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "log" -version = "0.4.26" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.4" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "minimal-lexical" @@ -607,9 +689,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -699,18 +781,18 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.21.0" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -720,12 +802,12 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "petgraph" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a98c6720655620a521dcc722d0ad66cd8afd5d86e34a89ef691c50b7b24de06" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", - "hashbrown 0.15.2", + "hashbrown 0.15.5", "indexmap", "serde", ] @@ -745,39 +827,54 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", "rand_core", - "zerocopy", ] [[package]] @@ -792,11 +889,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom", + "getrandom 0.3.4", ] [[package]] @@ -804,6 +901,8 @@ name = "rapx" version = "0.7.0" dependencies = [ "annotate-snippets", + "anyhow", + "bit-set", "cargo_metadata", "chrono", "clap", @@ -812,7 +911,10 @@ dependencies = [ "colorful", "fern", "fs4", + "fs_extra", "if_chain", + "indexmap", + "insta", "intervals", "itertools 0.14.0", "lazy_static", @@ -827,9 +929,10 @@ dependencies = [ "safety-parser", "serde", "serde_json", + "serde_yaml", "shlex", "snafu", - "syn 2.0.100", + "syn 2.0.117", "toml 0.8.23", "wait-timeout", "walkdir", @@ -838,9 +941,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -850,9 +953,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -861,9 +964,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rust_intervals" @@ -873,9 +976,9 @@ checksum = "994f92311abb71a820e646e4d9d54b9d0dd7f62f7a211298d3316e891bbaac41" [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" @@ -898,29 +1001,29 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "safety-parser" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671b4cfc6ae3c44bf69877f355002e2e9e5982d18db64dfbe109892e977a4bba" +checksum = "5fc7505b3e8044ccc7ea32835442e820f66689ef95486a2d4c2a0e61c6ae3d01" dependencies = [ "indexmap", "proc-macro2", "quote", "serde", - "syn 2.0.100", + "syn 2.0.117", "tinytemplate", - "toml 0.9.6", + "toml 0.9.12+spec-1.1.0", ] [[package]] @@ -934,11 +1037,12 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" dependencies = [ "serde", + "serde_core", ] [[package]] @@ -968,19 +1072,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -994,19 +1099,38 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + [[package]] name = "snafu" version = "0.7.5" @@ -1049,15 +1173,28 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1075,7 +1212,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -1102,14 +1239,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.6" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae2a4cf385da23d1d53bc15cdfa5c2109e93d8d362393c801e87da2f72f0e201" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", - "serde_spanned 1.0.3", - "toml_datetime 0.7.3", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow", @@ -1126,9 +1263,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] @@ -1149,9 +1286,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.2" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] @@ -1164,21 +1301,33 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "utf8parse" @@ -1206,45 +1355,41 @@ dependencies = [ ] [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen" -version = "0.2.100" +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 = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" +name = "wasm-bindgen" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.100", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1252,26 +1397,60 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.100", - "wasm-bindgen-backend", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] +[[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 = "winapi" version = "0.3.9" @@ -1290,11 +1469,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1305,18 +1484,38 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.52.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-targets", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "windows-link" -version = "0.1.0" +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] name = "windows-link" @@ -1324,6 +1523,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -1339,7 +1556,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -1408,20 +1625,99 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +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 0.5.0", + "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 0.5.0", + "indexmap", + "prettyplease", + "syn 2.0.117", + "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 = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "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]] @@ -1437,9 +1733,9 @@ dependencies = [ [[package]] name = "z3-sys" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70c949aa246ec4f1a08806ce5611ef3e8d82d6eb15dd002311a57395d198883b" +checksum = "6cd1b38ffd95fae0fcc40026fb1a822074e2a923ad4e011e90df878cbed721e0" dependencies = [ "bindgen", "pkg-config", @@ -1447,20 +1743,26 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/rapx/Cargo.toml b/rapx/Cargo.toml index e2de1ef6..b0fee9d5 100644 --- a/rapx/Cargo.toml +++ b/rapx/Cargo.toml @@ -46,12 +46,18 @@ toml = "0.8.23" itertools = "0.14.0" safety-parser = "0.4.1" syn = { version = "2", features = ["extra-traits", "full"] } +fs_extra = "1.3.0" +bit-set = "0.8.0" rust_intervals = "0.3.0" clap = { version = "4.5.60" } clap-cargo = "0.18.3" shlex = "1.3.0" color-print = "0.3.7" fs4 = "0.13.1" +indexmap = "2.13.0" +serde_yaml = "0.9.34" +anyhow = "1.0.102" +insta = { version = "1.46.3", features = ["yaml"] } [features] backtraces = ["snafu/backtraces", "snafu/backtraces-impl-backtrace-crate"] diff --git a/rapx/src/analysis/core/alias_analysis/mfp/interproc.rs b/rapx/src/analysis/core/alias_analysis/mfp/interproc.rs index 53320f6b..4a80cb50 100644 --- a/rapx/src/analysis/core/alias_analysis/mfp/interproc.rs +++ b/rapx/src/analysis/core/alias_analysis/mfp/interproc.rs @@ -1,5 +1,4 @@ /// Interprocedural analysis utilities -extern crate rustc_mir_dataflow; use rustc_hir::def_id::DefId; use rustc_middle::mir::{Body, TerminatorKind}; use rustc_mir_dataflow::ResultsCursor; diff --git a/rapx/src/analysis/core/alias_analysis/mfp/intraproc.rs b/rapx/src/analysis/core/alias_analysis/mfp/intraproc.rs index c14d82f9..0009853c 100644 --- a/rapx/src/analysis/core/alias_analysis/mfp/intraproc.rs +++ b/rapx/src/analysis/core/alias_analysis/mfp/intraproc.rs @@ -1,4 +1,3 @@ -extern crate rustc_mir_dataflow; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::DefId; use rustc_middle::{ diff --git a/rapx/src/analysis/core/alias_analysis/mfp/mod.rs b/rapx/src/analysis/core/alias_analysis/mfp/mod.rs index d9be5c3b..c545e421 100644 --- a/rapx/src/analysis/core/alias_analysis/mfp/mod.rs +++ b/rapx/src/analysis/core/alias_analysis/mfp/mod.rs @@ -2,7 +2,6 @@ pub mod interproc; pub mod intraproc; pub mod transfer; -extern crate rustc_mir_dataflow; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def_id::DefId; use rustc_middle::mir::{Operand, TerminatorKind}; diff --git a/rapx/src/analysis/core/api_dependency/fuzzable.rs b/rapx/src/analysis/core/api_dependency/fuzzable.rs new file mode 100644 index 00000000..76d26c9a --- /dev/null +++ b/rapx/src/analysis/core/api_dependency/fuzzable.rs @@ -0,0 +1,117 @@ +use rustc_hir::LangItem; +use rustc_middle::ty::{self, Ty, TyCtxt, TyKind}; +use rustc_span::sym; + +fn is_fuzzable_std_ty<'tcx>(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>, depth: usize) -> bool { + match ty.kind() { + ty::Adt(def, args) => { + if tcx.is_lang_item(def.did(), LangItem::String) { + return true; + } + if tcx.is_diagnostic_item(sym::Vec, def.did()) + && is_fuzzable_ty(args.type_at(0), tcx, depth + 1) + { + return true; + } + if tcx.is_diagnostic_item(sym::Arc, def.did()) + && is_fuzzable_ty(args.type_at(0), tcx, depth + 1) + { + return true; + } + false + } + _ => false, + } +} + +fn is_non_fuzzable_std_ty<'tcx>(ty: Ty<'tcx>, _tcx: TyCtxt<'tcx>) -> bool { + let name = format!("{}", ty); + match name.as_str() { + "core::alloc::LayoutError" => return true, + _ => {} + } + false +} + +const MAX_DEPTH: usize = 64; +pub fn is_fuzzable_ty<'tcx>(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>, depth: usize) -> bool { + if depth > MAX_DEPTH { + return false; + } + + if is_fuzzable_std_ty(ty, tcx, depth + 1) { + return true; + } + + if is_non_fuzzable_std_ty(ty, tcx) { + return false; + } + + match ty.kind() { + // Basical data type + TyKind::Bool + | TyKind::Char + | TyKind::Int(_) + | TyKind::Uint(_) + | TyKind::Float(_) + | TyKind::Str => true, + + // Infer + TyKind::Infer( + ty::InferTy::IntVar(_) + | ty::InferTy::FreshIntTy(_) + | ty::InferTy::FloatVar(_) + | ty::InferTy::FreshFloatTy(_), + ) => true, + + // Reference, Array, Slice + TyKind::Ref(_, inner_ty, _) | TyKind::Slice(inner_ty) => { + is_fuzzable_ty(inner_ty.peel_refs(), tcx, depth + 1) + } + + TyKind::Array(inner_ty, const_) => { + if const_.try_to_value().is_none() { + return false; + } + is_fuzzable_ty(inner_ty.peel_refs(), tcx, depth + 1) + } + + // Tuple + TyKind::Tuple(tys) => tys + .iter() + .all(|inner_ty| is_fuzzable_ty(inner_ty.peel_refs(), tcx, depth + 1)), + + // ADT + TyKind::Adt(adt_def, args) => { + if adt_def.is_union() { + return false; + } + + if adt_def.is_variant_list_non_exhaustive() { + return false; + } + + // if adt contain region, then we consider it non-fuzzable + if args.iter().any(|arg| arg.as_region().is_some()) { + return false; + } + + // if any field is not public or not fuzzable, then we consider it non-fuzzable + if !adt_def.all_fields().all(|field| { + field.vis.is_public() && is_fuzzable_ty(field.ty(tcx, args), tcx, depth + 1) + }) { + return false; + } + + // empty enum cannot be instantiated + if adt_def.is_enum() && adt_def.variants().is_empty() { + return false; + } + + true + } + + // 其他类型默认不可 Fuzz + _ => false, + } +} diff --git a/rapx/src/analysis/core/api_dependency/graph/avail.rs b/rapx/src/analysis/core/api_dependency/graph/avail.rs index 3ec92f97..483d0648 100644 --- a/rapx/src/analysis/core/api_dependency/graph/avail.rs +++ b/rapx/src/analysis/core/api_dependency/graph/avail.rs @@ -1,8 +1,8 @@ -use super::super::visitor::FnVisitor; +use super::super::visit::FnVisitor; use super::ApiDependencyGraph; use super::Config; use super::dep_edge::DepEdge; -use super::dep_node::{DepNode, desc_str}; +use super::dep_node::DepNode; use super::transform::TransformKind; use super::ty_wrapper::TyWrapper; use super::utils; diff --git a/rapx/src/analysis/core/api_dependency/graph/dep_edge.rs b/rapx/src/analysis/core/api_dependency/graph/dep_edge.rs index 43bb8019..8dc04bc4 100644 --- a/rapx/src/analysis/core/api_dependency/graph/dep_edge.rs +++ b/rapx/src/analysis/core/api_dependency/graph/dep_edge.rs @@ -1,11 +1,13 @@ use rustc_middle::ty::{self, Mutability, Ty}; +use serde::Serialize; use std::{fmt::Display, sync::OnceLock}; use super::transform::TransformKind; -#[derive(Clone, Copy, Eq, PartialEq, Debug)] +#[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize)] +#[serde(tag = "type")] pub enum DepEdge { - Arg(usize), + Arg { no: usize }, Ret, Transform(TransformKind), } @@ -13,7 +15,7 @@ pub enum DepEdge { impl Display for DepEdge { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - DepEdge::Arg(no) => write!(f, "{}", no), + DepEdge::Arg { no } => write!(f, "{}", no), DepEdge::Ret => write!(f, "r"), DepEdge::Transform(kind) => write!(f, "Transform({})", kind), } @@ -22,7 +24,7 @@ impl Display for DepEdge { impl DepEdge { pub fn arg(no: usize) -> DepEdge { - DepEdge::Arg(no) + DepEdge::Arg { no } } pub fn ret() -> DepEdge { DepEdge::Ret diff --git a/rapx/src/analysis/core/api_dependency/graph/dep_node.rs b/rapx/src/analysis/core/api_dependency/graph/dep_node.rs index 1613ff3c..ac1e1d85 100644 --- a/rapx/src/analysis/core/api_dependency/graph/dep_node.rs +++ b/rapx/src/analysis/core/api_dependency/graph/dep_node.rs @@ -8,24 +8,12 @@ use rustc_middle::{ use rustc_hir::def_id::DefId; -#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] -enum IntrinsicKind { - Borrow, -} - #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub enum DepNode<'tcx> { Api(DefId, ty::GenericArgsRef<'tcx>), Ty(TyWrapper<'tcx>), } -pub fn desc_str<'tcx>(node: DepNode<'tcx>, tcx: TyCtxt<'tcx>) -> String { - match node { - DepNode::Api(def_id, args) => tcx.def_path_str_with_args(def_id, args), - DepNode::Ty(ty) => ty.desc_str(tcx), - } -} - impl<'tcx> DepNode<'tcx> { pub fn api(id: impl IntoQueryParam, args: ty::GenericArgsRef<'tcx>) -> DepNode<'tcx> { DepNode::Api(id.into_query_param(), args) @@ -62,4 +50,11 @@ impl<'tcx> DepNode<'tcx> { } } } + + pub fn desc_str(&self, tcx: TyCtxt<'tcx>) -> String { + match self { + DepNode::Api(def_id, args) => tcx.def_path_str_with_args(*def_id, *args), + DepNode::Ty(ty) => ty.desc_str(tcx), + } + } } diff --git a/rapx/src/analysis/core/api_dependency/graph/dump.rs b/rapx/src/analysis/core/api_dependency/graph/dump.rs new file mode 100644 index 00000000..43e3a58c --- /dev/null +++ b/rapx/src/analysis/core/api_dependency/graph/dump.rs @@ -0,0 +1,177 @@ +use super::dep_edge::DepEdge; +use super::dep_node::DepNode; +use crate::analysis::core::api_dependency::ApiDependencyGraph; +use crate::analysis::utils::path::{PathResolver, get_path_resolver}; +use crate::utils::fs::rap_create_file; +use anyhow::Result; +use itertools::Itertools; +use petgraph::Graph; +use petgraph::dot; +use petgraph::graph::NodeIndex; +use rustc_middle::ty::{self, Ty, TyCtxt, TyKind}; +use rustc_middle::ty::{GenericArgsRef, List}; +use serde::{Serialize, ser::SerializeMap}; +use serde_yaml; +use std::io::Write; +use std::mem::MaybeUninit; +use std::path::Path; + +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "type")] +enum NodeInfo { + Api { + path: String, + generic_args: Vec, + }, + Ty { + path: String, + }, +} + +#[derive(Debug, Clone, Serialize)] +struct EdgeInfo { + from: usize, + to: usize, + kind: DepEdge, +} + +impl<'tcx> ApiDependencyGraph<'tcx> { + pub fn dump_to_file(&self, path: impl AsRef) -> Result<()> { + let dump_path = path.as_ref(); + let file = std::fs::File::create(path.as_ref())?; + match dump_path.extension() { + Some(ext) if ext == "json" => { + serde_json::to_writer_pretty(file, self)?; + } + Some(ext) if ext == "dot" => { + let dot_str = self.dump_to_dot(); + std::fs::write(dump_path, dot_str)?; + } + Some(ext) if ext == "yml" || ext == "yaml" => { + serde_yaml::to_writer(file, self)?; + } + _ => { + rap_info!( + "Unsupported dump format: {:?}, skip dumping API graph", + dump_path.extension() + ); + } + } + rap_info!("Dump API dependency graph to {}", dump_path.display()); + Ok(()) + } +} + +impl<'tcx> DepNode<'tcx> { + fn to_node_info(&self, resolver: &PathResolver<'tcx>) -> NodeInfo { + match self { + DepNode::Api(def_id, args) => NodeInfo::Api { + path: resolver.path_str_with_args(*def_id, ty::GenericArgs::empty()), + generic_args: args + .iter() + .map(|arg| resolver.generic_arg_str(arg)) + .collect_vec(), + }, + DepNode::Ty(ty_wrapper) => NodeInfo::Ty { + path: resolver.ty_str(ty_wrapper.ty()), + }, + } + } +} + +impl DepEdge { + fn to_edge_info(&self, from: usize, to: usize) -> EdgeInfo { + EdgeInfo { + from, + to, + kind: *self, + } + } +} + +// schema: +// nodes: [{type: "api"/"type", path}] +// edges: [{from,to,type}] +impl<'tcx> Serialize for ApiDependencyGraph<'tcx> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut map = serializer.serialize_map(Some(2))?; + let resolver = get_path_resolver(self.tcx); + + let node_len = self.graph.node_count(); + let mut nodes = Box::<[NodeInfo]>::new_uninit_slice(node_len); + let mut initialized_count = 0usize; + + for (expected_offset, node_index) in self.graph.node_indices().enumerate() { + let offset = node_index.index(); + assert!(offset < node_len, "node index out of bounds"); + + let node = self + .graph + .node_weight(node_index) + .expect("node index from node_indices must exist"); + nodes[offset].write(node.to_node_info(&resolver)); + initialized_count += 1; + } + + assert_eq!( + initialized_count, node_len, + "all node slots must be initialized" + ); + + // SAFETY: we assert that indices are contiguous and in-bounds, and we initialize + // each slot exactly once, so every element is fully initialized here. + let nodes = unsafe { nodes.assume_init() }.into_vec(); + + let mut edges = Vec::with_capacity(self.graph.edge_count()); + for edge_index in self.graph.edge_indices() { + let (from, to) = self + .graph + .edge_endpoints(edge_index) + .expect("edge index from edge_indices must have endpoints"); + let edge = self + .graph + .edge_weight(edge_index) + .expect("edge index from edge_indices must exist"); + edges.push(edge.to_edge_info(from.index(), to.index())); + } + + map.serialize_entry("nodes", &nodes)?; + map.serialize_entry("edges", &edges)?; + map.end() + } +} + +impl<'tcx> ApiDependencyGraph<'tcx> { + pub fn dump_to_dot(&self) -> String { + let tcx = self.tcx; + let get_edge_attr = + |graph: &Graph, DepEdge>, + edge_ref: petgraph::graph::EdgeReference| { + let color = match edge_ref.weight() { + DepEdge::Arg { .. } | DepEdge::Ret => "black", + DepEdge::Transform(_) => "darkorange", + }; + format!("label=\"{}\", color = {}", edge_ref.weight(), color) + }; + let get_node_attr = |graph: &Graph, DepEdge>, + node_ref: (NodeIndex, &DepNode<'tcx>)| { + format!("label={:?}, ", node_ref.1.desc_str(tcx)) + + match node_ref.1 { + DepNode::Api(..) => "color = blue", + DepNode::Ty(_) => "color = red", + } + + ", shape=box" + }; + + let dot = dot::Dot::with_attr_getters( + &self.graph, + &[dot::Config::NodeNoLabel, dot::Config::EdgeNoLabel], + &get_edge_attr, + &get_node_attr, + ); + format!("{:?}", dot) + } +} diff --git a/rapx/src/analysis/core/api_dependency/graph/mod.rs b/rapx/src/analysis/core/api_dependency/graph/mod.rs index 7a9244af..c7059840 100644 --- a/rapx/src/analysis/core/api_dependency/graph/mod.rs +++ b/rapx/src/analysis/core/api_dependency/graph/mod.rs @@ -1,20 +1,23 @@ pub mod avail; pub mod dep_edge; pub mod dep_node; +mod dump; mod resolve; -mod serialize; +mod std_tys; pub mod transform; mod ty_wrapper; use super::Config; use super::utils; -use super::visitor::FnVisitor; +use super::visit::{self, FnVisitor}; +use crate::analysis::core::api_dependency::is_fuzzable_ty; use crate::analysis::utils::def_path::path_str_def_id; use crate::rap_debug; use crate::rap_trace; use crate::utils::fs::rap_create_file; +use bit_set::BitSet; pub use dep_edge::DepEdge; -pub use dep_node::{DepNode, desc_str}; +pub use dep_node::DepNode; use petgraph::Direction; use petgraph::Graph; use petgraph::dot; @@ -40,16 +43,29 @@ pub struct ApiDependencyGraph<'tcx> { node_indices: HashMap, NodeIndex>, ty_nodes: Vec, api_nodes: Vec, - all_apis: HashSet, tcx: TyCtxt<'tcx>, } +#[derive(Copy, Clone, Debug)] pub struct Statistics { - pub api_count: usize, + pub num_api: usize, + pub num_generic_api: usize, pub type_count: usize, pub edge_cnt: usize, } +impl Statistics { + pub fn info(&self) { + rap_info!( + "API Graph contains {} API nodes, {} generic API nodes, {} type nodes, {} edges", + self.num_api, + self.num_generic_api, + self.type_count, + self.edge_cnt + ); + } +} + impl<'tcx> ApiDependencyGraph<'tcx> { pub fn new(tcx: TyCtxt<'tcx>) -> ApiDependencyGraph<'tcx> { ApiDependencyGraph { @@ -58,7 +74,6 @@ impl<'tcx> ApiDependencyGraph<'tcx> { ty_nodes: Vec::new(), api_nodes: Vec::new(), tcx, - all_apis: HashSet::new(), } } @@ -70,28 +85,34 @@ impl<'tcx> ApiDependencyGraph<'tcx> { self.ty_nodes.len() } - pub fn api_at(&self, idx: usize) -> (DefId, ty::GenericArgsRef<'tcx>) { + pub fn api_node_at(&self, idx: usize) -> DepNode<'tcx> { let index = self.api_nodes[idx]; - self.graph[index].expect_api() + self.graph[index] } fn tcx(&self) -> TyCtxt<'tcx> { self.tcx } - pub fn build(&mut self, config: Config) { + pub fn build(&mut self, config: &Config) { let tcx = self.tcx(); - let mut fn_visitor = FnVisitor::new(self, config, tcx); + let mut visitor = FnVisitor::new(config.visit_config, tcx); // 1. collect APIs - tcx.hir_visit_all_item_likes_in_crate(&mut fn_visitor); - self.all_apis = fn_visitor.apis().into_iter().collect(); - // add auxillary API from std - // self.add_auxiliary_api(); + tcx.hir_visit_all_item_likes_in_crate(&mut visitor); - // 2. resolve generic API to monomorphic API + // 2. add non generic APIs + visitor.non_generic_apis().iter().for_each(|&fn_did| { + self.add_identity_api(fn_did); + }); + + // 3. resolve generic API to monomorphic API if config.resolve_generic { - self.resolve_generic_api(); + self.resolve_generic_api( + visitor.non_generic_apis(), + visitor.generic_apis(), + config.max_generic_search_iteration, + ); } else { self.update_transform_edges(); } @@ -102,13 +123,19 @@ impl<'tcx> ApiDependencyGraph<'tcx> { } pub fn statistics(&self) -> Statistics { - let mut api_cnt = 0; + let mut num_api = 0; + let mut num_generic_api = 0; let mut ty_cnt = 0; let mut edge_cnt = 0; for node in self.graph.node_indices() { match self.graph[node] { - DepNode::Api(..) => api_cnt += 1, + DepNode::Api(did, ..) => { + num_api += 1; + if utils::fn_requires_monomorphization(did, self.tcx) { + num_generic_api += 1; + } + } DepNode::Ty(_) => ty_cnt += 1, } } @@ -118,7 +145,8 @@ impl<'tcx> ApiDependencyGraph<'tcx> { } Statistics { - api_count: api_cnt, + num_api, + num_generic_api, type_count: ty_cnt, edge_cnt, } @@ -147,6 +175,15 @@ impl<'tcx> ApiDependencyGraph<'tcx> { } } + pub fn get_node_from_index(&self, index: NodeIndex) -> DepNode<'tcx> { + self.graph[index] + } + + pub fn get_index_by_ty(&self, ty: Ty<'tcx>) -> Option { + let ty_wrapper = TyWrapper::from(ty); + self.get_index(DepNode::Ty(ty_wrapper)) + } + pub fn get_index(&self, node: DepNode<'tcx>) -> Option { self.node_indices.get(&node).map(|index| *index) } @@ -161,15 +198,14 @@ impl<'tcx> ApiDependencyGraph<'tcx> { } } - pub fn add_generic_api(&mut self, fn_did: DefId) -> bool { + pub fn add_identity_api(&mut self, fn_did: DefId) -> bool { let args = ty::GenericArgs::identity_for_item(self.tcx, fn_did); if !self.add_api(fn_did, args) { return false; } - let api_node = self.get_or_create_index(DepNode::api(fn_did, args)); - let binder_fn_sig = self.tcx.fn_sig(fn_did).instantiate_identity(); + self.get_or_create_index(DepNode::api(fn_did, args)); true } @@ -223,13 +259,79 @@ impl<'tcx> ApiDependencyGraph<'tcx> { } } - pub fn estimate_coverage_with( + pub fn depth_map(&self) -> HashMap, usize> { + let mut map = HashMap::new(); + let mut visited = BitSet::with_capacity(self.graph.node_count()); + + // initialize worklist with start node (indegree is zero) + let mut worklist = VecDeque::from_iter(self.graph.node_indices().filter(|index| { + let cond = self.is_start_node_index(*index); + if cond { + visited.insert(index.index()); + map.insert(self.get_node_from_index(*index), 0); + } + cond + })); + + rap_trace!("[depth_map] initial worklist = {:?}", worklist); + + const LARGE_ENOUGH: usize = 0xffffffff; + + // initialize queue with fuzzable type + while let Some(current) = worklist.pop_front() { + let node = self.get_node_from_index(current); + + if !map.contains_key(&node) { + // depth: Ty = min(prev_ty), Api = sum(arg_ty) + 1 + let depth = match node { + DepNode::Ty(_) => self + .graph + .neighbors_directed(current, Direction::Incoming) + .map(|prev| { + let prev_node = &self.get_node_from_index(prev); + map.get(prev_node).copied().unwrap_or(LARGE_ENOUGH) + }) + .min() + .unwrap_or(0), + DepNode::Api(..) => { + self.graph + .neighbors_directed(current, Direction::Incoming) + .map(|prev| { + let prev_node = &self.get_node_from_index(prev); + map.get(prev_node).copied().unwrap_or(LARGE_ENOUGH) + }) + .sum::() + + 1 + } + }; + map.insert(node, depth); + } + + for next in self.graph.neighbors(current) { + let is_reachable = match self.graph[next] { + DepNode::Ty(_) => true, + DepNode::Api(..) => self + .graph + .neighbors_directed(next, petgraph::Direction::Incoming) + .all(|nbor| visited.contains(nbor.index())), + }; + + if is_reachable && visited.insert(next.index()) { + rap_trace!("[depth_map] add {:?} to worklist", next); + worklist.push_back(next); + } + } + } + + map + } + + pub fn traverse_covered_api_with( &self, - tcx: TyCtxt<'tcx>, f_cover: &mut impl FnMut(DefId), f_total: &mut impl FnMut(DefId), ) { - let mut reachable = vec![false; self.graph.node_count()]; + let mut visited = BitSet::with_capacity(self.graph.node_count()); for index in self.graph.node_indices() { if let DepNode::Api(did, _) = self.graph[index] { @@ -240,7 +342,7 @@ impl<'tcx> ApiDependencyGraph<'tcx> { // initialize worklist with start node (indegree is zero) let mut worklist = VecDeque::from_iter(self.graph.node_indices().filter(|index| { if self.is_start_node_index(*index) { - reachable[index.index()] = true; + visited.insert(index.index()); true } else { false @@ -256,38 +358,56 @@ impl<'tcx> ApiDependencyGraph<'tcx> { } for next in self.graph.neighbors(index) { - if reachable[next.index()] { - continue; - } - if match self.graph[next] { + let is_reachable = match self.graph[next] { DepNode::Ty(_) => true, - DepNode::Api(..) => { - if self - .graph - .neighbors_directed(next, petgraph::Direction::Incoming) - .all(|nbor| reachable[nbor.index()]) - { - true - } else { - false - } - } - } { - // rap_trace!("[estimate_coverage] add {:?} to worklist", next); - reachable[next.index()] = true; + DepNode::Api(..) => self + .graph + .neighbors_directed(next, petgraph::Direction::Incoming) + .all(|nbor| visited.contains(nbor.index())), + }; + if is_reachable && visited.insert(next.index()) { + rap_trace!("[traverse_covered_api] add {:?} to worklist", next); worklist.push_back(next); } } } } + fn recache(&mut self) { + self.node_indices.clear(); + self.ty_nodes.clear(); + self.api_nodes.clear(); + for idx in self.graph.node_indices() { + let node = &self.graph[idx]; + self.node_indices.insert(node.clone(), idx); + match node { + DepNode::Api(..) => self.api_nodes.push(idx), + DepNode::Ty(..) => self.ty_nodes.push(idx), + _ => {} + } + } + } + + pub fn uncovered_api(&self) -> Vec { + let mut covered = HashSet::new(); + let mut total = HashSet::new(); + self.traverse_covered_api_with( + &mut |did| { + covered.insert(did); + }, + &mut |did| { + total.insert(did); + }, + ); + total.difference(&covered).copied().collect() + } + /// `estimate_coverage` treat each API as the distinct API. /// For example, if `foo`, `foo` are covered, this API return (2, 2). - pub fn estimate_coverage(&mut self) -> (usize, usize) { + pub fn estimate_coverage(&self) -> (usize, usize) { let mut num_total = 0; let mut num_estimate = 0; - self.estimate_coverage_with( - self.tcx, + self.traverse_covered_api_with( &mut |did| { num_estimate += 1; }, @@ -300,11 +420,10 @@ impl<'tcx> ApiDependencyGraph<'tcx> { /// `estimate_coverage_distinct` treat mono API as the original generic API. /// For example, if `foo`, `foo` are covered, this API return (1, 1). - pub fn estimate_coverage_distinct(&mut self) -> (usize, usize) { + pub fn estimate_coverage_distinct(&self) -> (usize, usize) { let mut total = HashSet::new(); let mut estimate = HashSet::new(); - self.estimate_coverage_with( - self.tcx, + self.traverse_covered_api_with( &mut |did| { estimate.insert(did); }, @@ -315,34 +434,32 @@ impl<'tcx> ApiDependencyGraph<'tcx> { (estimate.len(), total.len()) } - pub fn dump_to_dot>(&self, path: P, tcx: TyCtxt<'tcx>) { - let get_edge_attr = - |graph: &Graph, DepEdge>, - edge_ref: petgraph::graph::EdgeReference| { - let color = match edge_ref.weight() { - DepEdge::Arg(_) | DepEdge::Ret => "black", - DepEdge::Transform(_) => "darkorange", - }; - format!("label=\"{}\", color = {}", edge_ref.weight(), color) - }; - let get_node_attr = |graph: &Graph, DepEdge>, - node_ref: (NodeIndex, &DepNode<'tcx>)| { - format!("label={:?}, ", desc_str(node_ref.1.clone(), tcx)) - + match node_ref.1 { - DepNode::Api(..) => "color = blue", - DepNode::Ty(_) => "color = red", + pub fn dump_apis>(&self, path: P) { + let tcx = self.tcx; + let mut file = rap_create_file(path, "can not create api file"); + + self.graph + .node_indices() + .for_each(|index| match self.graph[index] { + DepNode::Api(did, args) => { + writeln!( + file, + "API,\t{},\tis_fuzzable = {}", + tcx.def_path_str_with_args(did, args), + utils::is_fuzzable_api(did, args, tcx) + ) + .expect("fail when writing data to api file"); } - + ", shape=box" - }; - - let dot = dot::Dot::with_attr_getters( - &self.graph, - &[dot::Config::NodeNoLabel, dot::Config::EdgeNoLabel], - &get_edge_attr, - &get_node_attr, - ); - let mut file = rap_create_file(path, "can not create dot file"); - write!(&mut file, "{:?}", dot).expect("fail when writing data to dot file"); - // println!("{:?}", dot); + DepNode::Ty(ty) => { + let ty = ty.ty(); + writeln!( + file, + "TYPE,\t{},\tis_fuzzable = {}", + ty, + is_fuzzable_ty(ty, tcx) + ) + .expect("fail when writing data to api file"); + } + }); } } diff --git a/rapx/src/analysis/core/api_dependency/graph/resolve.rs b/rapx/src/analysis/core/api_dependency/graph/resolve.rs index 899313fe..dd372095 100644 --- a/rapx/src/analysis/core/api_dependency/graph/resolve.rs +++ b/rapx/src/analysis/core/api_dependency/graph/resolve.rs @@ -1,24 +1,28 @@ use super::Config; use super::dep_edge::DepEdge; -use super::dep_node::{DepNode, desc_str}; +use super::dep_node::DepNode; use super::transform::TransformKind; use super::ty_wrapper::TyWrapper; use crate::analysis::core::api_dependency::ApiDependencyGraph; -use crate::analysis::core::api_dependency::mono::Mono; -use crate::analysis::core::api_dependency::utils::{is_fuzzable_ty, ty_complexity}; -use crate::analysis::core::api_dependency::visitor::FnVisitor; +use crate::analysis::core::api_dependency::graph::std_tys; +use crate::analysis::core::api_dependency::mono::{Mono, get_mono_complexity}; +use crate::analysis::core::api_dependency::utils::{ + fn_requires_monomorphization, is_fuzzable_ty, ty_complexity, +}; +use crate::analysis::core::api_dependency::visit::FnVisitor; use crate::analysis::core::api_dependency::{mono, utils}; +use crate::analysis::utils::def_path::path_str_def_id; use crate::utils::fs::rap_create_file; use crate::{rap_debug, rap_info, rap_trace}; use petgraph::Direction::{self, Incoming}; use petgraph::Graph; use petgraph::dot; use petgraph::graph::NodeIndex; -use petgraph::visit::{NodeIndexable, Visitable}; +use petgraph::visit::{EdgeRef, NodeIndexable, Visitable}; use rand::Rng; use rustc_hir::def_id::DefId; use rustc_middle::ty::{self, GenericArgsRef, TraitRef, Ty, TyCtxt}; -use rustc_span::sym::require; +use rustc_span::sym::{self, require}; use std::collections::HashMap; use std::collections::HashSet; use std::collections::VecDeque; @@ -27,6 +31,8 @@ use std::io::Write; use std::path::Path; use std::time; +const MAX_TY_COMPLX: usize = 5; + fn add_return_type_if_reachable<'tcx>( fn_did: DefId, args: &[ty::GenericArg<'tcx>], @@ -134,23 +140,40 @@ impl<'tcx> TypeCandidates<'tcx> { pub fn add_prelude_tys(&mut self) { let tcx = self.tcx; - let prelude_tys = [ + + let primitive_tys = [ tcx.types.bool, tcx.types.char, tcx.types.f32, - tcx.types.f64, tcx.types.i8, - tcx.types.i16, - tcx.types.i32, - tcx.types.i64, - tcx.types.isize, tcx.types.u8, - tcx.types.u16, + tcx.types.i32, tcx.types.u32, + tcx.types.i64, tcx.types.u64, + tcx.types.isize, tcx.types.usize, - Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, tcx.types.str_), ]; + + let mut prelude_tys = Vec::new(); + + prelude_tys.extend_from_slice(&primitive_tys); + // &str + prelude_tys.push(Ty::new_imm_ref( + tcx, + tcx.lifetimes.re_erased, + tcx.types.str_, + )); + // String + prelude_tys.push(Ty::new_adt( + self.tcx, + self.tcx.adt_def(self.tcx.lang_items().string().unwrap()), + ty::GenericArgs::empty(), + )); + for element_ty in &primitive_tys { + // Vec + prelude_tys.push(std_tys::std_vec(*element_ty, self.tcx)); + } prelude_tys.into_iter().for_each(|ty| { self.insert_all(ty); }); @@ -178,31 +201,48 @@ pub fn partion_generic_api<'tcx>( } impl<'tcx> ApiDependencyGraph<'tcx> { - pub fn resolve_generic_api(&mut self) { + pub fn resolve_generic_api( + &mut self, + non_generic_apis: &[DefId], + generic_apis: &[DefId], + max_iteration: usize, + ) { rap_info!("start resolving generic APIs"); - let generic_map = self.search_reachable_apis(); - self.prune_by_similarity(generic_map); + + // 1. Reachable generic API search + let generic_map = self.search_reachable_apis(non_generic_apis, generic_apis, max_iteration); + + self.add_mono_apis_from_map(&generic_map); + self.update_transform_edges(); + + rap_info!("finish resolving generic APIs"); + self.statistics().info(); + self.dump_to_file(Path::new("api_graph_unpruned.dot")); + + let reserved = self.prune_by_similarity(generic_map); + + let count = self.reserve_nodes(&reserved); + rap_info!("remove {} nodes by pruning", count); } - pub fn search_reachable_apis(&mut self) -> HashMap>> { + pub fn search_reachable_apis( + &mut self, + non_generic_apis: &[DefId], + generic_apis: &[DefId], + max_iteration: usize, + ) -> HashMap>> { let tcx = self.tcx; - let max_ty_complexity = 6; - let mut type_candidates = TypeCandidates::new(self.tcx, max_ty_complexity); + let mut type_candidates = TypeCandidates::new(self.tcx, MAX_TY_COMPLX); type_candidates.add_prelude_tys(); - // let mut num_reachable = 0; let mut generic_map: HashMap> = HashMap::new(); - - // initialize unreachable non generic API - let (mut unreachable_non_generic_api, generic_apis) = - partion_generic_api(&self.all_apis, tcx); + let mut unreachable_non_generic_api = Vec::from(non_generic_apis); rap_debug!("[resolve_generic] non_generic_api = {unreachable_non_generic_api:?}"); rap_debug!("[resolve_generic] generic_api = {generic_apis:?}"); let mut num_iter = 0; - let max_iteration = 10; loop { num_iter += 1; @@ -219,9 +259,10 @@ impl<'tcx> ApiDependencyGraph<'tcx> { } let mut current_tys = HashSet::new(); - // check whether there is any non-generic api that is reachable + + // check whether there is any non-generic reachable in this iteration. // if the api is reachable, add output type to reachble_tys, - // and remove fn_did from the set. + // and remove it from the set. unreachable_non_generic_api.retain(|fn_did| { !add_return_type_if_reachable( *fn_did, @@ -244,7 +285,7 @@ impl<'tcx> ApiDependencyGraph<'tcx> { let fn_sig = utils::fn_sig_with_generic_args(*fn_did, &mono.value, tcx); let output_ty = fn_sig.output(); if generic_map.entry(*fn_did).or_default().insert(mono) { - if !output_ty.is_unit() && ty_complexity(output_ty) <= max_ty_complexity { + if !output_ty.is_unit() && ty_complexity(output_ty) <= MAX_TY_COMPLX { current_tys.insert(output_ty); } } @@ -268,32 +309,116 @@ impl<'tcx> ApiDependencyGraph<'tcx> { let mono_cnt = generic_map.values().fold(0, |acc, monos| acc + monos.len()); - rap_debug!( - "# of reachable types: {}", - type_candidates.candidates().len() - ); - rap_debug!("# of mono APIs: {}", mono_cnt); + rap_debug!("# reachable types: {}", type_candidates.candidates().len()); + rap_debug!("# mono APIs: {}", mono_cnt); generic_map } - pub fn prune_by_similarity(&mut self, generic_map: HashMap>>) { + pub fn add_mono_apis_from_map(&mut self, generic_map: &HashMap>>) { + for (fn_did, mono_set) in generic_map { + for mono in mono_set { + let args = self.tcx.mk_args(&mono.value); + self.add_api(*fn_did, args); + } + } + } + + /// heuristic strategy: prioritize to reserve APIs that first arg of which is reachable. + /// This is based on that we want to reserve APIs that have the same Self type ASAP. + pub fn heuristic_select(&mut self, reserved: &mut [bool]) { + let mut worklist = VecDeque::new(); + let mut visited = vec![false; self.graph.node_count()]; + let mut impl_map: HashMap> = HashMap::new(); + let mut count_map: HashMap = HashMap::new(); + + // traverse from start node, if a node can achieve a reserved node, + // this node should be reserved + for node in self.graph.node_indices() { + if self.is_start_node_index(node) { + rap_trace!("initial node {:?}", self.graph[node]); + worklist.push_back(node); + } + } + + while let Some(node) = worklist.pop_front() { + if visited[node.index()] { + continue; + } + visited[node.index()] = true; + + match self.graph[node] { + DepNode::Api(fn_did, args) => { + if fn_requires_monomorphization(fn_did, self.tcx) { + let impl_entry = impl_map.entry(fn_did).or_default(); + let count_entry = count_map.entry(fn_did).or_default(); + let impls = mono::get_impls(self.tcx, fn_did, args); + let size = impls + .iter() + .fold(0, |cnt, did| cnt + (!impl_entry.contains(did)) as usize); + if *count_entry == 0 || size > 0 { + *count_entry += 1; + impls.iter().for_each(|did| { + impl_entry.insert(*did); + }); + reserved[node.index()] = true; + } + } + for neighbor in self.graph.neighbors(node) { + worklist.push_back(neighbor); + } + } + DepNode::Ty(..) => { + for edge in self.graph.edges_directed(node, Direction::Outgoing) { + let weight = self.graph.edge_weight(edge.id()).unwrap(); + if let DepEdge::Transform(_) | DepEdge::Arg { no: 0 } = weight { + worklist.push_back(edge.target()); + } + } + } + } + + if reserved[node.index()] { + rap_debug!( + "[propagate_reserved] reserve: {:?}", + self.graph.node_weight(node).unwrap() + ); + } + } + } + + pub fn minimal_select( + &mut self, + reserved: &mut [bool], + generic_map: &HashMap>>, + ) { let mut rng = rand::rng(); let mut reserved_map: HashMap, bool)>> = HashMap::new(); // transform into reserved map for (fn_did, mono_set) in generic_map { - let entry = reserved_map.entry(fn_did).or_default(); + let entry = reserved_map.entry(*fn_did).or_default(); mono_set.into_iter().for_each(|mono| { let args = self.tcx.mk_args(&mono.value); - self.add_api(fn_did, args); entry.push((args, false)); }); } - // add transform edges - self.update_transform_edges(); + // add all monomorphic APIs to API Graph, but select minimal set cover to be reserved + for (fn_did, monos) in &mut reserved_map { + select_minimal_set_cover(self.tcx, *fn_did, monos, &mut rng); + for (args, r) in monos { + if *r { + let idx = self.get_index(DepNode::Api(*fn_did, args)).unwrap(); + reserved[idx.index()] = true; + } + } + } + } - self.dump_to_dot(Path::new("api_graph_unpruned.dot"), self.tcx); + pub fn prune_by_similarity( + &mut self, + generic_map: HashMap>>, + ) -> Vec { let (estimate, total) = self.estimate_coverage_distinct(); rap_info!( "estimate API coverage before pruning: {:.2} ({}/{})", @@ -315,16 +440,11 @@ impl<'tcx> ApiDependencyGraph<'tcx> { } } - // add all monomorphic APIs to API Graph, but select minimal set cover to be reserved - for (fn_did, monos) in &mut reserved_map { - select_minimal_set_cover(self.tcx, *fn_did, monos, &mut rng); - for (args, r) in monos { - if *r { - let idx = self.get_index(DepNode::Api(*fn_did, args)).unwrap(); - reserved[idx.index()] = true; - } - } - } + // minimal set cover strategy + // self.minimal_select(&mut reserved, &generic_map); + + // heuristic strategy + self.heuristic_select(&mut reserved); // traverse from start node, if a node can achieve a reserved node, // this node should be reserved as well @@ -342,6 +462,10 @@ impl<'tcx> ApiDependencyGraph<'tcx> { } } + reserved + } + + pub fn reserve_nodes(&mut self, reserved: &[bool]) -> usize { let mut count = 0; for idx in (0..self.graph.node_count()).rev() { if !reserved[idx] { @@ -352,29 +476,7 @@ impl<'tcx> ApiDependencyGraph<'tcx> { } } self.recache(); - rap_info!("remove {} nodes by pruning", count); - let (estimate, total) = self.estimate_coverage_distinct(); - rap_info!( - "estimate API coverage after pruning: {:.2} ({}/{})", - estimate as f64 / total as f64, - estimate, - total - ); - } - - fn recache(&mut self) { - self.node_indices.clear(); - self.ty_nodes.clear(); - self.api_nodes.clear(); - for idx in self.graph.node_indices() { - let node = &self.graph[idx]; - self.node_indices.insert(node.clone(), idx); - match node { - DepNode::Api(..) => self.api_nodes.push(idx), - DepNode::Ty(..) => self.ty_nodes.push(idx), - _ => {} - } - } + count } pub fn propagate_reserved( @@ -386,7 +488,9 @@ impl<'tcx> ApiDependencyGraph<'tcx> { visited[node.index()] = true; match self.graph[node] { - DepNode::Api(..) => { + // Api should be reserved if must_reserve is true, + // or at least one its neighbor is reserved + DepNode::Api(fn_did, args) => { for neighbor in self.graph.neighbors(node) { if !visited[neighbor.index()] { reserved[node.index()] |= @@ -394,7 +498,10 @@ impl<'tcx> ApiDependencyGraph<'tcx> { } } } + + // Ty should be reserved if at least one its neighbor is reserved DepNode::Ty(..) => { + // self.graph.edges_directed(node, dir) for neighbor in self.graph.neighbors(node) { if !visited[neighbor.index()] { self.propagate_reserved(neighbor, visited, reserved); @@ -422,31 +529,31 @@ fn select_minimal_set_cover<'tcx, 'a>( ) { rap_debug!("select minimal set for: {}", tcx.def_path_str(fn_did)); let mut impl_vec = Vec::new(); + let mut cmplx_vec = Vec::new(); for (args, _) in monos.iter() { - let impls = mono::get_impls(tcx, fn_did, args); - impl_vec.push(impls); + impl_vec.push(mono::get_impls(tcx, fn_did, args)); + cmplx_vec.push(get_mono_complexity(args)); } let mut selected_cnt = 0; let mut complete = HashSet::new(); loop { let mut current_max = 0; + let mut current_cmplx = usize::MAX; let mut idx = 0; for i in 0..impl_vec.len() { - let impls = &impl_vec[i]; - let size = impls.iter().fold(0, |cnt, did| { - if !complete.contains(did) { - cnt + 1 - } else { - cnt - } - }); - if size > current_max { + let size = impl_vec[i] + .iter() + .fold(0, |cnt, did| cnt + (!complete.contains(did)) as usize); + + if size > current_max || (size == current_max && cmplx_vec[i] < current_cmplx) { current_max = size; + current_cmplx = cmplx_vec[i]; idx = i; } } - if current_max == 0 { + // though maybe all impls is empty, we have to select at least one + if current_max == 0 && selected_cnt > 0 { break; } selected_cnt += 1; @@ -457,9 +564,9 @@ fn select_minimal_set_cover<'tcx, 'a>( }); } - if selected_cnt == 0 { - let idx = rng.random_range(0..impl_vec.len()); - rap_debug!("random select: {:?}", monos[idx].0); - monos[idx].1 = true; - } + // if selected_cnt == 0 { + // let idx = rng.random_range(0..impl_vec.len()); + // rap_debug!("random select: {:?}", monos[idx].0); + // monos[idx].1 = true; + // } } diff --git a/rapx/src/analysis/core/api_dependency/graph/serialize.rs b/rapx/src/analysis/core/api_dependency/graph/serialize.rs deleted file mode 100644 index b9fab5c1..00000000 --- a/rapx/src/analysis/core/api_dependency/graph/serialize.rs +++ /dev/null @@ -1,78 +0,0 @@ -use super::dep_edge::DepEdge; -use super::dep_node::DepNode; -use crate::analysis::core::api_dependency::ApiDependencyGraph; -use serde::{ - Serialize, - ser::{SerializeMap, SerializeSeq}, -}; -use std::path::Path; - -#[derive(Serialize, Debug)] -struct NodeInfo { - id: usize, - kind: String, - path: String, - args: Vec, -} - -#[derive(Serialize, Debug)] -struct EdgeInfo { - id: usize, - kind: String, - from: usize, - to: usize, -} - -impl<'tcx> ApiDependencyGraph<'tcx> { - pub fn dump_to_json(&self, path: impl AsRef) -> std::io::Result<()> { - let file = std::fs::File::create(path)?; - serde_json::to_writer_pretty(file, self)?; - Ok(()) - } -} - -impl<'tcx> Serialize for ApiDependencyGraph<'tcx> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut map = serializer.serialize_map(Some(2))?; - let mut nodes = Vec::new(); - for index in self.graph.node_indices() { - let node_info = match self.graph[index] { - DepNode::Api(fn_did, args) => NodeInfo { - id: index.index(), - kind: "api".to_owned(), - path: self.tcx.def_path_str(fn_did), - args: args.iter().map(|arg| arg.to_string()).collect(), - }, - DepNode::Ty(ty) => NodeInfo { - id: index.index(), - kind: "type".to_owned(), - path: ty.ty().to_string(), - args: vec![], - }, - }; - nodes.push(node_info); - } - let mut edges = Vec::new(); - for index in self.graph.edge_indices() { - let kind = match self.graph[index] { - DepEdge::Arg(no) => "arg".to_owned(), - DepEdge::Ret => "ret".to_owned(), - DepEdge::Transform(kind) => format!("transform({})", kind), - }; - let (from, to) = self.graph.edge_endpoints(index).unwrap(); - let (from, to) = (from.index(), to.index()); - edges.push(EdgeInfo { - id: index.index(), - kind: "arg".to_owned(), - from, - to, - }); - } - map.serialize_entry("nodes", &nodes)?; - map.serialize_entry("edges", &edges)?; - map.end() - } -} diff --git a/rapx/src/analysis/core/api_dependency/graph/std_tys.rs b/rapx/src/analysis/core/api_dependency/graph/std_tys.rs new file mode 100644 index 00000000..ba66ba69 --- /dev/null +++ b/rapx/src/analysis/core/api_dependency/graph/std_tys.rs @@ -0,0 +1,19 @@ +use rustc_hir::LangItem; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_span::sym; + +pub fn std_vec<'tcx>(element_ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Ty<'tcx> { + let vec_def_id = tcx + .get_diagnostic_item(sym::Vec) + .expect("Vec should be defined in std"); + let alloc_def_id = tcx + .lang_items() + .global_alloc_ty() + .expect("Global should be defined in std"); + let alloc_ty = tcx.type_of(alloc_def_id).skip_binder(); + let args = tcx.mk_args(&[ + ty::GenericArg::from(element_ty), + ty::GenericArg::from(alloc_ty), + ]); + Ty::new_adt(tcx, tcx.adt_def(vec_def_id), args) +} diff --git a/rapx/src/analysis/core/api_dependency/graph/transform.rs b/rapx/src/analysis/core/api_dependency/graph/transform.rs index f6e473f9..086acf0e 100644 --- a/rapx/src/analysis/core/api_dependency/graph/transform.rs +++ b/rapx/src/analysis/core/api_dependency/graph/transform.rs @@ -2,6 +2,7 @@ use super::dep_edge::DepEdge; use super::{ApiDependencyGraph, DepNode, TyWrapper}; use petgraph::graph::NodeIndex; use rustc_middle::ty::{self}; +use serde::Serialize; use std::fmt::Display; static ALL_TRANSFORMKIND: [TransformKind; 2] = [ @@ -32,6 +33,15 @@ impl Display for TransformKind { } } +impl Serialize for TransformKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + impl<'tcx> ApiDependencyGraph<'tcx> { pub fn update_transform_edges(&mut self) { for node_index in self.graph.node_indices() { @@ -69,3 +79,30 @@ impl<'tcx> ApiDependencyGraph<'tcx> { ret } } + +#[cfg(test)] +mod tests { + use super::TransformKind; + use rustc_middle::ty; + + #[test] + fn serialize_ref_not_matches_expected() { + let kind = TransformKind::Ref(ty::Mutability::Not); + let serialized = serde_json::to_string(&kind).expect("serialize TransformKind::Ref(Not)"); + assert_eq!(serialized, "\"&T\""); + } + + #[test] + fn serialize_ref_mut_matches_expected() { + let kind = TransformKind::Ref(ty::Mutability::Mut); + let serialized = serde_json::to_string(&kind).expect("serialize TransformKind::Ref(Mut)"); + assert_eq!(serialized, "\"&mut T\""); + } + + #[test] + fn serialize_unwrap_matches_expected() { + let kind = TransformKind::Unwrap; + let serialized = serde_json::to_string(&kind).expect("serialize TransformKind::Unwrap"); + assert_eq!(serialized, "\"Unwrap\""); + } +} diff --git a/rapx/src/analysis/core/api_dependency/mod.rs b/rapx/src/analysis/core/api_dependency/mod.rs index b6c4c89e..8fd20a3e 100644 --- a/rapx/src/analysis/core/api_dependency/mod.rs +++ b/rapx/src/analysis/core/api_dependency/mod.rs @@ -1,3 +1,4 @@ +mod fuzzable; /// NOTE: This analysis module is currently under development and is highly unstable. /// The #[allow(unused)] attribute is applied to suppress excessive lint warnings. /// Once the analysis stabilizes, this marker should be removed. @@ -7,33 +8,43 @@ pub mod graph; mod mono; mod utils; #[allow(unused)] -mod visitor; +mod visit; use crate::analysis::Analysis; pub use graph::ApiDependencyGraph; pub use graph::{DepEdge, DepNode}; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_hir::def_id::LOCAL_CRATE; use rustc_middle::ty::TyCtxt; -pub use utils::is_fuzzable_ty; +use serde::Serialize; +use std::path::PathBuf; +pub use utils::{is_def_id_public, is_fuzzable_ty}; +pub use visit::Config as VisitConfig; -#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Default)] +#[derive(Debug, Clone, Serialize)] +pub struct StatsWithCoverage { + pub num_apis: usize, + pub num_generic_apis: usize, + pub num_covered_apis: usize, + pub num_covered_generic_apis: usize, +} + +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct Config { - pub pub_only: bool, pub resolve_generic: bool, - pub ignore_const_generic: bool, + pub visit_config: visit::Config, + pub max_generic_search_iteration: usize, + pub dump: Option, } -pub fn is_def_id_public(fn_def_id: impl Into, tcx: TyCtxt<'_>) -> bool { - let fn_def_id: DefId = fn_def_id.into(); - let local_id = fn_def_id.expect_local(); - rap_trace!( - "vis: {:?} (path: {}) => {:?}", - fn_def_id, - tcx.def_path_str(fn_def_id), - tcx.effective_visibilities(()).effective_vis(local_id) - ); - tcx.effective_visibilities(()).is_directly_public(local_id) - // || tcx.effective_visibilities(()).is_exported(local_id) +impl Default for Config { + fn default() -> Self { + Config { + resolve_generic: true, + visit_config: VisitConfig::default(), + max_generic_search_iteration: 10, + dump: None, + } + } } pub trait ApiDependencyAnalysis<'tcx> { @@ -64,41 +75,67 @@ impl<'tcx> Analysis for ApiDependencyAnalyzer<'tcx> { fn run(&mut self) { let local_crate_name = self.tcx.crate_name(LOCAL_CRATE); let local_crate_type = self.tcx.crate_types()[0]; - let config = self.config; - rap_debug!( + rap_info!( "Build API dependency graph on {} ({}), config = {:?}", local_crate_name.as_str(), local_crate_type, - config, + self.config, ); - let mut api_graph = ApiDependencyGraph::new(self.tcx); - api_graph.build(config); + let api_graph = &mut self.api_graph; + api_graph.build(&self.config); - let (estimate, total) = api_graph.estimate_coverage(); + let stats = api_graph.statistics(); + stats.info(); + let mut num_covered_apis = 0; + let mut num_covered_generic_apis = 0; + let mut num_total = 0; - let statistics = api_graph.statistics(); - // print all statistics - rap_info!( - "API Graph contains {} API nodes, {} type nodes, {} edges", - statistics.api_count, - statistics.type_count, - statistics.edge_cnt + api_graph.traverse_covered_api_with( + &mut |did| { + num_covered_apis += 1; + if utils::fn_requires_monomorphization(did, self.tcx) { + num_covered_generic_apis += 1; + } + }, + &mut |_| { + num_total += 1; + }, ); + + rap_info!("uncovered APIs: {:?}", api_graph.uncovered_api()); + rap_info!( - "estimate coverage: {:.2} ({}/{})", - estimate as f64 / total as f64, - estimate, - total + "Cov API/Cov GAPI/#API/#GAPI: {}({:.2})/{}({:.2})/{}/{}", + num_covered_apis, + num_covered_apis as f64 / stats.num_api as f64, + num_covered_generic_apis, + num_covered_generic_apis as f64 / stats.num_generic_api as f64, + stats.num_api, + stats.num_generic_api ); - let dot_path = format!("api_graph_{}_{}.dot", local_crate_name, local_crate_type); - let json_path = format!("api_graph_{}_{}.json", local_crate_name, local_crate_type); - rap_info!("Dump API dependency graph to {}", dot_path); - api_graph.dump_to_dot(dot_path, self.tcx); - api_graph - .dump_to_json(&json_path) - .expect("failed to dump API graph to JSON"); - rap_info!("Dump API dependency graph to {}", json_path); + + let stats_with_coverage = StatsWithCoverage { + num_apis: stats.num_api, + num_generic_apis: stats.num_generic_api, + num_covered_apis, + num_covered_generic_apis, + }; + + // dump adg stats + let stats_file = std::fs::File::create("adg_stats.json").unwrap(); + serde_json::to_writer(stats_file, &stats_with_coverage) + .expect("failed to dump stats to JSON"); + + // dump API graph, determine the format base on extension name + if let Some(dump_path) = &self.config.dump { + self.api_graph + .dump_to_file(dump_path) + .inspect_err(|err| { + rap_error!("{:?}", err); + }) + .expect("failed to dump API graph"); + } } fn reset(&mut self) { diff --git a/rapx/src/analysis/core/api_dependency/mono.rs b/rapx/src/analysis/core/api_dependency/mono.rs index 65408024..8487ace2 100644 --- a/rapx/src/analysis/core/api_dependency/mono.rs +++ b/rapx/src/analysis/core/api_dependency/mono.rs @@ -183,13 +183,6 @@ impl<'tcx> MonoSet<'tcx> { self } - fn filter_by_trait_bound(mut self, fn_did: DefId, tcx: TyCtxt<'tcx>) -> Self { - //let early_fn_sig = tcx.fn_sig(fn_did); - self.monos - .retain(|args| is_args_fit_trait_bound(fn_did, &args.value, tcx)); - self - } - pub fn random_sample(&mut self, rng: &mut R) { if self.monos.len() <= MAX_STEP_SET_SIZE { return; @@ -245,12 +238,12 @@ fn is_args_fit_trait_bound<'tcx>( tcx: TyCtxt<'tcx>, ) -> bool { let args = tcx.mk_args(args); - // rap_info!( - // "fn: {:?} args: {:?} identity: {:?}", - // fn_did, - // args, - // ty::GenericArgs::identity_for_item(tcx, fn_did) - // ); + rap_trace!( + "fn: {:?} args: {:?} identity: {:?}", + fn_did, + args, + ty::GenericArgs::identity_for_item(tcx, fn_did) + ); let infcx = tcx.infer_ctxt().build(ty::TypingMode::PostAnalysis); let pred = tcx.predicates_of(fn_did); let inst_pred = pred.instantiate(tcx, args); @@ -267,6 +260,7 @@ fn is_args_fit_trait_bound<'tcx>( param_env, pred.as_predicate(), ); + rap_trace!("[trait bound] check pred: {:?}", pred); let res = infcx.evaluate_obligation(&obligation); match res { @@ -313,7 +307,8 @@ fn get_mono_set<'tcx>( let mut rng = rand::rng(); // sample from reachable types - rap_debug!("[get_mono_set] fn_did: {:?}", fn_did); + rap_debug!("[get_mono_set] solve {}", tcx.def_path_str(fn_did)); + let identity = ty::GenericArgs::identity_for_item(tcx, fn_did); let infcx = tcx .infer_ctxt() .ignoring_regions() @@ -323,6 +318,7 @@ fn get_mono_set<'tcx>( let fresh_args = infcx.fresh_args_for_item(DUMMY_SP, fn_did); // this replace generic types in fn_sig to infer var, e.g. fn(Vec, i32) => fn(Vec, i32) let fn_sig = fn_sig_with_generic_args(fn_did, fresh_args, tcx); + let identity_fnsig = fn_sig_with_generic_args(fn_did, identity, tcx); let generics = tcx.generics_of(fn_did); // print fresh_args for debugging @@ -339,42 +335,47 @@ fn get_mono_set<'tcx>( rap_trace!("[get_mono_set] initialize s: {:?}", s); - let mut cnt = 0; - - for input_ty in fn_sig.inputs().iter() { - cnt += 1; + for (no, input_ty) in fn_sig.inputs().iter().enumerate() { if !input_ty.has_infer_types() { continue; } - rap_trace!("[get_mono_set] input_ty#{}: {:?}", cnt - 1, input_ty); - - let mut reachable_set = - available_ty - .iter() - .fold(MonoSet::new(), |mut reachable_set, ty| { - if let Some(mono) = unify_ty( - *input_ty, - (*ty).into(), - &fresh_args, - &infcx, - &dummy_cause, - param_env, - ) { - reachable_set.insert(mono); - } - reachable_set - }); - reachable_set.random_sample(&mut rng); rap_debug!( - "[get_mono_set] size: s = {}, input = {}", + "[get_mono_set] input_ty#{}: {}", + no, + identity_fnsig.inputs()[no] + ); + + let reachable_set = available_ty + .iter() + .fold(MonoSet::new(), |mut reachable_set, ty| { + if let Some(mono) = unify_ty( + *input_ty, + (*ty).into(), + &fresh_args, + &infcx, + &dummy_cause, + param_env, + ) { + reachable_set.insert(mono); + } + reachable_set + }); + // reachable_set.random_sample(&mut rng); + rap_debug!( + "[get_mono_set] size of s: {}, size of input: {}", s.count(), reachable_set.count() ); + rap_trace!("[get_mono_set] input = {:?}", reachable_set); s = s.merge(&reachable_set, tcx); s.random_sample(&mut rng); + rap_trace!("[get_mono_set] after merge s = {:?}", reachable_set); } - rap_trace!("[get_mono_set] after input types: {:?}", s); + rap_debug!( + "[get_mono_set] after input filter, size of s: {}", + s.count() + ); let mut res = MonoSet::new(); @@ -394,16 +395,8 @@ fn get_mono_set<'tcx>( // erase infer region var res.erase_region_var(tcx); - res -} - -fn is_special_std_ty<'tcx>(def_id: DefId, tcx: TyCtxt<'tcx>) -> bool { - let allowed_std_ty = [ - tcx.lang_items().string().unwrap(), - path_str_def_id(tcx, "std::vec::Vec"), - ]; - - allowed_std_ty.contains(&def_id) + // if there is still unbound generic type, we try to instantiate it with predefined candidates + res.instantiate_unbound(tcx) } fn solve_unbound_type_generics<'tcx>( @@ -424,6 +417,7 @@ fn solve_unbound_type_generics<'tcx>( let mut mset = MonoSet::all(args); rap_debug!("[solve_unbound] did = {did:?}, mset={mset:?}"); for pred in preds.predicates.iter() { + rap_debug!("[solve_unbound] pred = {:?}", pred); if let Some(trait_pred) = pred.as_trait_clause() { let trait_pred = trait_pred.skip_binder(); @@ -439,21 +433,18 @@ fn solve_unbound_type_generics<'tcx>( let mut p = MonoSet::new(); - for impl_did in tcx - .all_impls(trait_def_id) - .chain(tcx.inherent_impls(trait_def_id).iter().map(|did| *did)) + for impl_did in tcx.all_impls(trait_def_id) + // .chain(tcx.inherent_impls(trait_def_id).iter().map(|did| *did)) { // format: > let impl_trait_ref = tcx.impl_trait_ref(impl_did).skip_binder(); - rap_trace!("impl_trait_ref: {}", impl_trait_ref); - // filter irrelevant implementation. We only consider implementation if: + // filter irrelevant implementation. We only consider implementation that: // 1. it is local // 2. it is not local, but its' self_ty is a primitive if !impl_did.is_local() && !impl_trait_ref.self_ty().is_primitive() { continue; } - // rap_trace!("impl_trait_ref: {}", impl_trait_ref); if let Some(mono) = unify_trait( trait_pred.trait_ref, @@ -521,10 +512,25 @@ pub fn resolve_mono_apis<'tcx>( } // 2. get mono set from available types - let ret = get_mono_set(fn_did, &available_ty, tcx).instantiate_unbound(tcx); + let ret = get_mono_set(fn_did, &available_ty, tcx); + + // 3. check trait bound & ty is stable + let ret = ret.filter(|mono| { + is_args_fit_trait_bound(fn_did, &mono.value, tcx) + && mono.value.iter().all(|arg| { + if let Some(ty) = arg.as_type() { + !utils::is_ty_unstable(ty, tcx) + } else { + true + } + }) + }); - // 3. check trait bound - let ret = ret.filter_by_trait_bound(fn_did, tcx); + rap_debug!( + "[resolve_mono_apis] fn_did: {:?}, size of mono: {:?}", + fn_did, + ret.count() + ); ret } @@ -614,23 +620,35 @@ pub fn get_unbound_generic_candidates<'tcx>(tcx: TyCtxt<'tcx>) -> Vec(args: &GenericArgsRef<'tcx>) -> usize { + args.iter().fold(0, |acc, arg| { + if let Some(ty) = arg.as_type() { + acc + utils::ty_complexity(ty) + } else { + acc + } + }) +} + pub fn get_impls<'tcx>( tcx: TyCtxt<'tcx>, fn_did: DefId, args: GenericArgsRef<'tcx>, ) -> HashSet { + rap_debug!( + "get impls for fn: {:?} args: {:?}", + tcx.def_path_str_with_args(fn_did, args), + args + ); let mut impls = HashSet::new(); let preds = tcx.predicates_of(fn_did).instantiate(tcx, args); for (pred, _) in preds { if let Some(trait_pred) = pred.as_trait_clause() { - let trait_ref: rustc_type_ir::TraitRef> = - trait_pred.skip_binder().trait_ref; - // ignore Sized trait - // if tcx.is_lang_item(trait_ref.def_id, LangItem::Sized) - // || tcx.def_path_str(trait_ref.def_id) == "std::default::Default" - // { - // continue; - // } + let trait_ref: rustc_type_ir::TraitRef> = tcx + .liberate_late_bound_regions(fn_did, trait_pred) + .trait_ref; let res = tcx.codegen_select_candidate( TypingEnv::fully_monomorphized().as_query_input(trait_ref), diff --git a/rapx/src/analysis/core/api_dependency/utils.rs b/rapx/src/analysis/core/api_dependency/utils.rs index 2cb1a421..2683f2e0 100644 --- a/rapx/src/analysis/core/api_dependency/utils.rs +++ b/rapx/src/analysis/core/api_dependency/utils.rs @@ -1,85 +1,38 @@ #![allow(warnings, unused)] - +use super::fuzzable; use rustc_hir::LangItem; use rustc_hir::def_id::DefId; -use rustc_middle::ty::{self, FnSig, Ty, TyCtxt, TyKind}; +use rustc_middle::ty::{self, FnSig, GenericArgsRef, Ty, TyCtxt, TyKind}; use rustc_span::sym; -fn is_fuzzable_std_ty<'tcx>(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> bool { - match ty.kind() { - ty::Adt(def, _) => { - tcx.is_lang_item(def.did(), LangItem::String) - || tcx.is_diagnostic_item(sym::Vec, def.did()) - || tcx.is_diagnostic_item(sym::Arc, def.did()) - } - _ => false, - } +pub fn is_def_id_public(fn_def_id: impl Into, tcx: TyCtxt<'_>) -> bool { + let fn_def_id: DefId = fn_def_id.into(); + let local_id = fn_def_id.expect_local(); + rap_trace!( + "vis: {:?} (path: {}) => {:?}", + fn_def_id, + tcx.def_path_str(fn_def_id), + tcx.effective_visibilities(()).effective_vis(local_id) + ); + + tcx.effective_visibilities(()).is_directly_public(local_id) + || tcx.effective_visibilities(()).is_exported(local_id) } pub fn is_fuzzable_ty<'tcx>(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> bool { - if is_fuzzable_std_ty(ty, tcx) { - return true; - } - - match ty.kind() { - // Basical data type - TyKind::Bool - | TyKind::Char - | TyKind::Int(_) - | TyKind::Uint(_) - | TyKind::Float(_) - | TyKind::Str => true, - - // Infer - TyKind::Infer( - ty::InferTy::IntVar(_) - | ty::InferTy::FreshIntTy(_) - | ty::InferTy::FloatVar(_) - | ty::InferTy::FreshFloatTy(_), - ) => true, - - // Reference, Array, Slice - TyKind::Ref(_, inner_ty, _) | TyKind::Array(inner_ty, _) | TyKind::Slice(inner_ty) => { - is_fuzzable_ty(inner_ty.peel_refs(), tcx) - } - - // Tuple - TyKind::Tuple(tys) => tys - .iter() - .all(|inner_ty| is_fuzzable_ty(inner_ty.peel_refs(), tcx)), - - // ADT - TyKind::Adt(adt_def, substs) => { - if adt_def.variant_list_has_applicable_non_exhaustive() { - return false; - } - if adt_def.is_struct() { - // 检查所有字段是否为 pub 且类型可 Fuzz - adt_def.all_fields().all(|field| { - field.vis.is_public() && // 字段必须是 pub - is_fuzzable_ty(field.ty(tcx, substs).peel_refs(), tcx) - }) - } else if adt_def.is_enum() { - adt_def.variants().iter().all(|variant| { - variant - .fields - .iter() - .all(|field| is_fuzzable_ty(field.ty(tcx, substs).peel_refs(), tcx)) - }) - } else { - false // union 暂不处理 - } - } - - // 其他类型默认不可 Fuzz - _ => false, - } + rap_trace!("check fuzzable ty: {}.", ty); + let is_fuzzable = fuzzable::is_fuzzable_ty(ty, tcx, 0); + rap_trace!("is_fuzzable({}) = {}.", ty, is_fuzzable); + is_fuzzable } -pub fn fn_sig_without_binders<'tcx>(fn_did: DefId, tcx: TyCtxt<'tcx>) -> FnSig<'tcx> { - let early_fn_sig = tcx.fn_sig(fn_did); - let binder_fn_sig = early_fn_sig.instantiate_identity(); - tcx.liberate_late_bound_regions(fn_did, binder_fn_sig) +pub fn is_fuzzable_api<'tcx>(fn_did: DefId, args: GenericArgsRef<'tcx>, tcx: TyCtxt<'tcx>) -> bool { + let fn_sig = fn_sig_with_generic_args(fn_did, args, tcx); + fn_sig + .inputs() + .iter() + .copied() + .all(|ty| is_fuzzable_ty(ty, tcx)) } pub fn fn_sig_with_generic_args<'tcx>( @@ -127,3 +80,7 @@ pub fn ty_complexity<'tcx>(ty: Ty<'tcx>) -> usize { _ => 1, } } + +pub fn is_ty_unstable<'tcx>(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> bool { + ty == tcx.types.f16 +} diff --git a/rapx/src/analysis/core/api_dependency/visit.rs b/rapx/src/analysis/core/api_dependency/visit.rs new file mode 100644 index 00000000..37b22f8f --- /dev/null +++ b/rapx/src/analysis/core/api_dependency/visit.rs @@ -0,0 +1,165 @@ +use super::graph::ApiDependencyGraph; +use super::graph::{DepEdge, DepNode}; +use super::is_def_id_public; +use crate::analysis::core::api_dependency::mono; +use crate::{rap_debug, rap_trace}; +use rustc_hir::LangItem; +use rustc_hir::{ + BodyId, BodyOwnerKind, FnDecl, + def_id::{DefId, LocalDefId}, + intravisit::{FnKind, Visitor}, +}; +use rustc_middle::ty::{self, FnSig, ParamEnv, Ty, TyCtxt, TyKind}; +use rustc_span::Span; +use std::io::Write; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Hash)] +pub struct Config { + pub ignore_const_generic: bool, + pub include_unsafe: bool, + pub include_drop: bool, + pub include_generic: bool, + pub pub_only: bool, +} + +impl Default for Config { + fn default() -> Self { + Config { + pub_only: true, + ignore_const_generic: true, + include_unsafe: false, + include_drop: false, + include_generic: true, + } + } +} + +pub struct FnVisitor<'tcx> { + tcx: TyCtxt<'tcx>, + apis: Vec, + generic_apis: Vec, + config: Config, +} + +impl<'tcx> FnVisitor<'tcx> { + pub fn new(config: Config, tcx: TyCtxt<'tcx>) -> FnVisitor<'tcx> { + FnVisitor { + tcx, + apis: Vec::new(), + generic_apis: Vec::new(), + config, + } + } + + pub fn count_api(&self) -> usize { + self.apis.len() + } + + pub fn count_generic_api(&self) -> usize { + self.generic_apis.len() + } + + pub fn non_generic_apis(&self) -> &[DefId] { + &self.apis + } + + pub fn generic_apis(&self) -> &[DefId] { + &self.generic_apis + } + + pub fn write_funcs(&self, f: &mut T) { + for id in &self.apis { + write!(f, "{}\n", self.tcx.def_path_str(id)).expect("fail when write funcs"); + } + } +} + +pub fn has_const_generics(generics: &ty::Generics, tcx: TyCtxt<'_>) -> bool { + if generics + .own_params + .iter() + .any(|param| matches!(param.kind, ty::GenericParamDefKind::Const { .. })) + { + return true; + } + + if let Some(parent_def_id) = generics.parent { + let parent = tcx.generics_of(parent_def_id); + has_const_generics(parent, tcx) + } else { + false + } +} + +fn is_drop_impl(tcx: TyCtxt<'_>, fn_did: DefId) -> bool { + if let Some(impl_id) = tcx.trait_impl_of_assoc(fn_did) { + let trait_did = tcx.impl_trait_id(impl_id); + if tcx.is_lang_item(trait_did, LangItem::Drop) { + return true; + } + } + false +} + +impl<'tcx> Visitor<'tcx> for FnVisitor<'tcx> { + fn visit_fn<'v>( + &mut self, + fk: FnKind<'v>, + _fd: &'v FnDecl<'v>, + _b: BodyId, + span: Span, + id: LocalDefId, + ) -> Self::Result { + let fn_did = id.to_def_id(); + let generics = self.tcx.generics_of(fn_did); + rap_trace!( + "visit fn: {:?} (path: {}), generics: {:?}, span: {:?}", + fn_did, + self.tcx.def_path_str(fn_did), + generics, + span, + ); + + if self.tcx.def_path_str(fn_did).ends_with("dummy") && self.tcx.def_span(fn_did).is_dummy() + { + rap_trace!("skip rustc dummy fn"); + return; + } + + if self.config.pub_only && !is_def_id_public(fn_did, self.tcx) { + rap_trace!("skip for non-public"); + return; + } + + if !self.config.include_drop && is_drop_impl(self.tcx, fn_did) { + rap_trace!("skip drop impl"); + return; + } + + let is_generic = generics.requires_monomorphization(self.tcx); + + // if config.resolve_generic is false, skip all generic functions + if !self.config.include_generic && is_generic { + rap_trace!("skip generic fn"); + return; + } + + // if config.ignore_const_generic is true, + // skip functions with const generics + if self.config.ignore_const_generic && has_const_generics(generics, self.tcx) { + rap_trace!("skip const generic fn"); + return; + } + + if !self.config.include_unsafe && fk.header().unwrap().safety().is_unsafe() { + rap_trace!("skip unsafe fn"); + return; + } + + if is_generic { + self.generic_apis.push(fn_did); + } else { + self.apis.push(fn_did); + } + } +} diff --git a/rapx/src/analysis/core/api_dependency/visitor.rs b/rapx/src/analysis/core/api_dependency/visitor.rs deleted file mode 100644 index 0a381ab3..00000000 --- a/rapx/src/analysis/core/api_dependency/visitor.rs +++ /dev/null @@ -1,107 +0,0 @@ -use super::Config; -use super::graph::ApiDependencyGraph; -use super::graph::{DepEdge, DepNode}; -use super::is_def_id_public; -use crate::analysis::core::api_dependency::mono; -use crate::{rap_debug, rap_trace}; -use rustc_hir::{ - BodyId, BodyOwnerKind, FnDecl, - def_id::{DefId, LocalDefId}, - intravisit::{FnKind, Visitor}, -}; -use rustc_middle::ty::{self, FnSig, ParamEnv, Ty, TyCtxt, TyKind}; -use rustc_span::Span; -use std::io::Write; - -pub struct FnVisitor<'tcx, 'a> { - tcx: TyCtxt<'tcx>, - apis: Vec, - config: Config, - graph: &'a mut ApiDependencyGraph<'tcx>, -} - -impl<'tcx, 'a> FnVisitor<'tcx, 'a> { - pub fn new( - graph: &'a mut ApiDependencyGraph<'tcx>, - config: Config, - tcx: TyCtxt<'tcx>, - ) -> FnVisitor<'tcx, 'a> { - let fn_cnt = 0; - let funcs = Vec::new(); - FnVisitor { - tcx, - graph, - apis: funcs, - config, - } - } - - pub fn count_api(&self) -> usize { - self.apis.len() - } - - pub fn apis(self) -> Vec { - self.apis - } - - pub fn write_funcs(&self, f: &mut T) { - for id in &self.apis { - write!(f, "{}\n", self.tcx.def_path_str(id)).expect("fail when write funcs"); - } - } -} - -pub fn has_const_generics(generics: &ty::Generics, tcx: TyCtxt<'_>) -> bool { - if generics - .own_params - .iter() - .any(|param| matches!(param.kind, ty::GenericParamDefKind::Const { .. })) - { - return true; - } - - if let Some(parent_def_id) = generics.parent { - let parent = tcx.generics_of(parent_def_id); - has_const_generics(parent, tcx) - } else { - false - } -} - -impl<'tcx, 'a> Visitor<'tcx> for FnVisitor<'tcx, 'a> { - fn visit_fn<'v>( - &mut self, - _fk: FnKind<'v>, - _fd: &'v FnDecl<'v>, - _b: BodyId, - _span: Span, - id: LocalDefId, - ) -> Self::Result { - let fn_did = id.to_def_id(); - let generics = self.tcx.generics_of(fn_did); - - let is_generic = generics.requires_monomorphization(self.tcx); - if self.config.pub_only && !is_def_id_public(fn_did, self.tcx) { - return; - } - - // if config.resolve_generic is false, - // skip all generic functions - if !self.config.resolve_generic && is_generic { - return; - } - - // if config.ignore_const_generic is true, - // skip functions with const generics - if self.config.ignore_const_generic && has_const_generics(generics, self.tcx) { - return; - } - - if !is_generic { - let args = ty::GenericArgs::identity_for_item(self.tcx, fn_did); - self.graph.add_api(fn_did, &args); - } - - self.apis.push(fn_did); - } -} diff --git a/rapx/src/analysis/scan/mod.rs b/rapx/src/analysis/scan/mod.rs index cee5dbe5..ed7357e1 100644 --- a/rapx/src/analysis/scan/mod.rs +++ b/rapx/src/analysis/scan/mod.rs @@ -22,12 +22,17 @@ impl<'tcx> Analysis for ScanAnalysis<'tcx> { fn run(&mut self) { let crate_name = self.tcx.crate_name(LOCAL_CRATE); let crate_type = self.tcx.crate_types()[0]; - rap_info!("scan crate: {}", crate_name.as_str()); - rap_info!("crate type: {}", crate_type); + rap_info!("======== crate info ========"); + rap_info!("name: {}", crate_name.as_str()); + rap_info!("type: {}", crate_type); + rap_info!("============================"); + rap_info!(""); + rap_info!("======== API info ========"); let mut fn_visitor = FnVisitor::new(self.tcx); self.tcx.hir_visit_all_item_likes_in_crate(&mut fn_visitor); let stats = fn_visitor.statistic(); stats.info().print_log(); + rap_info!("============================"); } fn reset(&mut self) {} diff --git a/rapx/src/analysis/scan/visitor.rs b/rapx/src/analysis/scan/visitor.rs index bf8d69f4..8a8118f0 100644 --- a/rapx/src/analysis/scan/visitor.rs +++ b/rapx/src/analysis/scan/visitor.rs @@ -27,7 +27,7 @@ fn is_api_public(fn_def_id: impl Into, tcx: TyCtxt<'_>) -> bool { tcx.effective_visibilities(()).effective_vis(local_id) ); tcx.effective_visibilities(()).is_directly_public(local_id) - // || tcx.effective_visibilities(()).is_exported(local_id) + || tcx.effective_visibilities(()).is_exported(local_id) } impl<'tcx> FnVisitor<'tcx> { @@ -49,12 +49,24 @@ impl<'tcx> FnVisitor<'tcx> { id: LocalDefId, ) { let fn_did = id.to_def_id(); + rap_debug!("API path: {}", self.tcx.def_path_str(fn_did)); + rap_debug!( + "fn_sig: {}", + self.tcx.type_of(fn_did).instantiate_identity() + ); + rap_debug!( + "visibility: {:?}", + self.tcx + .effective_visibilities(()) + .effective_vis(fn_did.as_local().unwrap()) + .unwrap() + ); if !is_api_public(fn_did, self.tcx) { + rap_debug!("skip for not public API"); return; } - rap_debug!("API path: {}", self.tcx.def_path_str(fn_did)); - rap_debug!("type: {}", self.tcx.type_of(fn_did).instantiate_identity()); + let is_generic = self .tcx .generics_of(fn_did) diff --git a/rapx/src/analysis/utils/mod.rs b/rapx/src/analysis/utils/mod.rs index 16052316..e6ea7e70 100644 --- a/rapx/src/analysis/utils/mod.rs +++ b/rapx/src/analysis/utils/mod.rs @@ -2,4 +2,5 @@ pub mod def_path; pub mod draw_dot; #[allow(unused)] pub mod fn_info; +pub mod path; pub mod show_mir; diff --git a/rapx/src/analysis/utils/path.rs b/rapx/src/analysis/utils/path.rs new file mode 100644 index 00000000..b8d26221 --- /dev/null +++ b/rapx/src/analysis/utils/path.rs @@ -0,0 +1,210 @@ +use itertools::Itertools; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_middle::ty::{self, Ty, TyCtxt, TyKind}; +use rustc_span::Ident; +use std::collections::HashMap; + +/// A utility to resolve the actual visible path for re-export items. +pub struct PathResolver<'tcx> { + tcx: TyCtxt<'tcx>, + path_map: HashMap, +} + +pub fn get_path_resolver<'tcx>(tcx: TyCtxt<'tcx>) -> PathResolver<'tcx> { + let mut resolver = PathResolver::new(tcx); + resolver.build(LOCAL_CRATE.as_def_id(), String::new()); + resolver +} + +fn join_path_with_ident(current_path: &str, ident: Ident) -> String { + if current_path.is_empty() { + ident.as_str().to_owned() + } else { + (current_path.to_string() + "::" + ident.as_str()).to_owned() + } +} + +impl<'tcx> PathResolver<'tcx> { + fn new(tcx: TyCtxt<'tcx>) -> Self { + PathResolver { + tcx, + path_map: HashMap::new(), + } + } + + fn build(&mut self, mod_id: DefId, current_path: String) { + let childs = if mod_id.is_local() { + self.tcx.module_children_local(mod_id.expect_local()) + } else { + self.tcx.module_children(mod_id) + }; + + for child in childs { + if !child.vis.is_public() { + continue; + } + if let Some(did) = child.res.opt_def_id() { + let path = join_path_with_ident(¤t_path, child.ident); + self.path_map.entry(did).or_insert(path.clone()); + if self.tcx.def_kind(did).is_module_like() { + self.build(did, path); + } + } + } + } + + fn non_assoc_path_str(&self, def_id: DefId) -> String { + match self.path_map.get(&def_id) { + Some(path) => path.clone(), + None => { + // if def_id is from local crate, but we cannot find it in path_map, + // report this error. + if def_id.is_local() { + rap_error!( + "[PathResolver] cannot find path for {:?}, fallback to self.tcx.def_path_str", + def_id + ); + } + self.tcx.def_path_str(def_id) + } + } + } + + pub fn ty_str(&self, ty: Ty<'tcx>) -> String { + match ty.kind() { + TyKind::Adt(adt_def, args) => self.path_str_with_args(adt_def.did(), args), + TyKind::Array(inner_ty, const_) => { + format!("[{};{}]", self.ty_str(*inner_ty), const_) + } + TyKind::Tuple(tys) => { + format!("({})", tys.iter().map(|ty| self.ty_str(ty)).join(", ")) + } + TyKind::Ref(region, inner_ty, mutability) => { + format!( + "&{} {}{}", + region, + mutability.prefix_str(), + self.ty_str(*inner_ty) + ) + } + TyKind::RawPtr(inner_ty, mutability) => { + format!("*{} {}", mutability.ptr_str(), self.ty_str(*inner_ty)) + } + TyKind::Slice(inner_ty) => { + format!("[{}]", self.ty_str(*inner_ty)) + } + _ => ty.to_string(), + } + } + + pub fn path_str(&self, def_id: DefId) -> String { + self.path_str_with_args(def_id, ty::GenericArgs::identity_for_item(self.tcx, def_id)) + } + + pub fn path_str_with_args(&self, def_id: DefId, args: ty::GenericArgsRef<'tcx>) -> String { + // `{assoc_path}::{item_name}` + if let Some((assoc_id, kind)) = self.tcx.assoc_parent(def_id) { + rap_trace!("assoc item: {:?} => {:?}", assoc_id, kind); + // the number of generic of assoc parent + let num_generic = self.tcx.generics_of(assoc_id).own_params.len(); + + let (parent_args, own_args) = args.split_at(num_generic); + + let parent_path_str = match kind { + // Trait Impl + DefKind::Impl { of_trait: true } => { + let trait_ref = self + .tcx + .impl_trait_ref(assoc_id) + .instantiate(self.tcx, parent_args); + + let self_ty_str = self.ty_str(trait_ref.self_ty()); + let trait_str = self.non_assoc_path_str(trait_ref.def_id); + if trait_ref.args.len() > 1 { + format!( + "<{} as {}{}>", + self_ty_str, + trait_str, + self.generic_args_str(&trait_ref.args[1..]) + ) + } else { + format!("<{} as {}>", self_ty_str, trait_str) + } + } + // inherent impl + DefKind::Impl { of_trait: false } => { + let self_ty = self + .tcx + .type_of(assoc_id) + .instantiate(self.tcx, parent_args); + self.ty_str(self_ty) + } + // Trait + DefKind::Trait => { + let self_ty = parent_args[0].expect_ty(); + let self_ty_str = self.ty_str(self_ty); + let trait_str = self.non_assoc_path_str(assoc_id); + if parent_args.len() > 1 { + format!( + "<{} as {}{}>", + self_ty_str, + trait_str, + self.generic_args_str(&parent_args[1..]) + ) + } else { + format!("<{} as {}>", self_ty_str, trait_str) + } + } + _ => { + unreachable!( + "unexpected assoc parent: {:?} => {:?}, def_id: {:?}, path: {:?}", + assoc_id, + kind, + def_id, + self.tcx.def_path_str_with_args(def_id, args) + ); + } + }; + + if own_args.len() > 0 { + format!( + "{}::{}::{}", + parent_path_str, + self.tcx.item_name(def_id), + self.generic_args_str(own_args) + ) + } else { + format!("{}::{}", parent_path_str, self.tcx.item_name(def_id)) + } + } else { + if args.len() > 0 { + format!( + "{}::{}", + self.non_assoc_path_str(def_id), + self.generic_args_str(args) + ) + } else { + format!("{}", self.non_assoc_path_str(def_id)) + } + } + } + + pub fn generic_arg_str(&self, arg: ty::GenericArg<'tcx>) -> String { + match arg.kind() { + ty::GenericArgKind::Lifetime(_) => "'_".to_string(), + ty::GenericArgKind::Type(ty) => self.ty_str(ty), + ty::GenericArgKind::Const(const_) => format!("{}", const_), + } + } + + fn generic_args_str(&self, generic_args: &[ty::GenericArg<'tcx>]) -> String { + format!( + "<{}>", + generic_args + .iter() + .map(|arg| self.generic_arg_str(*arg)) + .join(", ") + ) + } +} diff --git a/rapx/src/bin/cargo-rapx/main.rs b/rapx/src/bin/cargo-rapx/main.rs index bd7591d7..96cbe4a4 100644 --- a/rapx/src/bin/cargo-rapx/main.rs +++ b/rapx/src/bin/cargo-rapx/main.rs @@ -28,11 +28,13 @@ fn phase_rustc_wrapper() { let is_primary = env::var("CARGO_PRIMARY_PACKAGE").is_ok(); let package_name = env::var("CARGO_PKG_NAME").unwrap_or_default(); + let is_build_script = + env::var("CARGO_CRATE_NAME").map_or(false, |name| name == "build_script_build"); // check `CARGO_PRIMARY_PACKAGE` to make sure we only run // rapx for the local crate, but not dependencies. // rapx only checks local crates - if is_primary { + if is_primary && !is_build_script { rap_debug!("run rapx for package {}", package_name); run_rap(); return; diff --git a/rapx/src/bin/cargo-rapx/utils.rs b/rapx/src/bin/cargo-rapx/utils.rs index 547ef56c..3de2c3ac 100644 --- a/rapx/src/bin/cargo-rapx/utils.rs +++ b/rapx/src/bin/cargo-rapx/utils.rs @@ -6,7 +6,9 @@ pub fn run_cmd(mut cmd: Command) { match cmd.status() { Ok(status) => { if !status.success() { - process::exit(status.code().unwrap()); + // 254 is an arbitrary non-zero magic number that + // indicates the program is terminated by signals + process::exit(status.code().unwrap_or(254)); } } Err(err) => panic!("Error in running {:?} {}.", cmd, err), diff --git a/rapx/src/cli.rs b/rapx/src/cli.rs index 4f749d98..6ed31ad8 100644 --- a/rapx/src/cli.rs +++ b/rapx/src/cli.rs @@ -1,3 +1,5 @@ +mod analyze; +pub use analyze::*; use clap::{Args, Subcommand, ValueEnum}; #[derive(Args, Debug, Clone)] @@ -10,13 +12,6 @@ pub struct RapxArgs { pub test_crate: Option, } -#[derive(Debug, Clone, Copy, ValueEnum)] -pub enum OptLevel { - Report, - Default, - All, -} - // NOTE: docstring is automatically used to generate help messages, // so please use it to explain the command instead of `help` attribute in `arg` macro. #[derive(Debug, Clone, Subcommand)] @@ -68,54 +63,10 @@ pub enum Commands { } #[derive(Debug, Clone, Copy, ValueEnum)] -pub enum AliasStrategyKind { - /// meet-over-paths (default) - Mop, - /// maximum-fixed-point - Mfp, -} - -// use command string to automatically generate help messages -#[derive(Debug, Clone, Copy, Subcommand)] -pub enum AnalysisKind { - /// perform alias analysis (meet-over-paths by default) - Alias { - /// specify the alias analysis strategy - #[arg(short, long, default_value = "mop")] - strategy: AliasStrategyKind, - }, - /// generate API dependency graphs - Adg, - /// generate unsafety propagation graphs for each module - Upg, - /// generate unsafety propagation graphs for each module of the Rust standard library - UpgStd, - /// generate callgraphs - Callgraph, - /// generate dataflow graphs - Dataflow { - /// print debug information during dataflow analysis - #[arg(short, long)] - debug: bool, - }, - /// analyze if the type holds a piece of memory on heap - OwnedHeap, - /// extract path constraints - Pathcond, - /// perform range analysis - Range { - /// print debug information during range analysis - #[arg(short, long)] - debug: bool, - }, - /// print basic information of the crate, e.g., the number of APIs - Scan, - /// print the SSA form of the crate - Ssa, - /// print the MIR of the crate - Mir, - /// print the MIR of the crate in dot format - DotMir, +pub enum OptLevel { + Report, + Default, + All, } // use command string to automatically generate help messages diff --git a/rapx/src/cli/analyze.rs b/rapx/src/cli/analyze.rs new file mode 100644 index 00000000..d90cb803 --- /dev/null +++ b/rapx/src/cli/analyze.rs @@ -0,0 +1,73 @@ +use clap::{Args, Subcommand, ValueEnum}; +use std::path::PathBuf; + +#[derive(Debug, Clone, Args)] +pub struct AdgArgs { + #[arg(long)] + /// Include private APIs in the API graph. By default, only public APIs are included. + pub include_private: bool, + #[arg(long)] + /// Include unsafe APIs in API graph. By default, only safe APIs are included. + pub include_unsafe: bool, + /// Include Drop trait in API graph. By default, Drop is not included. + #[arg(long)] + pub include_drop: bool, + /// The maximum number of iterations to search for generic APIs. + #[arg(long, default_value_t = 10)] + pub max_iteration: usize, + /// The path to dump the API graph to. Output format is decided by extension suffix. + /// default PATH = `./api_graph.dot`. + #[arg(long, default_missing_value = "./api_graph.dot", value_name = "PATH")] + pub dump: Option, +} + +#[derive(Debug, Clone, Copy, ValueEnum)] +pub enum AliasStrategyKind { + /// meet-over-paths (default) + Mop, + /// maximum-fixed-point + Mfp, +} + +// use command string to automatically generate help messages +#[derive(Debug, Clone, Subcommand)] +pub enum AnalysisKind { + /// perform alias analysis (meet-over-paths by default) + Alias { + /// specify the alias analysis strategy + #[arg(short, long, default_value = "mop")] + strategy: AliasStrategyKind, + }, + /// generate API dependency graphs + Adg(AdgArgs), + /// generate unsafety propagation graphs for each module + Upg, + /// generate unsafety propagation graphs for each module of the Rust standard library + UpgStd, + /// generate callgraphs + Callgraph, + /// generate dataflow graphs + Dataflow { + /// print debug information during dataflow analysis + #[arg(short, long)] + debug: bool, + }, + /// analyze if the type holds a piece of memory on heap + OwnedHeap, + /// extract path constraints + Pathcond, + /// perform range analysis + Range { + /// print debug information during range analysis + #[arg(short, long)] + debug: bool, + }, + /// print basic information of the crate, e.g., the number of APIs + Scan, + /// print the SSA form of the crate + Ssa, + /// print the MIR of the crate + Mir, + /// print the MIR of the crate in dot format + DotMir, +} diff --git a/rapx/src/def_id.rs b/rapx/src/def_id.rs index c927c86d..6e4ca36e 100644 --- a/rapx/src/def_id.rs +++ b/rapx/src/def_id.rs @@ -1,5 +1,3 @@ -extern crate indexmap; - use indexmap::IndexMap; use rustc_hir::def_id::DefId; use rustc_middle::ty::TyCtxt; diff --git a/rapx/src/lib.rs b/rapx/src/lib.rs index dfaf2659..7ed746d6 100644 --- a/rapx/src/lib.rs +++ b/rapx/src/lib.rs @@ -9,7 +9,7 @@ pub mod cli; pub mod def_id; pub mod help; pub mod preprocess; -extern crate intervals; + extern crate rustc_abi; extern crate rustc_ast; extern crate rustc_data_structures; @@ -22,6 +22,7 @@ extern crate rustc_infer; extern crate rustc_interface; extern crate rustc_metadata; extern crate rustc_middle; +extern crate rustc_mir_dataflow; extern crate rustc_public; extern crate rustc_session; extern crate rustc_span; @@ -30,8 +31,12 @@ extern crate rustc_trait_selection; extern crate rustc_traits; extern crate rustc_type_ir; extern crate thin_vec; + use crate::{ - analysis::{core::alias_analysis::mfp::MfpAliasAnalyzer, scan::ScanAnalysis}, + analysis::{ + core::{alias_analysis::mfp::MfpAliasAnalyzer, api_dependency}, + scan::ScanAnalysis, + }, cli::{AliasStrategyKind, AnalysisKind, Commands, ExtractKind, OptLevel, RapxArgs}, }; use analysis::{ @@ -132,7 +137,6 @@ impl Callbacks for RapCallback { } fn after_analysis<'tcx>(&mut self, _compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation { rap_trace!("Execute after_analysis() of compiler callbacks"); - rustc_public::rustc_internal::run(tcx, || { def_id::init(tcx); if self.is_building_test_crate() { @@ -144,8 +148,8 @@ impl Callbacks for RapCallback { } }) .expect("Failed to run rustc_public."); - rap_trace!("analysis done"); + rap_trace!("analysis done"); Compilation::Continue } } @@ -201,7 +205,7 @@ pub fn start_analyzer(tcx: TyCtxt, callback: &RapCallback) { } }, - &Commands::Analyze { kind } => match kind { + Commands::Analyze { kind } => match kind { AnalysisKind::Alias { strategy } => { let alias = match strategy { AliasStrategyKind::Mop => { @@ -217,15 +221,20 @@ pub fn start_analyzer(tcx: TyCtxt, callback: &RapCallback) { }; rap_info!("{}", FnAliasMapWrapper(alias)); } - AnalysisKind::Adg => { - let mut analyzer = ApiDependencyAnalyzer::new( - tcx, - analysis::core::api_dependency::Config { - pub_only: true, - resolve_generic: true, + AnalysisKind::Adg(args) => { + let config = api_dependency::Config { + resolve_generic: true, + visit_config: api_dependency::VisitConfig { + pub_only: !args.include_private, + include_generic: true, ignore_const_generic: true, + include_unsafe: args.include_unsafe, + include_drop: args.include_drop, }, - ); + max_generic_search_iteration: args.max_iteration, + dump: args.dump.clone(), + }; + let mut analyzer = ApiDependencyAnalyzer::new(tcx, config); analyzer.run(); } AnalysisKind::Upg => { @@ -246,7 +255,7 @@ pub fn start_analyzer(tcx: TyCtxt, callback: &RapCallback) { } ); } - AnalysisKind::Dataflow { debug } => { + &AnalysisKind::Dataflow { debug } => { if debug { let mut analyzer = DataFlowAnalyzer::new(tcx, true); analyzer.run(); @@ -271,7 +280,7 @@ pub fn start_analyzer(tcx: TyCtxt, callback: &RapCallback) { let result = analyzer.get_all_path_constraints(); rap_info!("{}", PathConstraintMapWrapper(result)); } - AnalysisKind::Range { debug } => { + &AnalysisKind::Range { debug } => { let mut analyzer = RangeAnalyzer::::new(tcx, debug); analyzer.run(); let result = analyzer.get_all_fn_ranges(); diff --git a/rapx/tests/adg/bug-regression/Cargo.toml b/rapx/tests/adg/bug-regression/Cargo.toml new file mode 100644 index 00000000..33f3a405 --- /dev/null +++ b/rapx/tests/adg/bug-regression/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "bug-regression" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/rapx/tests/adg/bug-regression/src/lib.rs b/rapx/tests/adg/bug-regression/src/lib.rs new file mode 100644 index 00000000..8e099ade --- /dev/null +++ b/rapx/tests/adg/bug-regression/src/lib.rs @@ -0,0 +1,16 @@ +#![feature(allocator_api)] +use std::alloc::Global; +// nested type +// fuzzable check should not cause stack overflow +pub struct A { + pub a: Vec, + pub b: Vec, +} + +pub fn dummy(a: A) {} + +pub fn higher_order_trait() +where + for<'a> &'a T: Default, +{ +} diff --git a/rapx/tests/adg/simple-graph/Cargo.toml b/rapx/tests/adg/simple-graph/Cargo.toml new file mode 100644 index 00000000..2e4f3a41 --- /dev/null +++ b/rapx/tests/adg/simple-graph/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "simple-graph" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/rapx/tests/adg/simple-graph/src/lib.rs b/rapx/tests/adg/simple-graph/src/lib.rs new file mode 100644 index 00000000..851e79c2 --- /dev/null +++ b/rapx/tests/adg/simple-graph/src/lib.rs @@ -0,0 +1,13 @@ +pub struct Item(u32); + +pub fn foo() -> Item { + Item(0) +} + +pub fn bar(_item: Item) -> Vec { + vec![0] +} + +pub fn vec_arg(_vec: Vec) -> String { + "hi".to_string() +} \ No newline at end of file diff --git a/rapx/tests/snapshots/tests__adg_simple_graph.snap b/rapx/tests/snapshots/tests__adg_simple_graph.snap new file mode 100644 index 00000000..9ee42f03 --- /dev/null +++ b/rapx/tests/snapshots/tests__adg_simple_graph.snap @@ -0,0 +1,33 @@ +--- +source: tests/tests.rs +expression: graph_str +--- +nodes: +- type: Api + path: foo + generic_args: [] +- type: Ty + path: Item +- type: Api + path: bar + generic_args: [] +- type: Ty + path: std::vec::Vec:: +- type: Api + path: vec_arg + generic_args: [] +edges: +- from: 0 + to: 1 + kind: + type: Ret +- from: 1 + to: 2 + kind: + type: Arg + no: 0 +- from: 3 + to: 4 + kind: + type: Arg + no: 0 diff --git a/rapx/tests/tests.rs b/rapx/tests/tests.rs index 7ec81566..de5007af 100644 --- a/rapx/tests/tests.rs +++ b/rapx/tests/tests.rs @@ -1,7 +1,8 @@ #![allow(clippy::bool_assert_comparison)] use fs4::fs_std::FileExt; +use insta::assert_snapshot; use std::fs::File; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::Command; /// Checks if any bug message in the output has confidence > 50 @@ -20,10 +21,16 @@ pub fn detected_high_confidence(output: &str) -> bool { }) } +#[inline(always)] +fn project_path(dir: &str) -> PathBuf { + Path::new("tests").join(dir) +} + #[inline(always)] fn run_with_args(dir: &str, args: &[&str]) -> String { - let raw_path = "./tests/".to_owned() + dir; - let project_path = Path::new(&raw_path); + // let raw_path = "./tests/".to_owned() + dir; + // let project_path = Path::new(&raw_path); + let project_path = project_path(dir); let lock_path = project_path.join(".rapx-test.lock"); let lock_file = File::create(&lock_path).expect("Failed to create lock file"); @@ -69,6 +76,7 @@ const ANALYZE_SSA_CMD: &[&str] = &["analyze", "ssa"]; const ANALYZE_RANGE_CMD: &[&str] = &["analyze", "range"]; const ANALYZE_CALLGRAPH_CMD: &[&str] = &["analyze", "callgraph"]; const AUDIT_UNSAFE_APIS_CMD: &[&str] = &["extract", "unsafe-apis"]; +const ANALYZE_ADG_CMD: &[&str] = &["analyze", "adg", "--dump", "api_graph.yml"]; // ================Dangling Pointer Detection Test===================== #[test] @@ -556,3 +564,17 @@ fn test_extract_unsafe_apis() { assert_contain(&output, "\"safety_doc\""); assert_contain(&output, "The pointer must be valid and non-null."); } + +#[test] +fn test_adg_bug() { + // This test pass if don't panic (e.g., stack overflow) during ADG construction and resolution. + let _ = run_with_args("adg/bug-regression", ANALYZE_ADG_CMD); +} + +#[test] +fn test_adg_simple_graph() { + let _ = run_with_args("adg/simple-graph", ANALYZE_ADG_CMD); + let graph_str = std::fs::read_to_string(project_path("adg/simple-graph").join("api_graph.yml")) + .expect("read api_graph.yml fail"); + assert_snapshot!(graph_str); +}