diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index bca3186..9600ff8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -34,9 +34,9 @@ jobs: - name: cargo sort run: cargo sort --check - name: Build - run: cargo build --features "serde" + run: cargo build --features serde - name: Run tests - run: cargo test -- --test-threads=1 + run: cargo test --features serde -- --test-threads=1 - name: fmt run: cargo fmt -v -- --check - name: lint diff --git a/Cargo.lock b/Cargo.lock index 657c0be..eecc903 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.20" @@ -81,12 +90,27 @@ version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + [[package]] name = "arrayvec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + [[package]] name = "assert_cmd" version = "2.0.16" @@ -109,6 +133,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + [[package]] name = "binread" version = "2.2.0" @@ -132,6 +162,21 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "2.6.0" @@ -166,9 +211,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "candid" -version = "0.10.10" +version = "0.10.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c30ee7f886f296b6422c0ff017e89dd4f831521dfdcc76f3f71aae1ce817222" +checksum = "1da3f1b775346993796ca20ad395f109a6a4ba641a5108d782fb3c9549da9f6d" dependencies = [ "anyhow", "binread", @@ -184,14 +229,14 @@ dependencies = [ "serde", "serde_bytes", "stacker", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "candid_derive" -version = "0.6.6" +version = "0.10.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de398570c386726e7a59d9887b68763c481477f9a043fb998a2e09d428df1a9" +checksum = "bf2915251e5a810fe81552e8f656137eac44b8b5046fbb81970117d83eba97d1" dependencies = [ "lazy_static", "proc-macro2", @@ -199,6 +244,28 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "candid_parser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3b88c9197c70f2d98205a2f8993aa4125e1f96c927532309556c45548bd4da6" +dependencies = [ + "anyhow", + "candid", + "codespan-reporting", + "convert_case", + "handlebars", + "hex", + "lalrpop", + "lalrpop-util", + "logos", + "num-bigint", + "pretty", + "serde", + "thiserror 1.0.69", + "toml", +] + [[package]] name = "cc" version = "1.2.1" @@ -272,6 +339,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core2" version = "0.4.0" @@ -299,6 +375,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.6" @@ -353,6 +435,41 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.87", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.87", +] + [[package]] name = "dary_heap" version = "0.3.7" @@ -365,6 +482,37 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.87", +] + [[package]] name = "difflib" version = "0.4.0" @@ -381,6 +529,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -393,6 +562,15 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -421,6 +599,18 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.4" @@ -437,6 +627,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.26.2" @@ -448,6 +649,22 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "handlebars" +version = "6.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" +dependencies = [ + "derive_builder", + "log", + "num-order", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -500,13 +717,15 @@ dependencies = [ "anyhow", "assert_cmd", "candid", + "candid_parser", "clap", "libflate", + "parse-display", "rustc-demangle", "serde", "serde_json", "tempfile", - "thiserror", + "thiserror 1.0.69", "walrus", "wasm-opt", "wasmparser 0.223.0", @@ -518,11 +737,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1762deb6f7c8d8c2bdee4b6c5a47b60195b74e9b5280faa5ba29692f8e17429c" dependencies = [ + "arbitrary", "crc32fast", "data-encoding", "serde", "sha2", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -531,6 +751,12 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "indexmap" version = "1.9.3" @@ -558,6 +784,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -573,6 +808,37 @@ dependencies = [ "libc", ] +[[package]] +name = "lalrpop" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -615,6 +881,16 @@ dependencies = [ "rle-decode-fast", ] +[[package]] +name = "libredox" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "link-cplusplus" version = "1.0.9" @@ -630,18 +906,67 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "logos" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7251356ef8cb7aec833ddf598c6cb24d17b689d20b993f9d11a3d764e34e6458" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f80069600c0d66734f5ff52cc42f2dabd6b29d205f333d61fd7832e9e9963f" +dependencies = [ + "beef", + "fnv", + "lazy_static", + "proc-macro2", + "quote", + "regex-syntax", + "syn 2.0.87", +] + +[[package]] +name = "logos-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24fb722b06a9dc12adb0963ed585f19fc61dc5413e6a9be9422ef92c091e731d" +dependencies = [ + "logos-codegen", +] + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "num-bigint" version = "0.4.6" @@ -662,6 +987,21 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-modular" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" + +[[package]] +name = "num-order" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" +dependencies = [ + "num-modular", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -677,12 +1017,135 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "parse-display" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287d8d3ebdce117b8539f59411e4ed9ec226e0a4153c7f55495c6070d68e6f72" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax", +] + +[[package]] +name = "parse-display-derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc048687be30d79502dea2f623d052f3a074012c6eac41726b7ab17213616b1" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn 2.0.87", +] + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pest" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" +dependencies = [ + "memchr", + "thiserror 2.0.12", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "pest_meta" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.7.0", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "predicates" version = "3.1.2" @@ -748,11 +1211,54 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rle-decode-fast" @@ -791,6 +1297,21 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "scratch" version = "1.0.7" @@ -844,6 +1365,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "sha2" version = "0.10.8" @@ -861,6 +1391,18 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -880,12 +1422,47 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "structmeta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn 2.0.87", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "strum" version = "0.24.1" @@ -940,6 +1517,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -961,7 +1549,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -975,6 +1572,60 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.7.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "typed-arena" version = "2.0.2" @@ -987,18 +1638,36 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "utf8parse" version = "0.2.2" @@ -1020,6 +1689,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "walrus" version = "0.22.0" @@ -1048,6 +1727,12 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasm-encoder" version = "0.212.0" @@ -1068,7 +1753,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror", + "thiserror 1.0.69", "wasm-opt-cxx-sys", "wasm-opt-sys", ] @@ -1124,6 +1809,22 @@ dependencies = [ "serde", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" @@ -1133,6 +1834,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" @@ -1215,6 +1922,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 1a88345..ef44d31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,16 +22,19 @@ path = "src/bin/main.rs" required-features = ["exe"] [features] -default = ["exe", "wasm-opt"] -exe = ["anyhow", "clap", "serde"] -wasm-opt = ["dep:wasm-opt", "tempfile"] +check-endpoints = ["dep:anyhow", "dep:candid_parser", "dep:parse-display"] +default = ["check-endpoints", "exe", "wasm-opt"] +exe = ["dep:anyhow", "dep:clap", "dep:serde"] +wasm-opt = ["dep:wasm-opt", "dep:tempfile"] serde = ["dep:serde", "dep:serde_json"] [dependencies] anyhow = { version = "1.0.34", optional = true } candid = "0.10" +candid_parser = { version = "0.2.1", optional = true } clap = { version = "4.1", features = ["derive", "cargo"], optional = true } libflate = "2.0" +parse-display = { version = "0.10.0", optional = true } rustc-demangle = "0.1" serde = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true } @@ -40,7 +43,6 @@ thiserror = "1.0.35" # Major version bump of walrus should result in a major version bump of ic-wasm. # Because we expose walrus types in ic-wasm public API. walrus = "0.22.0" - wasm-opt = { version = "0.116.0", optional = true } wasmparser = "0.223.0" diff --git a/src/bin/main.rs b/src/bin/main.rs index f486939..62399d5 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,4 +1,6 @@ use clap::{crate_authors, crate_version, Parser}; +#[cfg(feature = "check-endpoints")] +use ic_wasm::check_endpoints::{check_endpoints, CanisterEndpoint}; use ic_wasm::utils::make_validator_with_features; use std::path::PathBuf; @@ -97,6 +99,17 @@ enum SubCommand { #[clap(short, long, requires("start_page"))] page_limit: Option, }, + /// Check canister endpoints against provided Candid interface + #[cfg(feature = "check-endpoints")] + CheckEndpoints { + /// Candid interface file + #[clap(short, long)] + candid: PathBuf, + /// Optionally specify hidden endpoints, i.e., endpoints that are exposed by the canister but + /// not present in the Candid interface file. + #[arg(long)] + hidden: Vec, + }, } fn main() -> anyhow::Result<()> { @@ -126,7 +139,7 @@ fn main() -> anyhow::Result<()> { } } #[cfg(not(feature = "serde"))] - SubCommand::Info => { + SubCommand::Info {} => { let wasm_info = ic_wasm::info::WasmInfo::from(&m); print!("{wasm_info}"); } @@ -209,6 +222,10 @@ fn main() -> anyhow::Result<()> { return Ok(()); } } + #[cfg(feature = "check-endpoints")] + SubCommand::CheckEndpoints { candid, hidden } => { + return check_endpoints(&m, candid, hidden); + } }; // validate new module let module_bytes = m.emit_wasm(); diff --git a/src/check_endpoints/candid/mod.rs b/src/check_endpoints/candid/mod.rs new file mode 100644 index 0000000..53ffd49 --- /dev/null +++ b/src/check_endpoints/candid/mod.rs @@ -0,0 +1,51 @@ +use crate::check_endpoints::CanisterEndpoint; +use anyhow::{Error, Result}; +use candid::types::{FuncMode, Function, TypeInner}; +use candid_parser::utils::CandidSource; +use std::collections::BTreeSet; +use std::path::PathBuf; + +pub struct CandidParser { + path: PathBuf, +} + +impl CandidParser { + pub fn new(path: impl Into) -> Self { + Self { path: path.into() } + } + + pub fn parse(&self) -> Result> { + let (_, maybe_actor) = CandidSource::File(&self.path).load()?; + + let maybe_class = + maybe_actor.ok_or_else(|| Error::msg("Top-level actor definition not found"))?; + + let maybe_service = match maybe_class.as_ref() { + TypeInner::Class(_, class) => class, + _ => return Err(Error::msg("Top-level class definition not found")), + }; + + let maybe_functions = match maybe_service.as_ref() { + TypeInner::Service(maybe_functions) => maybe_functions, + _ => return Err(Error::msg("Top-level service definition not found")), + }; + + let functions = maybe_functions + .iter() + .filter_map(|(name, maybe_function)| { + if let TypeInner::Func(Function { modes, .. }) = maybe_function.as_ref() { + if modes.contains(&FuncMode::Query) || modes.contains(&FuncMode::CompositeQuery) + { + Some(CanisterEndpoint::Query(name.to_string())) + } else { + Some(CanisterEndpoint::Update(name.to_string())) + } + } else { + None + } + }) + .collect(); + + Ok(functions) + } +} diff --git a/src/check_endpoints/mod.rs b/src/check_endpoints/mod.rs new file mode 100644 index 0000000..0c8ac69 --- /dev/null +++ b/src/check_endpoints/mod.rs @@ -0,0 +1,103 @@ +mod candid; + +pub use crate::check_endpoints::candid::CandidParser; +use crate::info::ExportedMethodInfo; +use crate::utils::get_exported_methods; +use anyhow::anyhow; +use parse_display::{Display, FromStr}; +use std::collections::BTreeSet; +use std::path::PathBuf; +use walrus::Module; + +#[derive(Clone, Eq, Debug, Ord, PartialEq, PartialOrd, Display, FromStr)] +pub enum CanisterEndpoint { + #[display("update:{0}")] + Update(String), + #[display("query:{0}")] + Query(String), + #[display("composite_query:{0}")] + CompositeQuery(String), +} + +impl TryFrom<&ExportedMethodInfo> for CanisterEndpoint { + type Error = anyhow::Error; + + fn try_from(method: &ExportedMethodInfo) -> Result { + const CANISTER_QUERY_PREFIX: &str = "canister_query "; + const CANISTER_UPDATE_PREFIX: &str = "canister_update "; + const CANISTER_COMPOSITE_QUERY_PREFIX: &str = "canister_composite_query "; + + method + .name + .strip_prefix(CANISTER_QUERY_PREFIX) + .map(|q| CanisterEndpoint::Query(q.to_string())) + .or_else(|| { + method + .name + .strip_prefix(CANISTER_UPDATE_PREFIX) + .map(|u| CanisterEndpoint::Update(u.to_string())) + }) + .or_else(|| { + method + .name + .strip_prefix(CANISTER_COMPOSITE_QUERY_PREFIX) + .map(|u| CanisterEndpoint::CompositeQuery(u.to_string())) + }) + .ok_or_else(|| { + anyhow!( + "Invalid exported method in canister WASM: '{}'", + method.name + ) + }) + } +} + +pub fn check_endpoints( + module: &Module, + candid_path: impl Into, + hidden_endpoints: &[CanisterEndpoint], +) -> anyhow::Result<()> { + let wasm_endpoints = get_exported_methods(module) + .iter() + .map(CanisterEndpoint::try_from) + .collect::, _>>()?; + + let candid_endpoints = CandidParser::new(candid_path).parse()?; + + let missing_candid_endpoints = candid_endpoints + .difference(&wasm_endpoints) + .collect::>(); + missing_candid_endpoints.iter().for_each(|endpoint| { + eprintln!( + "ERROR: The following Candid endpoint is missing from the WASM exports section: {endpoint}" + ); + }); + + let missing_hidden_endpoints = BTreeSet::from(hidden_endpoints) + .difference(&wasm_endpoints) + .collect::>(); + missing_hidden_endpoints.iter().for_each(|endpoint| { + eprintln!( + "ERROR: The following hidden endpoint is missing from the WASM exports section: {endpoint}" + ); + }); + + let unexpected_endpoints = wasm_endpoints + .iter() + .filter(|endpoint| { + !candid_endpoints.contains(endpoint) && !hidden_endpoints.contains(endpoint) + }) + .collect::>(); + unexpected_endpoints.iter().for_each(|endpoint| { + eprintln!( + "ERROR: The following endpoint is unexpected in the WASM exports section: {endpoint}" + ); + }); + + if !missing_candid_endpoints.is_empty() || !missing_hidden_endpoints.is_empty() || !unexpected_endpoints.is_empty() { + Err(anyhow!("Canister WASM and Candid interface do not match!")) + } else { + println!("Canister WASM and Candid interface match!"); + Ok(()) + } +} diff --git a/src/info.rs b/src/info.rs index d2cb481..6857c50 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,6 +1,6 @@ use std::fmt; use std::io::Write; -use walrus::{ExportItem, Module}; +use walrus::Module; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -34,9 +34,10 @@ pub enum LanguageSpecificInfo { /// Information about an exported method. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug)] pub struct ExportedMethodInfo { - name: String, - internal_name: String, + pub name: String, + pub internal_name: String, } /// Statistics about a custom section. @@ -62,17 +63,7 @@ impl From<&Module> for WasmInfo { number_of_functions: m.funcs.iter().count(), number_of_callbacks: m.elements.iter().count(), start_function: m.start.map(|id| get_func_name(m, id)), - exported_methods: m - .exports - .iter() - .filter_map(|e| match e.item { - ExportItem::Function(id) => Some(ExportedMethodInfo { - name: e.name.clone(), - internal_name: get_func_name(m, id), - }), - _ => None, - }) - .collect(), + exported_methods: get_exported_methods(m), imported_ic0_system_api: m .imports .iter() diff --git a/src/lib.rs b/src/lib.rs index 9df083c..9b60204 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "check-endpoints")] +pub mod check_endpoints; pub mod info; pub mod instrumentation; pub mod limit_resource; diff --git a/src/utils.rs b/src/utils.rs index 0382a7b..747fbe9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,4 @@ +use crate::info::ExportedMethodInfo; use crate::Error; use libflate::gzip; use std::borrow::Cow; @@ -324,6 +325,19 @@ pub(crate) fn inject_top(builder: &mut InstrSeqBuilder<'_>, instrs: Vec Vec { + m.exports + .iter() + .filter_map(|e| match e.item { + ExportItem::Function(id) => Some(ExportedMethodInfo { + name: e.name.clone(), + internal_name: get_func_name(m, id), + }), + _ => None, + }) + .collect() +} + pub(crate) fn get_func_name(m: &Module, id: FunctionId) -> String { m.funcs .get(id) diff --git a/tests/tests.rs b/tests/tests.rs index 195c8ba..0505677 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,7 +1,9 @@ use assert_cmd::Command; use std::fs; +use std::io::Write; use std::path::Path; +use tempfile::NamedTempFile; fn wasm_input(wasm: &str, output: bool) -> Command { let mut cmd = Command::cargo_bin("ic-wasm").unwrap(); @@ -394,3 +396,70 @@ fn metadata_keep_name_section() { assert_functions_are_named(); } } + +#[test] +fn check_endpoints() { + wasm_input("wat.wasm", false) + .arg("check-endpoints") + .arg("--candid") + .arg( + create_tempfile( + r#" +service : () -> { + get : (key: text) -> (text) query; + inc : (owner: opt principal) -> (nat); + set : (key: text, value: text) -> (); +} +"#, + ) + .path(), + ) + .assert() + .stdout("Canister WASM and Candid interface match!\n") + .success(); + wasm_input("wat.wasm.gz", false) + .arg("check-endpoints") + .arg("--candid") + .arg( + create_tempfile( + r#" +service : () -> { + get : (key: text) -> (text) query; + inc : (owner: opt principal) -> (nat); +} +"#, + ) + .path(), + ) + .arg("--hidden") + .arg("update:set") + .assert() + .stdout("Canister WASM and Candid interface match!\n") + .success(); + wasm_input("wat.wasm.gz", false) + .arg("check-endpoints") + .arg("--candid") + .arg( + create_tempfile( + r#" +service : () -> { + inc : (owner: opt principal) -> (nat); +} +"#, + ) + .path(), + ) + .arg("--hidden") + .arg("update:set") + .arg("--hidden") + .arg("query:get") + .assert() + .stdout("Canister WASM and Candid interface match!\n") + .success(); +} + +fn create_tempfile(content: &str) -> NamedTempFile { + let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); + write!(temp_file, "{content}").expect("Failed to write temp file content"); + temp_file +}