diff --git a/.clippy.toml b/.clippy.toml index bb5bda0..1228d7c 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,7 +1,7 @@ -msrv = "1.64.0" # MSRV +msrv = "1.76.0" # MSRV disallowed-methods = [ { path = "std::option::Option::map_or", reason = "use `map(..).unwrap_or(..)`" }, { path = "std::option::Option::map_or_else", reason = "use `map(..).unwrap_or_else(..)`" }, { path = "std::result::Result::map_or", reason = "use `map(..).unwrap_or(..)`" }, { path = "std::result::Result::map_or_else", reason = "use `map(..).unwrap_or_else(..)`" }, -] \ No newline at end of file +] diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8139a93..05e9ab9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,11 +3,11 @@ updates: - package-ecosystem: github-actions directory: / schedule: - interval: daily + interval: weekly - package-ecosystem: cargo directory: / schedule: - interval: daily + interval: weekly ignore: - dependency-name: "*" # patch and minor updates don't matter for libraries diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4ed9621..a2ec923 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -79,7 +79,7 @@ jobs: # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability strategy: matrix: - msrv: [1.64.0] # 2021 edition requires 1.56 + msrv: [1.76.0] # 2021 edition requires 1.56 name: ubuntu / ${{ matrix.msrv }} steps: - uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index ca8033e..d89e0a3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ target/ # Coverage report files lcov.info + +# macOS +.DS_Store diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..693ea5c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,64 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'wasm2map'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=wasm2map" + ], + "filter": { + "name": "wasm2map", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'cargo-wasm2map'", + "cargo": { + "args": [ + "build", + "--bin=cargo-wasm2map", + "--package=cargo-wasm2map" + ], + "filter": { + "name": "cargo-wasm2map", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'cargo-wasm2map'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=cargo-wasm2map", + "--package=cargo-wasm2map" + ], + "filter": { + "name": "cargo-wasm2map", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 2893935..312bb0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,93 +3,105 @@ version = 3 [[package]] -name = "adler" -version = "1.0.0" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbe262130bf8e280f95bb7f04f74a82dca9950bbdc12c68b4d3dab0112494d37" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "anstream" -version = "0.3.0" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "1.0.0" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] -name = "autocfg" -version = "1.0.0" +name = "base64-simd" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +checksum = "781dd20c3aff0bd194fe7d2a977dd92f21c173891f3a03b677359e5fa457e5d5" +dependencies = [ + "simd-abstraction", +] [[package]] name = "bitflags" -version = "1.2.1" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bitvec" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] [[package]] name = "byteorder" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffea412272c01cbee45e0d34f71c54af698d48f7d404a61fb46b71f48e3f30db" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cargo-wasm2map" -version = "0.1.0" +version = "0.2.0" dependencies = [ + "anstyle", + "anstyle-parse", "clap", + "clap_lex", "proc-macro2", "wasm2map", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -98,20 +110,19 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.3.10" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384e169cc618c613d5e3ca6404dda77a8685a63e08660dcc64abaf7da7cb0c7a" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.3.10" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef137bbe35aab78bdb468ccfba75a5f4d8321ae011d34063770780545176af2d" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -121,204 +132,338 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.2" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.8", + "syn", ] [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "crc32fast" -version = "1.2.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ - "cfg-if 0.1.10", + "cfg-if", ] [[package]] -name = "errno" -version = "0.2.8" +name = "data-encoding" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ - "errno-dragonfly", - "libc", - "winapi", + "serde", + "uuid", ] [[package]] -name = "errno-dragonfly" -version = "0.1.1" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "gcc", - "libc", + "proc-macro2", + "quote", + "syn", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] -name = "fuchsia-zircon" -version = "0.3.1" +name = "form_urlencoded" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b5365afd01fdf916e775a224e844f80b3b9710d0f4f00903e219e859474d7ae" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "bitflags", - "fuchsia-zircon-sys", + "percent-encoding", ] [[package]] -name = "fuchsia-zircon-sys" -version = "0.3.1" +name = "funty" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069def9a0e5feb7e9120635f6ebad24d853a6affbb077fec84d0888316cf9ae6" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "gcc" -version = "0.3.4" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd5de9bef6f37b6e743c425657ce3642dbc48152fce88199a6d6344e2a527e1" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" [[package]] -name = "gimli" -version = "0.27.2" +name = "getrandom" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] [[package]] -name = "hashbrown" -version = "0.12.0" +name = "gimli" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +dependencies = [ + "fallible-iterator", + "stable_deref_trait", +] [[package]] name = "heck" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "hermit-abi" -version = "0.3.0" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856b5cb0902c2b6d65d5fd97dfa30f9b70c7538e770b98eab5ed52d8db923e01" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] [[package]] -name = "idna" -version = "0.2.0" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "indexmap" -version = "1.9.1" +name = "icu_locid_transform" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ - "autocfg", - "hashbrown", + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "io-lifetimes" -version = "1.0.1" +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d367024b3f3414d8e01f437f704f41a9f64ab36f9067fa73e526ad4c763c87" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ - "libc", - "windows-sys 0.42.0", + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", ] [[package]] -name = "is-terminal" -version = "0.4.4" +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix", - "windows-sys 0.45.0", + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "libc" -version = "0.2.133" +name = "icu_properties_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" [[package]] -name = "linux-raw-sys" -version = "0.1.2" +name = "icu_provider" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb68f22743a3fb35785f1e7f844ca5a3de2dde5bd0c0ef5b372065814699b121" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] [[package]] -name = "matches" -version = "0.1.2" +name = "icu_provider_macros" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15305656809ce5a4805b1ff2946892810992197ce1270ff79baded852187942e" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + +[[package]] +name = "ilog" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d6896d5a5d605a3b80da159ccc5a6e1fad346b4d2c1ad51046e22536b9a362" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "memchr" -version = "2.4.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" -version = "0.5.10" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ - "adler", + "adler2", ] +[[package]] +name = "normalize-path" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5438dd2b2ff4c6df6e1ce22d825ed2fa93ee2922235cc45186991717f0a892d" + [[package]] name = "object" -version = "0.31.1" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "flate2", "memchr", @@ -327,367 +472,545 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.12.0" +name = "outref" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" [[package]] name = "percent-encoding" -version = "2.0.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f28a6faf4ffea762ba8f4baef48c61a6db348647c73095034041fc79dd954" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro2" -version = "1.0.60" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" -version = "0.4.1" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5f78082e6a6d042862611e9640cf20776185fee506cf6cf67e93c6225cee31" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "fuchsia-zircon", "libc", + "rand_chacha", + "rand_core", ] [[package]] -name = "rustix" -version = "0.36.4" +name = "rand_chacha" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys 0.42.0", + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "ruzstd" -version = "0.3.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a15e661f0f9dac21f3494fe5d23a6338c0ac116a2d22c2b63010acd89467ffe" +checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f" dependencies = [ - "byteorder", - "thiserror", "twox-hash", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.134" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "simd-abstraction" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cadb29c57caadc51ff8346233b5cec1d240b68ce55cf1afc764818791876987" +dependencies = [ + "outref", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "sourcemap" +version = "9.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c4ea7042fd1a155ad95335b5d505ab00d5124ea0332a06c8390d200bb1a76a" +dependencies = [ + "base64-simd", + "bitvec", + "data-encoding", + "debugid", + "if_chain", + "rustc-hash", + "rustc_version", + "serde", + "serde_json", + "unicode-id-start", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa13613355688665b68639b1c378a62dbedea78aff0fc59a4fa656cbbdec657" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "1.0.11" +version = "2.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" +checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] -name = "syn" -version = "2.0.8" +name = "synstructure" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "thiserror" -version = "1.0.7" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e37992ba0f05f3aa079be7f453c91b55d15a01a7aad202baa9c6fa48c3e6b5" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.7" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3308bffe57639095bdb057e73850afecf2730c3b5539af96e6ae096644f4db" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" dependencies = [ "proc-macro2", "quote", - "syn 1.0.11", + "syn", ] [[package]] -name = "twox-hash" -version = "1.6.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "cfg-if 0.1.10", - "static_assertions", + "displaydoc", + "zerovec", ] [[package]] -name = "unicode-bidi" -version = "0.3.0" +name = "twox-hash" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2560b941fdb9ea38301b9b708504d612fcdf9c91a8c31d82219bd74cb07d304d" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "matches", + "cfg-if", + "static_assertions", ] [[package]] -name = "unicode-ident" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" - -[[package]] -name = "unicode-normalization" -version = "0.1.5" +name = "unicode-id-start" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" +checksum = "2f322b60f6b9736017344fa0635d64be2f458fbc04eef65f6be22976dd1ffd5b" [[package]] -name = "unicode-xid" -version = "0.2.0" +name = "unicode-ident" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "url" -version = "2.0.0" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ddaf52e65c6b81c56b7e957c0b1970f7937f21c5c6774c4e56fcb4e20b48c6" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ + "form_urlencoded", "idna", - "matches", "percent-encoding", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm2map" -version = "0.1.0" +version = "0.2.0" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "flate2", "gcc", "gimli", + "ilog", "memmap2", + "normalize-path", "object", "rand", + "rustc_version", + "rustversion", + "serde_json", + "sourcemap", + "tap", "thiserror", + "unicode-id-start", ] [[package]] name = "wasmparser" -version = "0.102.0" +version = "0.222.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48134de3d7598219ab9eaf6b91b15d8e50d31da76b8519fe4ecfcec2cf35104b" +checksum = "4adf50fde1b1a49c1add6a80d47aea500c88db70551805853aa8b88f3ea27ab5" dependencies = [ - "indexmap", - "url", + "bitflags", ] [[package]] -name = "winapi" -version = "0.3.0" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3ad91d846a4a5342c1fb7008d26124ee6cf94a3953751618577295373b32117" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows-targets", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.3.0" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a16a8e2ebfc883e2b1771c6482b1fb3c6831eab289ba391619a2d93a7356220f" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.3.0" +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca29cb03c8ceaf20f8224a18a530938305e9872b1478ea24ff44b4f503a1d1d" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows-sys" -version = "0.42.0" +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.1", - "windows_x86_64_msvc 0.42.1", -] +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows-sys" -version = "0.45.0" +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.1", -] +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.0", -] +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows-targets" -version = "0.42.1" +name = "windows_i686_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.1", - "windows_x86_64_msvc 0.42.1", -] +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows-targets" -version = "0.48.0" +name = "windows_x86_64_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.1" +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "windows_aarch64_msvc" -version = "0.42.1" +name = "write16" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" +name = "writeable" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] -name = "windows_i686_gnu" -version = "0.42.1" +name = "wyz" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] [[package]] -name = "windows_i686_msvc" -version = "0.42.1" +name = "yoke" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] [[package]] -name = "windows_i686_msvc" -version = "0.48.0" +name = "yoke-derive" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] [[package]] -name = "windows_x86_64_gnu" -version = "0.42.1" +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] [[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" +name = "zerocopy-derive" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.1" +name = "zerofrom" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" +name = "zerofrom-derive" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.42.1" +name = "zerovec" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" +name = "zerovec-derive" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/README.md b/README.md index 071f164..2ecf2d5 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,18 @@ ![Build status](https://github.com/mtolmacs/wasm2map/actions/workflows/test.yml/badge.svg) [![crates.io](https://img.shields.io/crates/v/wasm2map.svg)](https://crates.io/crates/wasm2map) [![Documentation](https://docs.rs/wasm2map/badge.svg)](https://docs.rs/wasm2map) -![Min Rust 1.64.0](https://badgen.net/badge/Min%20Rust/1.64.0) +![Min Rust 1.76.0](https://badgen.net/badge/Min%20Rust/1.76.0) Generates a browser-supported sourcemap for WASM binaries containing DWARF debug information and associates it with the WASM binary, so when loaded in the browser you can see the rust line, character and source code (if available) in the debug panel and console. NOTE: Can build without unsafe code (the only unsafe code is related to using the memmap2 crate). ### Before + ![Before WASM sourcemapping](https://raw.githubusercontent.com/mtolmacs/wasm2map/main/assets/before.png) ### After + ![After WASM sourcemapping](https://raw.githubusercontent.com/mtolmacs/wasm2map/main/assets/after.png) # Usage @@ -46,12 +48,18 @@ let mapper = WASM::load("/path/to/the/file.wasm"); } ``` +# Current limitations + +- It does not bundle the source code in the sourcemap currently, so source browsing will not work in the browser + # Contribution + Your contributions are welcome, especially bug reports and testing on various platforms. Feel free to open a PR if you can contribute a fix. If you would like to contribute an API change, extension or a new trait implementation, please open an issue first and discuss before starting work on a PR. For details please read the CONTRIBUTING.md file. # License + Licensed under either of Apache License, Version 2.0 or MIT license at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/cargo-wasm2map/Cargo.toml b/cargo-wasm2map/Cargo.toml index 06db51b..216e8d2 100644 --- a/cargo-wasm2map/Cargo.toml +++ b/cargo-wasm2map/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-wasm2map" -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = ["Mark Tolmacs "] description = "Cargo command which generates source map for .wasm file with DWARF debug info embedded as source map." @@ -10,17 +10,23 @@ repository = "https://github.com/mtolmacs/wasm2map" homepage = "https://github.com/mtolmacs/wasm2map" keywords = ["webassembly", "wasm", "debug", "sourcemap"] categories = ["command-line-utilities"] -rust-version = "1.64.0" +rust-version = "1.76.0" license = "MIT OR Apache-2.0" [dependencies] -wasm2map = { path = "../wasm2map" } # For -Zminimal-versions proc-macro2 = ">= 1.0.60" +anstyle = "^1" +anstyle-parse = "^0.2" +clap_lex = "0.7.4" + +[dependencies.wasm2map] +path = "../wasm2map" +features = ["memmap2", "loader"] [dependencies.clap] -version = "4.3.10" +version = "4.5.3" features = ["derive"] [badges] -maintenance = { status = "actively-developed" } \ No newline at end of file +maintenance = { status = "actively-developed" } diff --git a/cargo-wasm2map/src/main.rs b/cargo-wasm2map/src/main.rs index f814aaf..f6a492d 100644 --- a/cargo-wasm2map/src/main.rs +++ b/cargo-wasm2map/src/main.rs @@ -14,7 +14,7 @@ use clap::{Args, Parser}; use std::{fs, path::PathBuf}; -use wasm2map::WASM; +use wasm2map::{Loader, Wasm}; // Cargo commands receive the name of the subcommand as the main command // so we need to consume the name of our executable in order to get to the @@ -101,11 +101,19 @@ fn main() -> Result<(), String> { // TODO(mtolamcs): Test the base url parameter to make sure its a valid // url and it also does not reference the map file - // Load the WASM file to memory and parse the DWARF code section - let mut wasm = WASM::load(&args.path).map_err(|err| err.to_string())?; + // Open the WASM file + let reader = Loader::from_path(&args.path).map_err(|err| err.to_string())?; + + // Parse the DWARF code section + let wasm = Wasm::new(&reader, None, None).map_err(|err| err.to_string())?; // Generate the source map JSON for the loaded WASM - let sourcemap = wasm.map_v3(args.bundle_sources); + let sourcemap = wasm + .build( + args.bundle_sources, + Some(map.file_name().unwrap().to_str().unwrap()), + ) + .map_err(|err| err.to_string())?; // Dump JSON to the map file fs::write(&map, sourcemap).map_err(|err| err.to_string())?; @@ -113,12 +121,15 @@ fn main() -> Result<(), String> { // If patching is requested, then patch the WASM file at the parameter // with the provided source bap base url + the mapfile name if args.patch { - let url = format!( + let _url = format!( "{}/{}", args.base_url.unwrap().as_str(), map.file_name().unwrap().to_str().unwrap() ); - wasm.patch(&url).map_err(|err| err.to_string())?; + + // TODO: Patching + + //wasm.patch(&url).map_err(|err| err.to_string())?; } Ok(()) diff --git a/wasm2map/Cargo.toml b/wasm2map/Cargo.toml index 1dc9a48..30742ff 100644 --- a/wasm2map/Cargo.toml +++ b/wasm2map/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm2map" -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = ["Mark Tolmacs "] description = "Generates source map for .wasm file with DWARF debug info embedded as source map." @@ -10,40 +10,49 @@ repository = "https://github.com/mtolmacs/wasm2map" homepage = "https://github.com/mtolmacs/wasm2map" keywords = ["webassembly", "wasm", "debug", "sourcemap"] categories = ["development-tools::debugging"] -rust-version = "1.64.0" +rust-version = "1.76.0" license = "MIT OR Apache-2.0" [lib] [features] -default = ["memmap2"] +default = ["memmap2", "loader"] +loader = [] [dependencies.gimli] -version = "0.27.2" +version = "0.31.1" default-features = false -features = ["read"] +features = ["std", "read", "endian-reader"] [dependencies.memmap2] -version = "~0.5.10" +version = "~0.9.5" default-features = false optional = true [dependencies.object] -version = ">= 0.31.1" -features = [ - "wasm" -] +version = ">=0.31.1" +features = ["std", "wasm"] [dependencies] +normalize-path = "0.2.1" +serde_json = "^1" +sourcemap = "^9.1" +thiserror = ">= 1.0.7" +rustversion = "=1.0.19" +ilog = "=1.0.1" # For -Zminimal-versions cfg-if = ">= 0.1.5" rand = ">= 0.3.23" -thiserror = ">= 1.0.7" flate2 = ">= 1.0.26" +tap = ">= 1.0.1" +unicode-id-start = ">= 1.1.0" + +[dev-dependencies] +rustc_version = "*" [build-dependencies] # For -Zminimal-versions gcc = ">= 0.3.4" [badges] -maintenance = { status = "actively-developed" } \ No newline at end of file +maintenance = { status = "actively-developed" } diff --git a/wasm2map/src/_test.rs b/wasm2map/src/_test.rs new file mode 100644 index 0000000..9099829 --- /dev/null +++ b/wasm2map/src/_test.rs @@ -0,0 +1,450 @@ +use sourcemap::SourceMap; +use std::{ + fs, + ops::Deref, + path::{Path, PathBuf}, +}; + +use crate::{error::Error, json::encode, vlq, CodePoint, WASM}; + +// Consts needed to build golden versions of the binary WASM module section. +// See wasm2map::WASM::patch() doc-comment for details. +const WASM_CUSTOM_SECTION_ID: u8 = 0; +const WASM_SOURCEMAPPINGURL_SECTION_NAME: &[u8] = b"sourceMappingURL"; + +// TODO: Test sourcemap size load +// TODO: Test sourcemap generation + +/// Tests the format of the sourcemap, makes sure the JSON is valid and +/// the required keys are present, with the right type of values. +#[test] +fn can_create_valid_sourcemap_format() { + testutils::run_test(|out| { + if let Ok(mapper) = WASM::load(out) { + let sourcemap = mapper.map_v3(false); + + let parsed = serde_json::from_str::(sourcemap.as_str()) + .expect("Sourcemap is not a valid JSON file"); + let json = parsed.as_object().expect("Sourcemap is not a JSON object"); + + let version = json + .get("version") + .expect("Sourcemap JSON object has no requied version key") + .as_i64() + .expect("Sourcemap JSON version value is not an integer"); + assert!(version == 3); + + let names = json + .get("names") + .expect("Sourcemap JSON has no names key") + .as_array() + .expect("Sourcemap JSON key names is not an array"); + assert!(names.is_empty()); + + let sources = json + .get("sources") + .expect("Sourcemap JSON object has no sources key") + .as_array() + .expect("Sourcemap JSON sources value is not an array"); + assert!(!sources.is_empty()); + sources.iter().for_each(|value| { + let path = Path::new( + value + .as_str() + .expect("Sourcemap JSON sources item is not a string"), + ); + assert!(path.extension().is_some()); + }); + + let mappings = json + .get("mappings") + .expect("Sourcemap JSON object has no mappings key") + .as_str() + .expect("Sourcemap JSON key mappings is not a string"); + assert!(!mappings.is_empty()); + } else { + unreachable!() + } + }); +} + +/// The Rust library core files are included in DWARF as relative file paths. +/// This test checks if some of the Rust core files are included in the sources +/// list with some leading parent directories, meaning the relative paths in +/// DWARF are resolved. +#[test] +fn relative_paths_are_considered() { + testutils::run_test(|out| { + if let Ok(mapper) = WASM::load(out) { + let sourcemap = mapper.map_v3(false); + + assert!(sourcemap.contains("/library/core/src/any.rs")); + assert!(sourcemap.contains("/library/core/src/panicking.rs")); + } else { + unreachable!() + } + }); +} + +/// When the caller requests the bundling of source file contents, we check +/// that the generated sourcemap has the user source code for the test code. +#[test] +fn can_bundle_source() { + testutils::run_test(|out| { + if let Ok(mapper) = WASM::load(out) { + let sourcemap = mapper.map_v3(true); + fs::write("sourcemap.json", &sourcemap).expect("FAILED to write file"); + assert!(sourcemap.contains("fn main() {}")); + fs::remove_file("sourcemap.json").expect("Failed to delete temp file"); + } else { + unreachable!() + } + }); +} + +/// Check the ability of the library to modify the WASM file to add / change +/// a sourcemap. Makes sure the library does not break the WASM binary. +#[test] +fn can_add_and_update_sourcemap() { + testutils::run_test(|out| { + // Set up the test byte data + const URL: &str = "http://localhost:8080"; + let content = [ + &[WASM_SOURCEMAPPINGURL_SECTION_NAME.len() as u8], + WASM_SOURCEMAPPINGURL_SECTION_NAME, + &[URL.len() as u8], + URL.as_bytes(), + ] + .concat(); + let section = [ + &[WASM_CUSTOM_SECTION_ID] as &[u8], + &[content.len() as u8], + content.as_ref(), + ] + .concat(); + const URL2: &str = "http://127.0.0.1:8080"; + let content2 = [ + &[WASM_SOURCEMAPPINGURL_SECTION_NAME.len() as u8], + WASM_SOURCEMAPPINGURL_SECTION_NAME, + &[URL2.len() as u8] as &[u8], + URL2.as_bytes(), + ] + .concat(); + let section2 = [ + &[WASM_CUSTOM_SECTION_ID] as &[u8], + &[content2.len() as u8], + content2.as_ref(), + ] + .concat(); + + let mapper = WASM::load(&out); + if let Ok(mut mapper) = mapper { + // Patch the WASM with sourceMappingURL and check if it is applied + // correctly + if let Err(error) = mapper.patch(URL) { + panic!("Failed to patch the WASM file the first time: {}", error); + } + { + let test = testutils::peek_wasm_file_end(out.clone(), section.len()); + assert_eq!(test, section); + } + + // Update it and check if it's still valid and not duplicated + if let Err(error) = mapper.patch(URL2) { + panic!("Failed to patch the WASM file the first time: {}", error); + } + { + let test = + testutils::peek_wasm_file_end(out.clone(), section.len() + section2.len()); + + // Test if the patch just keeps adding patches or properly + // update the old one + assert_ne!( + test, + [section.as_ref() as &[u8], section2.as_ref()].concat() + ); + + // Test if the only sourceMappingURL is the last one we set + assert_eq!(test[test.len() - section2.len()..], section2); + } + + // Attempt to patch with the last one for sanity check + if let Err(error) = mapper.patch(URL2) { + panic!("Failed to patch the WASM file the first time: {}", error); + } + { + // Test if WASM binary is at least structurally valid + let raw = fs::read(&out).expect("Cannot open the WASM file"); + let obj = object::File::parse(raw.deref()); + assert!(obj.is_ok()); + } + } else { + panic!("Error loading WASM: {}", mapper.err().unwrap()); + } + + let mapper = WASM::load(&out); + if let Ok(mut mapper) = mapper { + // Attempt to patch with the last one for sanity check + if let Err(error) = mapper.patch(URL2) { + panic!("Failed to patch the WASM file the first time: {}", error); + } + { + // Test if WASM binary is at least structurally valid + let raw = fs::read(&out).expect("Cannot open the WASM file"); + let obj = object::File::parse(raw.deref()); + assert!(obj.is_ok()); + } + } else { + panic!("Error loading WASM: {}", mapper.err().unwrap()); + } + }) +} + +/// Just check if an error is raised when the target WASM binary does not exists. +/// This monitors a regression in the code previously present, where patching +/// failed silently and the user did not recognize why there is no sourcemap loaded. +#[test] +fn test_path_handles_nonexistent_wasm() { + testutils::run_test(|out| { + let mapper = WASM::load(&out); + if let Ok(mut mapper) = mapper { + // Delete the WASM file to trigger error + fs::remove_file(&out).ok(); + + // Attempt to patch with the last one for sanity check + let result = mapper.patch("http://127.0.0.1:8080"); + + assert!(result.is_err()) + } else { + panic!("Error loading WASM: {}", mapper.err().unwrap()); + } + }); +} + +/// Make sure the error type of this crate handles all needed cases. +#[test] +fn test_error_types() { + fn errors() -> Result<(), Box> { + let _error: crate::Error = + std::io::Error::new(std::io::ErrorKind::AddrInUse, "This is a test").into(); + + let dumbarray = Vec::::new(); + let _error: crate::Error = match object::File::parse(dumbarray.as_slice()) { + Ok(_) => unreachable!(), + Err(err) => err.into(), + }; + + let _error: crate::Error = gimli::Error::Io.into(); + + let _error: crate::Error = "This is a test".into(); + + let _error: crate::Error = "This is a test".to_owned().into(); + + let num: Result = u32::MAX.try_into(); + let _error: crate::Error = match num { + Ok(_) => unreachable!(), + Err(err) => err.into(), + }; + + Err(Box::from(_error)) + } + + let errors = errors(); + assert!(errors.is_err()); + + let error: crate::Error = "This is a test".into(); + assert_eq!(format!("{}", error), "This is a test"); +} + +/// Test the VLQ encoding of numbers to VLQ byte sequences, which +/// can be turned into "mappings" in the sourcemap. +#[test] +fn test_numeric_encode_to_byte_sequence() { + assert_eq!(vlq::encode_uint_var(432), vec![176, 3]) +} + +/// Check if the Debug, Display is present on types in this library +#[test] +fn test_derived_macros_present() { + testutils::run_test(|out| { + let codepoint = CodePoint { + path: PathBuf::new(), + address: 0, + line: 0, + column: 0, + }; + assert!(!format!("{:#?}", codepoint).is_empty()); + let wasm = + WASM::load(out).expect("Loading WASM file is unsuccessful in derived macros test"); + assert!(!format!("{:#?}", wasm).is_empty()); + let error = Error::from(""); + assert!(!format!("{:#?}", error).is_empty()); + }) +} + +/// Test our specialized JSON encoding is working, all special characters are +/// encoded properly. +#[test] +fn test_json_encode() { + let buf = [0; 32] + .iter() + .enumerate() + .map(|(count, _)| u8::try_from(count).expect("Data buffer is longer than 32")) + .collect::>(); + assert_eq!( + encode(std::str::from_utf8(buf.as_slice()).expect("Wrong test buffer data")), + r#"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f"# + ); + let buf2 = &[36, 35, 34, 92, 93, 94]; + assert_eq!( + encode(std::str::from_utf8(buf2.as_slice()).expect("Wrong second test buffer data")), + r#"$#\"\\"# + ); +} + +/// Uses the Mozilla source-map package (which is directly used by the Firefox +/// browser to resolve sourcemaps) to retrieve a known good position for the +/// test source. +#[test] +fn position_retrieval_works() { + let workspace = testutils::get_workspace_dir(); + + let sourcemap = { + let path = workspace.clone().join("wasm2map/test/assets/golden.wasm"); + let wasm = WASM::load(path.as_path()).expect("Could not load golden.wasm"); + + SourceMap::from_slice(wasm.map_v3(false).as_bytes()) + .expect("Generated sourcemap is not valid") + }; + + let golden = { + let path = workspace + .clone() + .join("wasm2map/test/assets/golden.wasm.map"); + + SourceMap::from_reader(fs::File::open(path.as_path()).expect("Cannot load golden.wasm.map")) + .expect("Cannot parse sourcemap golden.wasm.map") + }; + + golden.tokens().for_each(|golden_token| { + let col = golden_token.get_dst_col(); + let line = golden_token.get_dst_line(); + let token = sourcemap + .lookup_token(line, col) + .expect("Position from golden.json is not present in the sourcemap"); + let left = golden_token.to_string(); + let right = token.to_string(); + + assert!( + left.as_str().eq(right.as_str()), + "[{}] {} <=> {}", + col, + left, + right + ); + }); +} + +// TODO: Test relocation + +// TODO: Test DWO? + +mod testutils { + use std::{ + fs, panic, + path::PathBuf, + process::{Command, Stdio}, + }; + + // Get the target dir for the project or workspace directly from cargo + // so we can create the temporary WASM file somewhere reliable + pub fn get_workspace_dir() -> PathBuf { + let mut out = PathBuf::new(); + let raw = Command::new("cargo") + .args(["locate-project", "--workspace"]) + .output() + .expect("Failed to locate cargo project") + .stdout; + let locate_project = &String::from_utf8_lossy(&raw); + out.push(&locate_project[9..locate_project.len() - 14]); + out + } + + // Invoke the rustc command to compile a simple WASM binary with DWARF info + // so we can run our tests on it + // + // # Concept + // + // rustc gets the source code (the source param) from stdin and writes out + // the created WASM binary to the project / workspace target dir. + // + // NOTE: We also force the WASM32 target obviously, so the tests need that toolchain + pub fn build_with_rustc(source: &'_ str, output: &'_ str) { + let mut file = get_workspace_dir(); + file.push("target"); + file.push(format!("test{}.rs", get_thread_id())); + std::fs::write(&file, source).unwrap(); + + let mut rustc = Command::new("rustc") + .args(["--target", "wasm32-unknown-unknown", "-g", "-o", output]) + .arg(file) + .stdout(Stdio::piped()) + .spawn() + .expect("Test WASM compile unsuccessful"); + rustc + .wait() + .expect("Could not compile test WASM successfully"); + } + + // Builds a test WASM file via rustc in the target directory for the tests + // to manipulate + pub fn setup() -> String { + let mut out = get_workspace_dir(); + out.push("target"); + out.push(format!("test{}.wasm", get_thread_id())); + + build_with_rustc("fn main() {}", out.display().to_string().as_str()); + + out.to_string_lossy().to_string() + } + + // Remove the test WASM at the end of each test case + pub fn teardown() { + let mut target = get_workspace_dir(); + target.push("target"); + + let mut wasm = target.clone(); + wasm.push(format!("test{}.wasm", get_thread_id())); + fs::remove_file(wasm.as_path()).ok(); + + let mut input = target.clone(); + input.push(format!("test{}.rs", get_thread_id())); + fs::remove_file(input.as_path()).ok(); + } + + pub fn get_thread_id() -> u64 { + // TODO(mtolmacs): There's an easier way on nightly, https://github.com/rust-lang/rust/issues/67939 + let str = format!("{:#?}", std::thread::current().id()); + let num = &str.as_str()[14..str.len() - 3]; + + str::parse::(num).expect("ThreadId debug format changed") + } + + // Loads 'loopback' bytes from the end of the WASM binary specified by the 'path' + // parameter, which we can use to match against expected binary patters + pub fn peek_wasm_file_end(path: String, lookback: usize) -> Vec { + let binary = fs::read(path).expect("Can't open the test WASM file for reading"); + binary[binary.len() - lookback..].to_owned() + } + + // Run a test with setup and teardown for the test case + pub fn run_test(test: T) + where + T: FnOnce(String) + panic::UnwindSafe, + { + let out = setup(); + let result = panic::catch_unwind(|| test(out)); + teardown(); + assert!(result.is_ok()) + } +} diff --git a/wasm2map/src/dwarf/mod.rs b/wasm2map/src/dwarf/mod.rs new file mode 100644 index 0000000..2d281bf --- /dev/null +++ b/wasm2map/src/dwarf/mod.rs @@ -0,0 +1,5 @@ +mod reader; +mod relocate; +mod section; + +pub use reader::Reader; diff --git a/wasm2map/src/dwarf/reader.rs b/wasm2map/src/dwarf/reader.rs new file mode 100644 index 0000000..5da4050 --- /dev/null +++ b/wasm2map/src/dwarf/reader.rs @@ -0,0 +1,187 @@ +use crate::error::{Error, InternalError}; +use gimli::Reader as _; +use object::{Object, ObjectSection, ObjectSymbol}; +use std::{borrow::Cow, cell::OnceCell, rc::Rc}; + +use super::{ + relocate::{Relocate, RelocationMap}, + section::SectionReader, +}; + +pub type Relocator<'a> = Relocate>>; + +/// +/// +/// +pub struct Reader<'reader, R: object::ReadRef<'reader> + 'reader> { + binary: &'reader object::File<'reader, R>, + dwo_parent: Option<&'reader object::File<'reader, R>>, + sup_file: Option<&'reader object::File<'reader, R>>, + dwarf: OnceCell>>, +} + +impl<'reader, R> Reader<'reader, R> +where + R: object::ReadRef<'reader> + 'reader, +{ + /// + /// + /// + pub fn new( + binary: &'reader object::File<'reader, R>, + dwo_parent: Option<&'reader object::File<'reader, R>>, + sup_file: Option<&'reader object::File<'reader, R>>, + ) -> Self { + Self { + binary, + dwo_parent, + sup_file, + dwarf: OnceCell::new(), + } + } + + /// + /// + /// + pub fn get(&'reader self) -> Result<&'reader gimli::Dwarf>, Error> { + self.dwarf.get().ok_or(()).or_else(|_| self.load()) + } + + /// + /// + /// + fn load(&'reader self) -> Result<&'reader gimli::Dwarf>, Error> { + // If the WASM debug info is in a split DWARF object (DWO), then load + // the parent object first, so we can link them. The parent archive + // contains references to the DWO object we resolve later in generating + // the source map + let parent = if let Some(parent) = self.dwo_parent { + let load_parent_section = + |id: gimli::SectionId| Self::load_file_section(id, parent, false); + Some(gimli::Dwarf::load(load_parent_section)?) + } else { + None + }; + let parent = parent.as_ref(); + + // This is the target object binary we are generating the sourcemap for + let load_section = + |id: gimli::SectionId| Self::load_file_section(id, self.binary, parent.is_some()); + + let mut dwarf = gimli::Dwarf::load(load_section)?; + + if parent.is_some() { + if let Some(parent) = parent { + dwarf.make_dwo(parent); + } else { + dwarf.file_type = gimli::DwarfFileType::Dwo; + } + } + + // Load optional supplemental file + if let Some(sup) = self.sup_file { + let load_sup_section = |id: gimli::SectionId| { + // Note: we really only need the `.debug_str` section, + // but for now we load them all. + Self::load_file_section(id, sup, false) + }; + dwarf.load_sup(load_sup_section)?; + } + + dwarf.populate_abbreviations_cache(gimli::AbbreviationsCacheStrategy::All); + + Ok(self.dwarf.get_or_init(|| dwarf)) + } + + /// + /// + /// + fn load_file_section( + id: gimli::SectionId, + object: &'reader object::File<'reader, R>, + is_dwo: bool, + ) -> Result, Error> { + let mut relocations = RelocationMap::default(); + let name = if is_dwo { + id.dwo_name() + } else { + Some(id.name()) + }; + + let data = match name.and_then(|name| object.section_by_name(name)) { + Some(ref section) => { + // DWO sections never have relocations, so don't bother. + if !is_dwo { + // Collect the relocations in this section and add to the relocation map + relocations.extend(Self::get_relocations(object, section)?); + } + section.uncompressed_data()? + } + // Use a non-zero capacity so that `ReaderOffsetId`s are unique. + None => Cow::Owned(Vec::with_capacity(1)), + }; + + let reader = gimli::EndianReader::new(SectionReader { data }, gimli::LittleEndian); + let offset = reader.offset_from(&reader); + Ok(Relocate { + relocations: Rc::new(relocations), + offset, + reader, + }) + } + + /// + /// + /// + fn get_relocations( + object: &object::File<'reader, R>, + section: &object::Section<'reader, 'reader, R>, + ) -> Result { + let mut relocations: RelocationMap = RelocationMap::new(); + + for (offset, mut relocation) in section.relocations() { + let offset = usize::try_from(offset).map_err(InternalError::from)?; + + match relocation.kind() { + object::RelocationKind::Absolute => { + if let object::RelocationTarget::Symbol(symbol_idx) = relocation.target() { + match object.symbol_by_index(symbol_idx) { + Ok(symbol) => { + let addend = + symbol.address().wrapping_add(relocation.addend() as u64); + relocation.set_addend(addend as i64); + } + Err(_) => { + let msg = format!( + "Relocation with invalid symbol for section {} at offset 0x{:08x}", + section.name().unwrap(), + offset + ); + return Err(InternalError::from(msg).into()); + } + } + } + + if relocations.insert(offset, relocation).is_some() { + let msg = format!( + "Multiple relocations for section {} at offset 0x{:08x}", + section.name().unwrap(), + offset + ); + return Err(InternalError::from(msg).into()); + } + } + _ => { + let msg = format!( + "Unsupported relocation for section {} at offset 0x{:08x}", + section.name().unwrap(), + offset + ); + return Err(InternalError::from(msg).into()); + } + } + } + + Ok(relocations) + } +} diff --git a/wasm2map/src/dwarf/relocate.rs b/wasm2map/src/dwarf/relocate.rs new file mode 100644 index 0000000..bbfa0d9 --- /dev/null +++ b/wasm2map/src/dwarf/relocate.rs @@ -0,0 +1,128 @@ +use gimli::Reader; +use object::Relocation; +use std::{borrow::Cow, collections::HashMap, rc::Rc}; + +pub type RelocationMap = HashMap; + +#[derive(Debug, Clone)] +pub struct Relocate> { + pub relocations: Rc, + pub offset: usize, + pub reader: R, +} + +impl> Relocate { + pub fn relocate(&self, offset: usize, value: u64) -> u64 { + if let Some(relocation) = self.relocations.get(&offset) { + if let object::RelocationKind::Absolute = relocation.kind() { + if relocation.has_implicit_addend() { + // Use the explicit addend too, because it may have the symbol value. + return value.wrapping_add(relocation.addend() as u64); + } else { + return relocation.addend() as u64; + } + } + }; + value + } +} + +impl> Reader for Relocate { + type Endian = R::Endian; + type Offset = R::Offset; + + fn read_address(&mut self, address_size: u8) -> gimli::Result { + let value = self.reader.read_address(address_size)?; + Ok(self.relocate(self.offset, value)) + } + + fn read_length(&mut self, format: gimli::Format) -> gimli::Result { + let value = self.reader.read_length(format)?; + ::from_u64(self.relocate(self.offset, value as u64)) + } + + fn read_offset(&mut self, format: gimli::Format) -> gimli::Result { + let value = self.reader.read_offset(format)?; + ::from_u64(self.relocate(self.offset, value as u64)) + } + + fn read_sized_offset(&mut self, size: u8) -> gimli::Result { + let value = self.reader.read_sized_offset(size)?; + ::from_u64(self.relocate(self.offset, value as u64)) + } + + #[inline] + fn split(&mut self, len: Self::Offset) -> gimli::Result { + let mut other = self.clone(); + other.reader.truncate(len)?; + self.reader.skip(len)?; + Ok(other) + } + + // All remaining methods simply delegate to `self.reader`. + + #[inline] + fn endian(&self) -> Self::Endian { + self.reader.endian() + } + + #[inline] + fn len(&self) -> Self::Offset { + self.reader.len() + } + + #[inline] + fn empty(&mut self) { + self.reader.empty() + } + + #[inline] + fn truncate(&mut self, len: Self::Offset) -> gimli::Result<()> { + self.reader.truncate(len) + } + + #[inline] + fn offset_from(&self, base: &Self) -> Self::Offset { + self.reader.offset_from(&base.reader) + } + + #[inline] + fn offset_id(&self) -> gimli::ReaderOffsetId { + self.reader.offset_id() + } + + #[inline] + fn lookup_offset_id(&self, id: gimli::ReaderOffsetId) -> Option { + self.reader.lookup_offset_id(id) + } + + #[inline] + fn find(&self, byte: u8) -> gimli::Result { + self.reader.find(byte) + } + + #[inline] + fn skip(&mut self, len: Self::Offset) -> gimli::Result<()> { + self.reader.skip(len) + } + + #[inline] + fn to_slice(&self) -> gimli::Result> { + self.reader.to_slice() + } + + #[inline] + fn to_string(&self) -> gimli::Result> { + self.reader.to_string() + } + + #[inline] + fn to_string_lossy(&self) -> gimli::Result> { + self.reader.to_string_lossy() + } + + #[inline] + fn read_slice(&mut self, buf: &mut [u8]) -> gimli::Result<()> { + self.reader.read_slice(buf) + } +} diff --git a/wasm2map/src/dwarf/section.rs b/wasm2map/src/dwarf/section.rs new file mode 100644 index 0000000..13192c9 --- /dev/null +++ b/wasm2map/src/dwarf/section.rs @@ -0,0 +1,21 @@ +use std::borrow::Cow; + +/// We need a holder struct to own the binary data coming out of the object +/// reader when the DWARF loader loads a section. Since the gimli::Reader trait +/// is not implemented for Cow returned by object::File::section_by_name we +/// need to implement it ourselves. +#[derive(Clone, Debug)] +pub struct SectionReader<'a> { + pub data: Cow<'a, [u8]>, +} + +impl<'a> std::ops::Deref for SectionReader<'a> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.data.deref() + } +} + +unsafe impl<'a> gimli::StableDeref for SectionReader<'a> {} +unsafe impl<'a> gimli::CloneStableDeref for SectionReader<'a> {} diff --git a/wasm2map/src/error.rs b/wasm2map/src/error.rs index 6535ece..50e832f 100644 --- a/wasm2map/src/error.rs +++ b/wasm2map/src/error.rs @@ -1,63 +1,40 @@ -use std::fmt::Display; - -/// Common error type for the crate -#[derive(Debug)] -pub struct Error { - msg: String, -} - -impl std::error::Error for Error {} - -impl Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.msg) - } -} - -// Implement these From<...> traits to make -// the library codemuch more readable -impl From for Error { - fn from(value: std::io::Error) -> Self { - Self { - msg: value.to_string(), - } - } -} - -impl From for Error { - fn from(value: object::Error) -> Self { - Self { - msg: value.to_string(), - } - } -} - -impl From for Error { - fn from(value: gimli::Error) -> Self { - Self { - msg: value.to_string(), - } - } -} - -impl From<&str> for Error { - fn from(value: &str) -> Self { - Self { - msg: value.to_owned(), - } - } -} - -impl From for Error { +use std::{fmt::Debug, io, num::TryFromIntError}; + +/// Common public error type for the library which is exported from the crate +#[derive(thiserror::Error, Debug)] +#[error(transparent)] +pub enum Error { + /// Signals an issue with the WASM object file structure, segments or reading + Object(#[from] object::Error), + /// Signals an issue with the DWARF data structures in the object file + /// or parsing of the DWARF data + Dwarf(#[from] gimli::Error), + /// When the source file or output file cannot be read or mapped to memory + IoError(#[from] io::Error), + /// Internal error which shouldn't ever happen. Signals a programming error + /// with this lib or the downstream dependencies, but panicking in libraries + /// is not nice with the upstream implementor, so we wrap it up with this. + Internal(#[from] InternalError), +} + +/// The opaque internal error type for programming errors. Should not be exposed +/// outside the library. +#[derive(thiserror::Error, Debug)] +pub enum InternalError { + #[error("Internal Error: {0}")] + Generic(String), + #[error("Internal Error: {0}")] + TryFromInt(#[from] TryFromIntError), +} + +impl From for InternalError { fn from(value: String) -> Self { - Self { msg: value } + Self::Generic(value) } } -impl From for Error { - fn from(value: std::num::TryFromIntError) -> Self { - Self { - msg: value.to_string(), - } +impl From<&'static str> for InternalError { + fn from(value: &'static str) -> Self { + Self::Generic(String::from(value)) } } diff --git a/wasm2map/src/json.rs b/wasm2map/src/json.rs deleted file mode 100644 index d74fb53..0000000 --- a/wasm2map/src/json.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::borrow::Cow; -use std::str; - -// Inspired by: -// -pub(crate) fn encode(string: &str) -> Cow<'_, str> { - const BB: u8 = b'b'; // \x08 - const TT: u8 = b't'; // \x09 - const NN: u8 = b'n'; // \x0A - const FF: u8 = b'f'; // \x0C - const RR: u8 = b'r'; // \x0D - const QU: u8 = b'"'; // \x22 - const BS: u8 = b'\\'; // \x5C - const UU: u8 = b'u'; // \x00...\x1F except the ones above - const __: u8 = 0; - - // Lookup table of escape sequences. A value of b'x' at index i means that byte - // i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped. - static ESCAPE: [u8; 256] = [ - // 1 2 3 4 5 6 7 8 9 A B C D E F - UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0 - UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1 - __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2 - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3 - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4 - __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5 - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6 - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7 - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8 - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9 - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F - ]; - - let mut start = 0; - let mut new_string = String::new(); - - for (index, byte) in string.bytes().enumerate() { - let escape = ESCAPE[byte as usize]; - if escape == 0 { - continue; - } - - if start < index { - new_string += &string[start..index]; - } - - match escape { - QU => new_string += r#"\""#, - BS => new_string += r"\\", - BB => new_string += r"\b", - FF => new_string += r"\f", - NN => new_string += r"\n", - RR => new_string += r"\r", - TT => new_string += r"\t", - UU => { - static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef"; - - new_string += str::from_utf8(&[ - b'\\', - b'u', - b'0', - b'0', - HEX_DIGITS[(byte >> 4) as usize], - HEX_DIGITS[(byte & 0xF) as usize], - ]) - .unwrap() - } - _ => unreachable!(), - } - - start = index + 1; - } - - if new_string.is_empty() { - string.into() - } else { - new_string.into() - } -} diff --git a/wasm2map/src/lib.rs b/wasm2map/src/lib.rs index 9016c9f..e26e309 100644 --- a/wasm2map/src/lib.rs +++ b/wasm2map/src/lib.rs @@ -14,475 +14,220 @@ //! * [wasm_sourcemap.py](https://github.com/emscripten-core/emscripten/blob/main/tools/wasm-sourcemap.py) by the Emscripten Team //! * [WebAssembly Debugging](https://medium.com/oasislabs/webassembly-debugging-bec0aa93f8c6) by Will Scott and Oasis Labs +mod dwarf; mod error; -mod json; +#[cfg(feature = "loader")] +mod loader; #[cfg(test)] mod test; -mod vlq; -use error::Error; -use object::{Object, ObjectSection}; -use std::{ - borrow::Cow, - collections::BTreeMap, - fs, - io::{self, Seek, Write}, - ops::Deref, - path::{Path, PathBuf}, - str, -}; +use dwarf::Reader; +pub use error::Error; +use error::InternalError; +use gimli::Reader as _; +#[cfg(feature = "loader")] +pub use loader::Loader; +use normalize_path::NormalizePath; +pub use object::ReadRef; +use object::{self, File, FileKind, Object, ObjectSection, SectionIndex}; +use sourcemap::SourceMapBuilder; +use std::{cell::OnceCell, path::PathBuf, str}; -const DWARF_CODE_SECTION_ID: usize = 10; +type Entry = (u32, u32, u32, u32, Option, Option, bool); -/// Represents a code unit which can be translated to a sourcemap code point -#[derive(Debug)] -pub struct CodePoint { - path: PathBuf, - address: i64, - line: i64, - column: i64, -} - -/// The actual DWARF to Sourcemap mapper -/// -/// # Usage /// -/// ```rust -/// use wasm2map::WASM; -/// -/// let mapper = WASM::load("/path/to/the/file.wasm"); -/// if let Ok(mut mapper) = mapper { -/// let sourcemap = mapper.map_v3(false); -/// mapper.patch("http://localhost:8080").expect("Failed to patch"); -/// } -/// ``` -#[derive(Debug)] -pub struct WASM { - path: PathBuf, - points: BTreeMap, - sourcemap_size: Option, -} - -struct Generated { - mappings: Vec, - sources: Vec, - contents: Option>>, +pub struct Wasm<'wasm, R: ReadRef<'wasm>> { + binary: File<'wasm, R>, + dwo_parent: Option>, + sup_file: Option>, + offset: u32, + dwarf: OnceCell>, } -impl WASM { - /// Loads the WASM file under 'path' into memory and parses the DWARF info - /// If the WASM or the DWARF info in it is malformed (or non-existent) - /// it returns with the appropriate error result. - pub fn load(path: impl AsRef) -> Result { - let path = path.as_ref().to_owned(); - - #[cfg(feature = "memmap2")] - let raw = { - // Load the WASM file into memory via mmap to speed things up - // with large WASM files - let file = fs::File::open(&path)?; - unsafe { memmap2::Mmap::map(&file) }? - }; - #[cfg(not(feature = "memmap2"))] - let raw = { - // Load the WASM file via the standard library, which can be slower - // for larger WASM files, but some platforms might not be supported - // by memmap2 - fs::read(&path)? - }; - - // Parse the modules and sections from the WASM - let object = object::File::parse(raw.deref())?; - - // Load the sourcemap custom section (if any) and calculate the total - // size of the whole custom module (that is, the sourceMappingURL module) - let sourcemap_size = match object.section_by_name("sourceMappingURL") { - Some(section) => { - // This is the '0' section type - const CUSTOM_SEGMENT_ID_SIZE: u64 = 1; - // The size of the length b"sourceMappingURL" (which is always - // 1 byte, so the size of u8) + the length of the - // b"sourceMappingURL" byte array - const SEGMENT_NAME_SIZE: u64 = - std::mem::size_of::() as u64 + b"sourceMappingURL".len() as u64; - let section_size_length = vlq::encode_uint_var(section.size() as u32).len() as u64; - let section_size = CUSTOM_SEGMENT_ID_SIZE - + SEGMENT_NAME_SIZE - + section_size_length - + section.size(); - Some(section_size) - } - None => None, - }; - - // Load the code section to get its offset - let offset: i64 = { - let (code_section_offset, _) = object - .section_by_index(object::SectionIndex(DWARF_CODE_SECTION_ID))? - .file_range() - .ok_or("Missing code section in WASM")?; - code_section_offset.try_into()? - }; - - // Load all of the DWARF sections - let section = - gimli::Dwarf::load(|id: gimli::SectionId| -> Result, gimli::Error> { - match object.section_by_name(id.name()) { - Some(ref section) => Ok(section - .uncompressed_data() - .unwrap_or(Cow::Borrowed(&[][..]))), - None => Ok(Cow::Borrowed(&[][..])), - } - })?; +impl<'wasm, R: ReadRef<'wasm>> Wasm<'wasm, R> { + /// Create a new Wasm instance from a binary file and + /// optional DWO parent and also optional supplemental file + pub fn new(binary: R, dwo_parent: Option, sup_file: Option) -> Result { + let file = File::parse(binary)?; + let offset = file + .section_by_index(SectionIndex(10))? + .file_range() + .ok_or(InternalError::Generic( + "The code section in the WASM file does not contain a size parameter".into(), + ))? + .0 + .try_into() + .map_err(InternalError::from)?; - // Borrow a `Cow<[u8]>` to create an `EndianSlice`. - let borrow_section: &dyn for<'a> Fn( - &'a Cow<[u8]>, - ) - -> gimli::EndianSlice<'a, gimli::RunTimeEndian> = - &|section| gimli::EndianSlice::new(section, gimli::RunTimeEndian::Little); - - // Create `EndianSlice`s for all of the sections. - let dwarf = section.borrow(&borrow_section); + Ok(Self { + binary: match FileKind::parse(binary)? { + FileKind::Wasm => Ok(file), + _ => Err(InternalError::Generic( + "Object does not represent a WASM file".into(), + )), + }?, + dwo_parent: if let Some(dwo_parent) = dwo_parent { + let dwo_parent = match FileKind::parse(dwo_parent)? { + FileKind::Wasm => Ok(File::parse(dwo_parent)?), + _ => Err(InternalError::Generic( + "DWO parent file is not connected to a WASM file".into(), + )), + }?; + Some(dwo_parent) + } else { + None + }, + sup_file: if let Some(sup_file) = sup_file { + let sup_file = match FileKind::parse(sup_file)? { + FileKind::Wasm => Ok(File::parse(sup_file)?), + _ => Err(InternalError::Generic( + "Supplemental file is not connected to a WASM file".into(), + )), + }?; + Some(sup_file) + } else { + None + }, + offset, + dwarf: OnceCell::new(), + }) + } - // Collect the debug data and enforce that they are sorted by address - // which BTreeMap guarantees - let mut points: BTreeMap = BTreeMap::new(); + /// + /// + /// + pub fn build(&'wasm self, _bundle_sources: bool, _name: Option<&str>) -> Result { + let mut entries: Vec = Vec::new(); + let mut mapper = SourceMapBuilder::new(None); + + let dwarf = self + .dwarf + .get_or_init(|| { + Reader::new( + &self.binary, + self.dwo_parent.as_ref(), + self.sup_file.as_ref(), + ) + }) + .get()?; let mut iter = dwarf.units(); while let Some(header) = iter.next()? { - let unit = dwarf.unit(header)?; + let unit = match dwarf.unit(header) { + Ok(unit) => unit, + Err(_) => continue, + }; - // Get the line program for the compilation unit. if let Some(program) = unit.line_program.clone() { - // Iterate over the line program rows for the unit. - let mut rows = program.rows(); - while let Some((header, row)) = rows.next_row()? { - // We will collect the embdedded path from the DWARF loc metadata - let mut path = PathBuf::new(); - - if let Some(file) = row.file(header) { - if let Some(dir) = file.directory(header) { - let dir = &dwarf.attr_string(&unit, dir)?.to_string_lossy(); - let dir = Path::new(dir.as_ref()); - - // Relative directories are relative to the compilation unit directory. - if dir.is_relative() { - if let Some(dir) = unit.comp_dir { - path.push(dir.to_string_lossy().as_ref()) + let mut rows = program.clone().rows(); + while let Some((line_header, row)) = rows.next_row()? { + let line: u32 = match row.line() { + Some(line) => line.get().try_into().map_err(InternalError::from)?, + None => continue, + }; + let column: u32 = match row.column() { + gimli::ColumnType::Column(column) => { + column.get().try_into().map_err(InternalError::from)? + } + gimli::ColumnType::LeftEdge => 0, + }; + let mut address = row.address().try_into().map_err(InternalError::from)?; + address += self.offset; + let file = match row.file(line_header) { + Some(file) => { + let mut file_name = PathBuf::from( + dwarf + .attr_string(&unit, file.path_name())? + .to_string_lossy()? + .as_ref(), + ); + + if let Some(directory_attr) = file.directory(program.header()) { + if let Ok(directory) = dwarf.attr_string(&unit, directory_attr) { + if let Ok(directory) = directory.to_string_lossy() { + file_name = + PathBuf::from(directory.as_ref()).join(file_name); + } } } - - path.push(dir); - } - - path.push( - dwarf - .attr_string(&unit, file.path_name())? - .to_string_lossy() - .as_ref(), - ); - } - - // The address of the instruction in the code section - let address: i64 = { - let mut addr: i64 = row.address().try_into()?; - if row.end_sequence() { - addr -= 1; + let sid = mapper.add_source( + file_name + .normalize() + .to_str() + .ok_or(InternalError::Generic( + "Error converting source file path to string".into(), + ))? + .replace('\\', "/") + .as_str(), + ); + Some(sid) } - addr + offset + None => None, }; + let eos = row.end_sequence(); - // Determine line/column. DWARF line/column is never 0 - let line = { - let line = match row.line() { - Some(line) => line.get(), + // TODO: Bundle sources? - // No line information means this code block does not belong to - // a source code block (generated by the compiler for whatever - // reason) - None => 0, - }; - line.try_into()? - }; - - let column: i64 = { - let col = match row.column() { - gimli::ColumnType::LeftEdge => 1, - gimli::ColumnType::Column(column) => column.get(), - }; - col.try_into()? - }; + if eos { + address -= 1; + let last = entries.last_mut().unwrap(); + if last.1 == address { + last.6 = true; + } + } - let point = CodePoint { - path, + entries.push(( + 0, address, - line, - column, - }; - - points.insert(point.address, point); + line.saturating_sub(1), + column.saturating_sub(1), + file, + None, // TODO: Look up name + eos, + )); } } } - Ok(Self { - path, - points, - sourcemap_size, - }) - } - - /// Generate the sourcemap v3 JSON from the parsed WASM DWARF data. - /// - /// The `bundle` parameter, when set to true, bundles the source code - /// of your project in the source map, so you can jump to the source - /// code from the console, not just the raw WASM bytecode. - /// - /// Note: The mapper is currently not able to package the source code - /// of crate dependencies, nor the rust library sources. - /// - /// # Example output - /// - /// ```json - /// { - /// "version": 3, - /// "names": [], - /// "sources": [ - /// "file/path/name.rs", - /// "another/file/path.rs" - /// ... - /// ], - /// "sourcesContent": [ - /// null, - /// null, - /// null, - /// "fn main() {}", - /// null, - /// ... - /// ], - /// "mappings": { - /// "yjBAiIA,qCAIiB,QAMhB,...,oBAAA" - /// } - /// } - /// ``` - pub fn map_v3(&self, bundle: bool) -> String { - let mut sourcemap = String::with_capacity(self.points.len() * 4 + 100); - let Generated { - mappings, - sources, - contents, - } = self.generate(bundle); - - sourcemap.push('{'); - sourcemap.push_str(r#""version":3,"#); - sourcemap.push_str(r#""names":[],"#); - let processed_sources: Vec = sources + //Self::remove_dead_entries(&mut entries); + entries.sort_by(|left, right| left.1.cmp(&right.1)); + entries .into_iter() - .map(|source| { - if let Some(pos) = source.find(':') { - source[pos + 1..].to_string() - } else { - source - } - }) - .map(|source| source.replace('\\', "/")) - .collect(); - sourcemap - .push_str(format!(r#""sources":["{}"],"#, processed_sources.join(r#"",""#)).as_str()); + //.filter(|item| !item.6) + .for_each(|(dst_line, dst_col, src_line, src_col, source, name, _)| { + mapper.add_raw(dst_line, dst_col, src_line, src_col, source, name, false); + }); - if let Some(contents) = contents { - debug_assert!(bundle); - sourcemap.push_str(format!(r#""sourcesContent":[{}],"#, contents.join(",")).as_str()); - } else { - sourcemap.push_str(r#""sourcesContent":null,"#); - } - - sourcemap.push_str(format!(r#""mappings":"{}""#, mappings.join(",")).as_str()); - sourcemap.push('}'); + let mut buf: Vec = Vec::new(); + mapper.into_sourcemap().to_writer(&mut buf).unwrap(); - sourcemap + Ok(String::from_utf8(buf).unwrap()) } - #[allow(rustdoc::invalid_html_tags)] - /// Patch the loaded WASM file to reference the sourcemap and ask the - /// browser or debugger to load it for us when referencing the code - /// - /// # Limitations - /// This can only work if the sourceMappingURL custom section is the last - /// section of the WASM. - /// - /// # How does this work? - /// - /// The WebAssembly specification contains a "custom" section definition - /// which is used to encode the sourcemap url in the WASM binary. /// - /// The structure of the custom module is as follows (without ): - /// ( - /// 0 ( - /// - /// - /// ) - /// ) /// - /// This structure is VLQ encoded without the parentheses and spaces into - /// a byte array and appended to the end of the WASM binary. - /// - /// More details in the [WebAssembly Module Specification](https://webassembly.github.io/spec/core/binary/modules.html) - pub fn patch(&mut self, url: &str) -> Result<(), Error> { - // Open WASM binary for writing - let mut wasm = fs::OpenOptions::new() - .write(true) - .open(&self.path) - .map_err(|err| { - format!( - "Failed to open WASM file to append sourcemap section: {}", - err - ) - })?; - - // Grab the actual size (byte count) of the WASM binary - let size = wasm.seek(io::SeekFrom::End(0))?; - - // Determine the file cusrsor position without the custom section (if any) - // by subtracting the size of the sourceMappingURL section from the - // byte size of the WASM binary - let pos = self - .sourcemap_size - .map(|length| size - length) - .unwrap_or(size); - - // Truncate the WASM binary and position the file cursor to the new end - // (if there was a sourcemap added), no-op otherwise - wasm.set_len(pos)?; - wasm.seek(io::SeekFrom::End(0))?; - - // Generate the souceMappingURL custom - // section (see above for info on structure) - const WASM_CUSTOM_SECTION_ID: u32 = 0; - let section_name = "sourceMappingURL"; - let section_content = [ - &vlq::encode_uint_var(section_name.len() as u32)[..], - section_name.as_bytes(), - &vlq::encode_uint_var(url.len() as u32)[..], - url.as_bytes(), - ] - .concat(); - let section = [ - &vlq::encode_uint_var(WASM_CUSTOM_SECTION_ID)[..], - &vlq::encode_uint_var(section_content.len() as u32)[..], - section_content.as_ref(), - ] - .concat(); - - // Write out the custom section - wasm.write_all(§ion) - .map_err(|err| format!("Failed to write sourcemap section to WASM file: {}", err))?; - - let _s = wasm.seek(io::SeekFrom::End(0)); - - // Set the sourcemap data after writing it out - self.sourcemap_size = Some(section.len() as u64); - - Ok(()) - } - - // Generate the sourcemap mappings and source ids. - // - // The sourcemap 3 format tries to save on file size by using offsets - // wherever possible. So we need to encode the source file data and - // line, column data for each WASM code segment address in the expected - // order, so offsets make sense when resolved by the browser (or debugger) - fn generate<'a>(&'a self, bundle: bool) -> Generated { - // We collect all referenced source code files in a table and use the - // source id (which is the value param of this HashMap) as the basis for - // the offset when encoding position (i.e. last source id - this source id), - // which require preserving the order of inserts! - let mut sources: Vec<&'a Path> = Vec::new(); - //let mut sources: BTreeMap<&'a Path, i64> = BTreeMap::new(); - //let mut sources: HashMap<&'a Path, i64> = HashMap::new(); - - // This is the WASM address -> file:line:col mapping table in the - // required format, which is basically offsets written after each other - // in the specified order (address, source id, line, finally col) - let mut mappings: Vec = Vec::new(); - - // These variables track the last of the four pieces of data so we can - // subtract from them to get an offset and then update them to the latest - let mut last_address: i64 = 0; - let mut last_source_id: i64 = 0; - let mut last_line: i64 = 1; - let mut last_column: i64 = 1; - - for line in self.points.values() { - // Line 0 means that this is an intermediate code block and does not - // refer to a code block in the source files. We need to skip these - // in order to generate the proper offset encoding - if line.line == 0 { - continue; + fn _remove_dead_entries(entries: &mut Vec) { + let mut block_start = 0; + let mut cur_entry = 0; + while cur_entry < entries.len() { + if !entries.get(cur_entry).unwrap().6 { + cur_entry += 1; + } else { + let fn_start = entries.get(block_start).unwrap().1; + let fn_ptr = entries.get(cur_entry).unwrap().1; + let fn_size_length = (fn_ptr - fn_start + 1).ilog(128) + 1; + let min_live_offset = 1 + fn_size_length; + if fn_start < min_live_offset { + cur_entry += 1; + entries.as_mut_slice()[block_start..cur_entry] + .iter_mut() + .for_each(|e| e.6 = true); + cur_entry += 1; + continue; + } + cur_entry += 1; + block_start = cur_entry; } - - // We either get the id of a source file if already in the table - // or we get the max(id) + 1 as the new id for a previously unseen - // source file, which we promptly insert into the source table - - let source_id: i64 = - if let Some(id) = sources.iter().position(|&val| val == line.path.as_path()) { - id as i64 - } else { - let id = sources.len() as i64; - sources.push(&line.path); - id - }; - - // Calculate the offsets (see above) - let address_delta = line.address - last_address; - let source_id_delta = source_id - last_source_id; - let line_delta = line.line - last_line; - let column_delta = line.column - last_column; - - // Store the mapping offsets in the specific format - // (see above) in the mapping table - let mapping = format!( - "{}{}{}{}", - vlq::encode(address_delta).as_str(), - vlq::encode(source_id_delta).as_str(), - vlq::encode(line_delta).as_str(), - vlq::encode(column_delta).as_str() - ); - mappings.push(mapping); - - // Update the tracking variables to the freshly calculated values - // to use them in the next iteration (see above) - last_address = line.address; - last_source_id = source_id; - last_line = line.line; - last_column = line.column; - } - - // We only need the file paths from the sources table in the order - // they were encoded, turned to strings - let sources = sources - .iter() - .filter_map(|p| Some(p.as_os_str().to_str()?.to_owned())) - .collect::>(); - - let contents = bundle.then(|| { - sources - .iter() - .map(Path::new) - .map(|path| { - fs::read_to_string(path) - .map(|content| Cow::Owned(format!(r#""{}""#, json::encode(&content)))) - .unwrap_or(Cow::Borrowed("null")) - }) - .collect() - }); - - Generated { - mappings, - sources, - contents, } } } diff --git a/wasm2map/src/loader/file.rs b/wasm2map/src/loader/file.rs new file mode 100644 index 0000000..cf59e22 --- /dev/null +++ b/wasm2map/src/loader/file.rs @@ -0,0 +1,48 @@ +use crate::error::Error; +use object::{ReadCache, ReadRef}; +use std::{ + fs::File, + io::{Read, Seek}, + path::Path, +}; + +/// +#[derive(Debug)] +pub struct Loader { + data: ReadCache, +} + +impl Loader { + /// + pub fn from_path(path: impl AsRef) -> Result { + let file = File::open(path)?; + Ok(Self { + data: ReadCache::new(file), + }) + } + + /// + pub fn from_file(file: File) -> Result { + Ok(Self { + data: ReadCache::new(file), + }) + } +} + +impl<'a, R: Read + Seek> ReadRef<'a> for &'a Loader { + fn len(self) -> Result { + self.data.len() + } + + fn read_bytes_at(self, offset: u64, size: u64) -> Result<&'a [u8], ()> { + self.data.read_bytes_at(offset, size) + } + + fn read_bytes_at_until( + self, + range: std::ops::Range, + delimiter: u8, + ) -> Result<&'a [u8], ()> { + self.data.read_bytes_at_until(range, delimiter) + } +} diff --git a/wasm2map/src/loader/mmap.rs b/wasm2map/src/loader/mmap.rs new file mode 100644 index 0000000..754ae06 --- /dev/null +++ b/wasm2map/src/loader/mmap.rs @@ -0,0 +1,54 @@ +use crate::error::Error; +use memmap2::Mmap; +use object::ReadRef; +use std::{ + fs::File, + io::{Read, Seek}, + marker::PhantomData, + path::Path, +}; + +/// +#[derive(Debug)] +pub struct Loader { + data: Mmap, + _marker: PhantomData, +} + +impl Loader { + /// + pub fn from_path(path: impl AsRef) -> Result { + let file = File::open(path)?; + + Ok(Self { + data: unsafe { memmap2::Mmap::map(&file) }?, + _marker: PhantomData, + }) + } + + /// + pub fn from_file(file: File) -> Result { + Ok(Self { + data: unsafe { memmap2::Mmap::map(&file) }?, + _marker: PhantomData, + }) + } +} + +impl<'a, R: Read + Seek> ReadRef<'a> for &'a Loader { + fn len(self) -> Result { + self.data.len().try_into().map_err(|_| ()) + } + + fn read_bytes_at(self, offset: u64, size: u64) -> Result<&'a [u8], ()> { + self.data.read_bytes_at(offset, size) + } + + fn read_bytes_at_until( + self, + range: std::ops::Range, + delimiter: u8, + ) -> Result<&'a [u8], ()> { + self.data.read_bytes_at_until(range, delimiter) + } +} diff --git a/wasm2map/src/loader/mod.rs b/wasm2map/src/loader/mod.rs new file mode 100644 index 0000000..d79301e --- /dev/null +++ b/wasm2map/src/loader/mod.rs @@ -0,0 +1,18 @@ +/// Helper module to load wasm a file into memory easily. Currently loading from +/// a file is supported. +/// +/// The type of 'Loader' is determined by the feature flag 'memmap2'. If the +/// memmap2 feature is enabled, then the loader uses unsafe code to use the +/// fast mmap OS feature to load the file into memory in one swoop. Otherwise, +/// if safe code is required, then the file loader uses traditional file I/O +/// to do the same. + +#[cfg(not(feature = "memmap2"))] +pub mod file; +#[cfg(not(feature = "memmap2"))] +pub use file::Loader; + +#[cfg(feature = "memmap2")] +pub mod mmap; +#[cfg(feature = "memmap2")] +pub use mmap::Loader; diff --git a/wasm2map/src/test.rs b/wasm2map/src/test.rs index d328f16..6aeb9ab 100644 --- a/wasm2map/src/test.rs +++ b/wasm2map/src/test.rs @@ -1,50 +1,128 @@ -use std::{fs, ops::Deref, path::PathBuf}; +// TODO: Test relocation +// TODO: Test DWO? -use crate::{error::Error, json::encode, vlq, CodePoint, WASM}; +use sourcemap::SourceMap; -// Consts needed to build golden versions of the binary WASM module section. -// See wasm2map::WASM::patch() doc-comment for details. -const WASM_CUSTOM_SECTION_ID: u8 = 0; -const WASM_SOURCEMAPPINGURL_SECTION_NAME: &[u8] = b"sourceMappingURL"; - -// TODO: Test sourcemap size load -// TODO: Test sourcemap generation +use crate::{Loader, Wasm}; +use std::{fs::File, io::Write, panic, path::Path}; +/// Tests the format of the sourcemap, makes sure the JSON is valid and +/// the required keys are present, with the right type of values. #[test] -fn can_create_sourcemap() { - testutils::run_test(|out| { - if let Ok(mapper) = WASM::load(out) { - let sourcemap = mapper.map_v3(false); - - assert!(sourcemap.starts_with(r#"{"version":3,"names":[],"sources":["#)); - assert!(sourcemap.ends_with(r#""}"#)); +fn can_create_valid_sourcemap_format() { + panic::catch_unwind(|| { + let path = testutils::get_workspace_dir().join("wasm2map/test/assets/golden.wasm"); + let loader = Loader::from_path(path).expect("Could not load WASM file"); + let wasm = Wasm::new(&loader, None, None).expect("Could not load WASM sections"); + if let Ok(sourcemap) = wasm.build(false, None) { + let parsed = serde_json::from_str::(sourcemap.as_str()) + .expect("Sourcemap is not a valid JSON file"); + let json = parsed.as_object().expect("Sourcemap is not a JSON object"); + + let version = json + .get("version") + .expect("Sourcemap JSON object has no requied version key") + .as_i64() + .expect("Sourcemap JSON version value is not an integer"); + assert!(version == 3); + + let names = json + .get("names") + .expect("Sourcemap JSON has no names key") + .as_array() + .expect("Sourcemap JSON key names is not an array"); + assert!(names.is_empty()); + + let sources = json + .get("sources") + .expect("Sourcemap JSON object has no sources key") + .as_array() + .expect("Sourcemap JSON sources value is not an array"); + assert!(!sources.is_empty()); + sources.iter().for_each(|value| { + let path = Path::new( + value + .as_str() + .expect("Sourcemap JSON sources item is not a string"), + ); + assert!(path.extension().is_some()); + }); + + let mappings = json + .get("mappings") + .expect("Sourcemap JSON object has no mappings key") + .as_str() + .expect("Sourcemap JSON key mappings is not a string"); + assert!(!mappings.is_empty()); } else { - unreachable!() + unreachable!("Could not load WASM binary") } - }); + }) + .expect("Cannot create a valid sourcemap format"); } +/// Check the address resolution in the generated sourcemap against a known good +/// example #[test] -fn relative_paths_are_considered() { - testutils::run_test(|out| { - if let Ok(mapper) = WASM::load(out) { - let sourcemap = mapper.map_v3(false); +fn position_retrieval_works() { + let golden = { + let path = testutils::get_workspace_dir().join("wasm2map/test/assets/golden.wasm.map"); + let bytes = std::fs::read(path).expect("Could not load golden sourcemap"); + SourceMap::from_slice(&bytes).expect("Malformed golden sourcemap file") + }; - // Any fixed relative path should have at least a `/` beforehand. - assert!(sourcemap.contains("/library/core/src/any.rs")); - assert!(sourcemap.contains("/library/core/src/panicking.rs")); - } else { - unreachable!() - } + let path = testutils::get_workspace_dir().join("wasm2map/test/assets/golden.wasm"); + let loader = Loader::from_path(path).expect("Could not load WASM file"); + let wasm = Wasm::new(&loader, None, None).expect("Could not load WASM sections"); + let sourcemap = SourceMap::from_slice( + wasm.build(false, None) + .expect("Failed to build sourcemap for golden WASM") + .as_bytes(), + ) + .expect("Generated sourcemap is not valid"); + + let mut entry: u32 = 0; + golden.tokens().for_each(|golden_token| { + entry += 1; + let col = golden_token.get_dst_col() + 1; + let line = golden_token.get_dst_line(); + let golden_token = golden.lookup_token(line, col).expect("Even the golden sourcemap cannot lookup a position"); + let token = sourcemap.lookup_token(line, col).unwrap_or_else(|| { + panic!( + "Position {}:{} from golden.wasm.map is not present in the generated sourcemap at position {}", + line, col, entry + ) + }); + let left = golden_token.to_string(); + let right = token.to_string(); + + assert!( + left.as_str().eq(right.as_str()), + "[{}:{}] {} <=> {} at position {}", + line, + col, + left, + right, + entry + ); }); } +/// The Rust library core files are included in DWARF as relative file paths. +/// This test checks if some of the Rust core files are included in the sources +/// list with some leading parent directories, meaning the relative paths in +/// DWARF are resolved. #[test] -fn can_bundle_source() { +fn relative_paths_are_considered() { testutils::run_test(|out| { - if let Ok(mapper) = WASM::load(out) { - let sourcemap = mapper.map_v3(true); - assert!(sourcemap.contains("fn main() {}")); + if let Ok(loader) = Loader::from_path(out) { + let sourcemap = Wasm::new(&loader, None, None) + .expect("Could not load WASM sections from test build output") + .build(false, None) + .expect("Failed to build sourcemap from test build output"); + + assert!(sourcemap.contains("core/src/any.rs")); + assert!(sourcemap.contains("core/src/panicking.rs")); } else { unreachable!() } @@ -52,194 +130,45 @@ fn can_bundle_source() { } #[test] -fn can_add_and_update_sourcemap() { +fn does_not_contain_absolute_paths() { + let workspace = testutils::get_workspace_dir().into_os_string(); testutils::run_test(|out| { - // Set up the test byte data - const URL: &str = "http://localhost:8080"; - let content = [ - &[WASM_SOURCEMAPPINGURL_SECTION_NAME.len() as u8], - WASM_SOURCEMAPPINGURL_SECTION_NAME, - &[URL.len() as u8], - URL.as_bytes(), - ] - .concat(); - let section = [ - &[WASM_CUSTOM_SECTION_ID] as &[u8], - &[content.len() as u8], - content.as_ref(), - ] - .concat(); - const URL2: &str = "http://127.0.0.1:8080"; - let content2 = [ - &[WASM_SOURCEMAPPINGURL_SECTION_NAME.len() as u8], - WASM_SOURCEMAPPINGURL_SECTION_NAME, - &[URL2.len() as u8] as &[u8], - URL2.as_bytes(), - ] - .concat(); - let section2 = [ - &[WASM_CUSTOM_SECTION_ID] as &[u8], - &[content2.len() as u8], - content2.as_ref(), - ] - .concat(); - - let mapper = WASM::load(&out); - if let Ok(mut mapper) = mapper { - // Patch the WASM with sourceMappingURL and check if it is applied - // correctly - if let Err(error) = mapper.patch(URL) { - panic!("Failed to patch the WASM file the first time: {}", error); - } - { - let test = testutils::peek_wasm_file_end(out.clone(), section.len()); - assert_eq!(test, section); - } - - // Update it and check if it's still valid and not duplicated - if let Err(error) = mapper.patch(URL2) { - panic!("Failed to patch the WASM file the first time: {}", error); - } - { - let test = - testutils::peek_wasm_file_end(out.clone(), section.len() + section2.len()); - - // Test if the patch just keeps adding patches or properly - // update the old one - assert_ne!( - test, - [section.as_ref() as &[u8], section2.as_ref()].concat() - ); - - // Test if the only sourceMappingURL is the last one we set - assert_eq!(test[test.len() - section2.len()..], section2); - } - - // Attempt to patch with the last one for sanity check - if let Err(error) = mapper.patch(URL2) { - panic!("Failed to patch the WASM file the first time: {}", error); - } - { - // Test if WASM binary is at least structurally valid - let raw = fs::read(&out).expect("Cannot open the WASM file"); - let obj = object::File::parse(raw.deref()); - assert!(obj.is_ok()); - } - } else { - panic!("Error loading WASM: {}", mapper.err().unwrap()); - } + if let Ok(loader) = Loader::from_path(out) { + let sourcemap = Wasm::new(&loader, None, None) + .expect("Could not load WASM sections from test build output") + .build(false, None) + .expect("Failed to build sourcemap from test build output"); - let mapper = WASM::load(&out); - if let Ok(mut mapper) = mapper { - // Attempt to patch with the last one for sanity check - if let Err(error) = mapper.patch(URL2) { - panic!("Failed to patch the WASM file the first time: {}", error); - } - { - // Test if WASM binary is at least structurally valid - let raw = fs::read(&out).expect("Cannot open the WASM file"); - let obj = object::File::parse(raw.deref()); - assert!(obj.is_ok()); - } + assert!(!sourcemap.contains(workspace.to_str().unwrap())); } else { - panic!("Error loading WASM: {}", mapper.err().unwrap()); + unreachable!() } - }) + }); } +/// When the caller requests the bundling of source file contents, we check +/// that the generated sourcemap has the user source code for the test code. #[test] -fn test_path_handles_nonexistent_wasm() { +fn can_bundle_source() { testutils::run_test(|out| { - let mapper = WASM::load(&out); - if let Ok(mut mapper) = mapper { - // Delete the WASM file to trigger error - fs::remove_file(&out).ok(); + if let Ok(loader) = Loader::from_path(out) { + let sourcemap = Wasm::new(&loader, None, None) + .expect("Could not load WASM sections from test build output") + .build(true, None) + .expect("Failed to build sourcemap from test build output"); - // Attempt to patch with the last one for sanity check - let result = mapper.patch("http://127.0.0.1:8080"); + File::create("test.json") + .unwrap() + .write_all(sourcemap.as_bytes()) + .unwrap(); - assert!(result.is_err()) + assert!(sourcemap.contains("fn main() {}")); } else { - panic!("Error loading WASM: {}", mapper.err().unwrap()); + unreachable!() } }); } -#[test] -fn test_error_types() { - fn errors() -> Result<(), Box> { - let _error: crate::Error = - std::io::Error::new(std::io::ErrorKind::AddrInUse, "This is a test").into(); - - let dumbarray = Vec::::new(); - let _error: crate::Error = match object::File::parse(dumbarray.as_slice()) { - Ok(_) => unreachable!(), - Err(err) => err.into(), - }; - - let _error: crate::Error = gimli::Error::Io.into(); - - let _error: crate::Error = "This is a test".into(); - - let _error: crate::Error = "This is a test".to_owned().into(); - - let num: Result = u32::MAX.try_into(); - let _error: crate::Error = match num { - Ok(_) => unreachable!(), - Err(err) => err.into(), - }; - - Err(Box::from(_error)) - } - - let errors = errors(); - assert!(errors.is_err()); - - let error: crate::Error = "This is a test".into(); - assert_eq!(format!("{}", error), "This is a test"); -} - -#[test] -fn test_numeric_encode_to_byte_sequence() { - assert_eq!(vlq::encode_uint_var(432), vec![176, 3]) -} - -#[test] -fn test_derived_macros_present() { - testutils::run_test(|out| { - let codepoint = CodePoint { - path: PathBuf::new(), - address: 0, - line: 0, - column: 0, - }; - assert!(!format!("{:#?}", codepoint).is_empty()); - let wasm = - WASM::load(out).expect("Loading WASM file is unsuccessful in derived macros test"); - assert!(!format!("{:#?}", wasm).is_empty()); - let error = Error::from(""); - assert!(!format!("{:#?}", error).is_empty()); - }) -} - -#[test] -fn test_json_encode() { - let buf = [0; 32] - .iter() - .enumerate() - .map(|(count, _)| u8::try_from(count).expect("Data buffer is longer than 32")) - .collect::>(); - assert_eq!( - encode(std::str::from_utf8(buf.as_slice()).expect("Wrong test buffer data")), - r#"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f"# - ); - let buf2 = &[36, 35, 34, 92, 93, 94]; - assert_eq!( - encode(std::str::from_utf8(buf2.as_slice()).expect("Wrong second test buffer data")), - r#"$#\"\\"# - ); -} - mod testutils { use std::{ fs, panic, @@ -249,7 +178,7 @@ mod testutils { // Get the target dir for the project or workspace directly from cargo // so we can create the temporary WASM file somewhere reliable - pub fn get_target_dir() -> PathBuf { + pub fn get_workspace_dir() -> PathBuf { let mut out = PathBuf::new(); let raw = Command::new("cargo") .args(["locate-project", "--workspace"]) @@ -271,13 +200,24 @@ mod testutils { // // NOTE: We also force the WASM32 target obviously, so the tests need that toolchain pub fn build_with_rustc(source: &'_ str, output: &'_ str) { - let mut file = get_target_dir(); + if rustc_version::version().unwrap() != rustc_version::Version::parse("1.83.0").unwrap() { + panic!("Test suite is only confirmed to work on Rust 1.83.0"); + } + + let mut file = get_workspace_dir(); file.push("target"); file.push(format!("test{}.rs", get_thread_id())); std::fs::write(&file, source).unwrap(); let mut rustc = Command::new("rustc") - .args(["--target", "wasm32-unknown-unknown", "-g", "-o", output]) + .args([ + "--target", + "wasm32-unknown-unknown", + //"--crate-type=cdylib", + "-g", + "-o", + output, + ]) .arg(file) .stdout(Stdio::piped()) .spawn() @@ -290,18 +230,21 @@ mod testutils { // Builds a test WASM file via rustc in the target directory for the tests // to manipulate pub fn setup() -> String { - let mut out = get_target_dir(); + let mut out = get_workspace_dir(); out.push("target"); out.push(format!("test{}.wasm", get_thread_id())); - build_with_rustc("fn main() {}", out.display().to_string().as_str()); + build_with_rustc( + "#[allow(dead_code)] fn main() {}", + out.display().to_string().as_str(), + ); out.to_string_lossy().to_string() } // Remove the test WASM at the end of each test case pub fn teardown() { - let mut target = get_target_dir(); + let mut target = get_workspace_dir(); target.push("target"); let mut wasm = target.clone(); diff --git a/wasm2map/src/vlq.rs b/wasm2map/src/vlq.rs deleted file mode 100644 index 8b1fd50..0000000 --- a/wasm2map/src/vlq.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Simple implementation of VLQ (variable-length quality) encoding to avoid -// yet another dependency to accomplish this simple task -// -// TODO(mtolmacs): Use smallvec instead of string -pub(crate) fn encode(value: i64) -> String { - const VLQ_CHARS: &[u8] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".as_bytes(); - let mut x = if value >= 0 { - value << 1 - } else { - (-value << 1) + 1 - }; - let mut result = String::new(); - - while x > 31 { - let idx: usize = (32 + (x & 31)).try_into().unwrap(); - let ch: char = VLQ_CHARS[idx].into(); - result.push(ch); - x >>= 5; - } - let idx: usize = x.try_into().unwrap(); - let ch: char = VLQ_CHARS[idx].into(); - result.push(ch); - - result -} - -pub(crate) fn encode_uint_var(mut n: u32) -> Vec { - let mut result = Vec::new(); - while n > 127 { - result.push((128 | (n & 127)) as u8); - n >>= 7; - } - result.push(n as u8); - result -} diff --git a/wasm2map/test/assets/golden.dump b/wasm2map/test/assets/golden.dump new file mode 100644 index 0000000..951926e Binary files /dev/null and b/wasm2map/test/assets/golden.dump differ diff --git a/wasm2map/test/assets/golden.wasm b/wasm2map/test/assets/golden.wasm new file mode 100644 index 0000000..c501b02 Binary files /dev/null and b/wasm2map/test/assets/golden.wasm differ diff --git a/wasm2map/test/assets/golden.wasm.map b/wasm2map/test/assets/golden.wasm.map new file mode 100644 index 0000000..3770f35 --- /dev/null +++ b/wasm2map/test/assets/golden.wasm.map @@ -0,0 +1,82 @@ +{ + "version": 3, + "names": [], + "sources": [ + "/rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/sys_common/backtrace.rs", + "/rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/hint.rs", + "/rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/ops/function.rs", + "/rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/rt.rs", + "/rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/process.rs", + "/rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/ptr/mod.rs", + "target/test5.rs", + "library/core/src/any.rs", + "library/alloc/src/raw_vec.rs", + "library/core/src/num/mod.rs", + "library/core/src/alloc/layout.rs", + "library/core/src/result.rs", + "library/core/src/fmt/mod.rs", + "library/core/src/ptr/mod.rs", + "library/alloc/src/alloc.rs", + "library/std/src/thread/mod.rs", + "library/alloc/src/string.rs", + "library/core/src/char/methods.rs", + "library/alloc/src/vec/mod.rs", + "library/core/src/ptr/mut_ptr.rs", + "library/core/src/intrinsics.rs", + "/cargo/registry/src/index.crates.io-6f17d22bba15001f/dlmalloc-0.2.4/src/dlmalloc.rs", + "library/core/src/cmp.rs", + "library/std/src/sys/unsupported/once.rs", + "library/core/src/cell.rs", + "library/core/src/option.rs", + "library/std/src/io/stdio.rs", + "library/std/src/sync/once_lock.rs", + "library/core/src/sync/atomic.rs", + "library/std/src/sync/remutex.rs", + "library/std/src/rt.rs", + "library/alloc/src/ffi/c_str.rs", + "library/alloc/src/sync.rs", + "library/std/src/sys_common/thread_info.rs", + "library/core/src/cell/once.rs", + "library/std/src/panicking.rs", + "library/core/src/ops/function.rs", + "library/std/src/sync/once.rs", + "library/core/src/mem/maybe_uninit.rs", + "library/std/src/sys_common/backtrace.rs", + "library/core/src/hint.rs", + "library/core/src/panic/panic_info.rs", + "library/std/src/alloc.rs", + "/cargo/registry/src/index.crates.io-6f17d22bba15001f/dlmalloc-0.2.4/src/lib.rs", + "library/alloc/src/boxed.rs", + "library/std/src/sys/unsupported/locks/rwlock.rs", + "library/std/src/sys/unsupported/common.rs", + "library/std/src/sys/unsupported/process.rs", + "library/panic_abort/src/lib.rs", + "src/dlmalloc.rs", + "/rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/num/mod.rs", + "/rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/ptr/mut_ptr.rs", + "src/wasm.rs", + "/rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/stdarch/crates/core_arch/src/wasm32/memory.rs", + "library/core/src/slice/memchr.rs", + "library/core/src/fmt/rt.rs", + "library/core/src/panicking.rs", + "library/core/src/ptr/const_ptr.rs", + "library/core/src/iter/traits/iterator.rs", + "library/core/src/iter/range.rs", + "library/core/src/ptr/non_null.rs", + "library/core/src/slice/iter/macros.rs", + "library/core/src/str/validations.rs", + "library/core/src/str/iter.rs", + "library/core/src/str/mod.rs", + "library/core/src/slice/index.rs", + "library/core/src/str/traits.rs", + "library/core/src/str/count.rs", + "library/core/src/iter/traits/accum.rs", + "library/core/src/fmt/num.rs", + "library/core/src/slice/mod.rs", + "src/macros.rs", + "src/mem/impls.rs", + "/rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/ptr/const_ptr.rs" + ], + "sourcesContent": null, + "mappings": "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+EO,WAAA,GiB7EZ,kCtBw6BI,CgBpTI,Sfte4B,CFhRX,EK81CjB,cC36CA,MNyFc,KYo8Ed,oBkCplFD,GAIH,kCASM,iBACC,4BADD,GACC,wBADD,GACC,gBADD,GACC,gBADD,GACC,gBADD,GACC,oBADD,OACC,kB7B82BS,sBjBtvBM,UAYA,mBuBuGd,QAEqD,qBAA7B,oBAAZ,UADG,6BAcd,WAAA,GAsCb,oBvBlH2C,eUosBZ,OV9jBvB,GAvBG,MCm4BP,CDtzBc,SE4BkB,MF/MZ,YACZ,MAWI,cA2K0B,OA3K1B,IA2KE,oBGs/CV,OAAM,KHt/CI,EAlDV,OADA,QUq+CG,kBVj2CP,kBAEsC,QADT,QUi2CrB,Ua9/CU,ObigDsB,cAEhC,MVhoDJ,KWkzBS,ONqVT,GKpVG,oBV3lBA,gBMlUE,eAmBA,QHmrBL,MoB9dH,6BvB6JqC,SAAA,GwBjJ1C,0BvBg3BI,GC/7BmE,GAA1C,OD+7BzB,CAAA,CC31BiB,ED21BjB,UC11BmB,uBAAA,GsBhBtB,OvB02BG,SAAA,GC/7BmE,GAA1C,CsBqF5B,gBrBssBqB,gCAAA,GEwdd,M0C1zCJ,Q1C8RJ,E2C7bA,wBfHQ,uCeuBK,YAAA,M/B+gBI,cAAT,Gb2vBG,GAKY,O6C3aN,W7C2aW,M8C//BpB,uBpCuqCJ,CqCtyBO,KCyDH,KChnBe,EC5HX,aACL,qBAUc,OA/BL,OANZ,SAuCG,2BAjCH,CAsCqB,OAtCT,CAAZ,SAyCO,2BAzCP,CA8CyB,OA9Cb,CAAZ,KA+Ca,OAAL,QC2EE,CAAN,UAK6B,KAArB,YtCscC,CiC9PF,ICgYJ,ECyDH,KChnBe,EC5HX,WACL,OAYA,OAQI,GAHc,OAtCT,GAAZ,QAAY,GAAZ,EA8CyB,OA9Cb,CAAZ,SA+Ca,OAAL,MC2EE,CAAN,GCwEG,SCQA,ODJH,MAUY,KEsGZ,SD5GG,KDQO,KvD41BV,CyDxvBA,QzConBA,mBb4aA,MAGY,2BAkBf,EAjBY,euDj1CV,GAOC,eN6LW,iBAcI,6BAAA,WJ+rBN,SIpsBS,QC/MtB,CM4BuB,KPmLD,OC/MtB,CM4BuB,KPmLD,OC/MtB,CM4BuB,KPmLD,OC/MtB,CM4BuB,S3DoqCv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,iBACC,kCADD,aAgET,cAAA,Ge+FD,sCAmBsB,cjDq5Ed,uBiDn5EY,SAES,uBZ8rBZ,CrCmtDT,UAAA,ciDh5EqB,UZ6rBZ,CrCmtDT,UiD/4EY,UANE,aAgBM,UAGT,GlDoxBF,SkDjxBG,ClDixBH,GkDnxBY,eACT,YADS,IZ8qBZ,OrCmtDT,eiD13EW,GlD4wBF,SkDvwBG,ClDuwBH,OkDxwBY,CZmqBZ,OrCmtDT,SD9mDS,SkD3wBG,ClD2wBH,OkD1wBG,KAcR,YlD4vBK,UkD9vB4C,GAEjD,QACH,aAAA,GzD+vCD,+BF3jC4B,G6D1EpC,WC5SO,ctBqnCH,KsBlnC4B,CrB06Bf,KqBj/BH,eACM,SAAR,OC24BK,OtBqGA,CqBj/BH,SA2EN,WAEc,MC+zBL,aD9zBc,CACpB,OAvCG,mBATgB,KAShB,aAJc,OAQA,SAFD,KAEsB,KAArB,CAKhB,OATE,OrBy8BG,CqBz8BH,SAuCH,MAlEG,OACY,SAAd,OrBm+BK,OAAA,CqBp+BH,aAyEN,GCszBS,sBD34BG,SAAR,OC24BK,OtBqGA,CqBj/BH,SDkXL,EAAA,GAQO,YACH" +} \ No newline at end of file