├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── client ├── .gitignore ├── index.html ├── main.js ├── package-lock.json ├── package.json └── style.css ├── shell.nix └── src ├── main.rs └── symbols.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /client/groups.json -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "byteorder" 13 | version = "1.4.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 22 | 23 | [[package]] 24 | name = "crc32fast" 25 | version = "1.3.2" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 28 | dependencies = [ 29 | "cfg-if", 30 | ] 31 | 32 | [[package]] 33 | name = "eyre" 34 | version = "0.6.8" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" 37 | dependencies = [ 38 | "indenter", 39 | "once_cell", 40 | ] 41 | 42 | [[package]] 43 | name = "flate2" 44 | version = "1.0.26" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" 47 | dependencies = [ 48 | "crc32fast", 49 | "miniz_oxide", 50 | ] 51 | 52 | [[package]] 53 | name = "indenter" 54 | version = "0.3.3" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 57 | 58 | [[package]] 59 | name = "itoa" 60 | version = "1.0.6" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 63 | 64 | [[package]] 65 | name = "memchr" 66 | version = "2.5.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 69 | 70 | [[package]] 71 | name = "miniz_oxide" 72 | version = "0.7.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 75 | dependencies = [ 76 | "adler", 77 | ] 78 | 79 | [[package]] 80 | name = "my-binary-is-thicc-af" 81 | version = "0.1.0" 82 | dependencies = [ 83 | "eyre", 84 | "object", 85 | "rustc-demangle", 86 | "rustc-hash", 87 | "serde", 88 | "serde_json", 89 | ] 90 | 91 | [[package]] 92 | name = "object" 93 | version = "0.31.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" 96 | dependencies = [ 97 | "flate2", 98 | "memchr", 99 | "ruzstd", 100 | ] 101 | 102 | [[package]] 103 | name = "once_cell" 104 | version = "1.18.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 107 | 108 | [[package]] 109 | name = "proc-macro2" 110 | version = "1.0.63" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" 113 | dependencies = [ 114 | "unicode-ident", 115 | ] 116 | 117 | [[package]] 118 | name = "quote" 119 | version = "1.0.29" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" 122 | dependencies = [ 123 | "proc-macro2", 124 | ] 125 | 126 | [[package]] 127 | name = "rustc-demangle" 128 | version = "0.1.23" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 131 | 132 | [[package]] 133 | name = "rustc-hash" 134 | version = "1.1.0" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 137 | 138 | [[package]] 139 | name = "ruzstd" 140 | version = "0.3.1" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "9a15e661f0f9dac21f3494fe5d23a6338c0ac116a2d22c2b63010acd89467ffe" 143 | dependencies = [ 144 | "byteorder", 145 | "thiserror", 146 | "twox-hash", 147 | ] 148 | 149 | [[package]] 150 | name = "ryu" 151 | version = "1.0.13" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 154 | 155 | [[package]] 156 | name = "serde" 157 | version = "1.0.164" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" 160 | dependencies = [ 161 | "serde_derive", 162 | ] 163 | 164 | [[package]] 165 | name = "serde_derive" 166 | version = "1.0.164" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" 169 | dependencies = [ 170 | "proc-macro2", 171 | "quote", 172 | "syn", 173 | ] 174 | 175 | [[package]] 176 | name = "serde_json" 177 | version = "1.0.99" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" 180 | dependencies = [ 181 | "itoa", 182 | "ryu", 183 | "serde", 184 | ] 185 | 186 | [[package]] 187 | name = "static_assertions" 188 | version = "1.1.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 191 | 192 | [[package]] 193 | name = "syn" 194 | version = "2.0.22" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616" 197 | dependencies = [ 198 | "proc-macro2", 199 | "quote", 200 | "unicode-ident", 201 | ] 202 | 203 | [[package]] 204 | name = "thiserror" 205 | version = "1.0.40" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" 208 | dependencies = [ 209 | "thiserror-impl", 210 | ] 211 | 212 | [[package]] 213 | name = "thiserror-impl" 214 | version = "1.0.40" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" 217 | dependencies = [ 218 | "proc-macro2", 219 | "quote", 220 | "syn", 221 | ] 222 | 223 | [[package]] 224 | name = "twox-hash" 225 | version = "1.6.3" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" 228 | dependencies = [ 229 | "cfg-if", 230 | "static_assertions", 231 | ] 232 | 233 | [[package]] 234 | name = "unicode-ident" 235 | version = "1.0.9" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" 238 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "my-binary-is-thicc-af" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | eyre = "0.6.8" 10 | object = "0.31.1" 11 | # I may be depending on things one shall not depend on (the output format). 12 | rustc-demangle = { version = "=0.1.23", features = ["std"] } 13 | rustc-hash = "1.1.0" 14 | serde = { version = "1.0.164", features = ["derive"] } 15 | serde_json = "1.0.99" 16 | 17 | [profile.release] 18 | debug = 1 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Nilstrieb 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust binary size analyzer 2 | 3 | ## .text mode 4 | 5 | How to use: 6 | 7 | `cd client && npm i && npm run dev` 8 | 9 | In a separate shell: 10 | 11 | `cargo run --release BINARY_PATH > client/groups.json` 12 | 13 | Note: The symbol parsing code is extremely cursed. This may lead to very wonky results. 14 | 15 | ## .rodata mode 16 | 17 | Pass `--rodata` to `cargo run`. It will print all symbols in rodata, printing their estimated size. -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | my-binary-is-thicc-af 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/main.js: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | import FoamTree from '@carrotsearch/foamtree'; 3 | import groups from './groups.json' 4 | 5 | const appElem = document.getElementById('app'); 6 | console.log(appElem) 7 | const foamtree = new FoamTree({ 8 | id: "app", 9 | layout: 'squarified', 10 | stacking: 'flattened', 11 | dataObject: { 12 | groups 13 | }, 14 | }); 15 | 16 | window.addEventListener("resize", (() => { 17 | let timeout; 18 | return () => { 19 | window.clearTimeout(timeout); 20 | timeout = window.setTimeout(foamtree.resize, 300); 21 | }; 22 | })()); -------------------------------------------------------------------------------- /client/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "client", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "@carrotsearch/foamtree": "^3.5.1" 12 | }, 13 | "devDependencies": { 14 | "vite": "^4.3.9" 15 | } 16 | }, 17 | "node_modules/@carrotsearch/foamtree": { 18 | "version": "3.5.1", 19 | "resolved": "https://registry.npmjs.org/@carrotsearch/foamtree/-/foamtree-3.5.1.tgz", 20 | "integrity": "sha512-73YfG2jauHoC3fs0IVWcW8uI9u8lOS50SKLF25QiUVbNvIoJ1fuJtLQWBfhK8D8WQYMNP4P5Hcxa2FWayITI2A==" 21 | }, 22 | "node_modules/@esbuild/android-arm": { 23 | "version": "0.17.19", 24 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", 25 | "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", 26 | "cpu": [ 27 | "arm" 28 | ], 29 | "dev": true, 30 | "optional": true, 31 | "os": [ 32 | "android" 33 | ], 34 | "engines": { 35 | "node": ">=12" 36 | } 37 | }, 38 | "node_modules/@esbuild/android-arm64": { 39 | "version": "0.17.19", 40 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", 41 | "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", 42 | "cpu": [ 43 | "arm64" 44 | ], 45 | "dev": true, 46 | "optional": true, 47 | "os": [ 48 | "android" 49 | ], 50 | "engines": { 51 | "node": ">=12" 52 | } 53 | }, 54 | "node_modules/@esbuild/android-x64": { 55 | "version": "0.17.19", 56 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", 57 | "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", 58 | "cpu": [ 59 | "x64" 60 | ], 61 | "dev": true, 62 | "optional": true, 63 | "os": [ 64 | "android" 65 | ], 66 | "engines": { 67 | "node": ">=12" 68 | } 69 | }, 70 | "node_modules/@esbuild/darwin-arm64": { 71 | "version": "0.17.19", 72 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", 73 | "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", 74 | "cpu": [ 75 | "arm64" 76 | ], 77 | "dev": true, 78 | "optional": true, 79 | "os": [ 80 | "darwin" 81 | ], 82 | "engines": { 83 | "node": ">=12" 84 | } 85 | }, 86 | "node_modules/@esbuild/darwin-x64": { 87 | "version": "0.17.19", 88 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", 89 | "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", 90 | "cpu": [ 91 | "x64" 92 | ], 93 | "dev": true, 94 | "optional": true, 95 | "os": [ 96 | "darwin" 97 | ], 98 | "engines": { 99 | "node": ">=12" 100 | } 101 | }, 102 | "node_modules/@esbuild/freebsd-arm64": { 103 | "version": "0.17.19", 104 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", 105 | "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", 106 | "cpu": [ 107 | "arm64" 108 | ], 109 | "dev": true, 110 | "optional": true, 111 | "os": [ 112 | "freebsd" 113 | ], 114 | "engines": { 115 | "node": ">=12" 116 | } 117 | }, 118 | "node_modules/@esbuild/freebsd-x64": { 119 | "version": "0.17.19", 120 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", 121 | "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", 122 | "cpu": [ 123 | "x64" 124 | ], 125 | "dev": true, 126 | "optional": true, 127 | "os": [ 128 | "freebsd" 129 | ], 130 | "engines": { 131 | "node": ">=12" 132 | } 133 | }, 134 | "node_modules/@esbuild/linux-arm": { 135 | "version": "0.17.19", 136 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", 137 | "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", 138 | "cpu": [ 139 | "arm" 140 | ], 141 | "dev": true, 142 | "optional": true, 143 | "os": [ 144 | "linux" 145 | ], 146 | "engines": { 147 | "node": ">=12" 148 | } 149 | }, 150 | "node_modules/@esbuild/linux-arm64": { 151 | "version": "0.17.19", 152 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", 153 | "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", 154 | "cpu": [ 155 | "arm64" 156 | ], 157 | "dev": true, 158 | "optional": true, 159 | "os": [ 160 | "linux" 161 | ], 162 | "engines": { 163 | "node": ">=12" 164 | } 165 | }, 166 | "node_modules/@esbuild/linux-ia32": { 167 | "version": "0.17.19", 168 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", 169 | "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", 170 | "cpu": [ 171 | "ia32" 172 | ], 173 | "dev": true, 174 | "optional": true, 175 | "os": [ 176 | "linux" 177 | ], 178 | "engines": { 179 | "node": ">=12" 180 | } 181 | }, 182 | "node_modules/@esbuild/linux-loong64": { 183 | "version": "0.17.19", 184 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", 185 | "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", 186 | "cpu": [ 187 | "loong64" 188 | ], 189 | "dev": true, 190 | "optional": true, 191 | "os": [ 192 | "linux" 193 | ], 194 | "engines": { 195 | "node": ">=12" 196 | } 197 | }, 198 | "node_modules/@esbuild/linux-mips64el": { 199 | "version": "0.17.19", 200 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", 201 | "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", 202 | "cpu": [ 203 | "mips64el" 204 | ], 205 | "dev": true, 206 | "optional": true, 207 | "os": [ 208 | "linux" 209 | ], 210 | "engines": { 211 | "node": ">=12" 212 | } 213 | }, 214 | "node_modules/@esbuild/linux-ppc64": { 215 | "version": "0.17.19", 216 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", 217 | "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", 218 | "cpu": [ 219 | "ppc64" 220 | ], 221 | "dev": true, 222 | "optional": true, 223 | "os": [ 224 | "linux" 225 | ], 226 | "engines": { 227 | "node": ">=12" 228 | } 229 | }, 230 | "node_modules/@esbuild/linux-riscv64": { 231 | "version": "0.17.19", 232 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", 233 | "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", 234 | "cpu": [ 235 | "riscv64" 236 | ], 237 | "dev": true, 238 | "optional": true, 239 | "os": [ 240 | "linux" 241 | ], 242 | "engines": { 243 | "node": ">=12" 244 | } 245 | }, 246 | "node_modules/@esbuild/linux-s390x": { 247 | "version": "0.17.19", 248 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", 249 | "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", 250 | "cpu": [ 251 | "s390x" 252 | ], 253 | "dev": true, 254 | "optional": true, 255 | "os": [ 256 | "linux" 257 | ], 258 | "engines": { 259 | "node": ">=12" 260 | } 261 | }, 262 | "node_modules/@esbuild/linux-x64": { 263 | "version": "0.17.19", 264 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", 265 | "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", 266 | "cpu": [ 267 | "x64" 268 | ], 269 | "dev": true, 270 | "optional": true, 271 | "os": [ 272 | "linux" 273 | ], 274 | "engines": { 275 | "node": ">=12" 276 | } 277 | }, 278 | "node_modules/@esbuild/netbsd-x64": { 279 | "version": "0.17.19", 280 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", 281 | "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", 282 | "cpu": [ 283 | "x64" 284 | ], 285 | "dev": true, 286 | "optional": true, 287 | "os": [ 288 | "netbsd" 289 | ], 290 | "engines": { 291 | "node": ">=12" 292 | } 293 | }, 294 | "node_modules/@esbuild/openbsd-x64": { 295 | "version": "0.17.19", 296 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", 297 | "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", 298 | "cpu": [ 299 | "x64" 300 | ], 301 | "dev": true, 302 | "optional": true, 303 | "os": [ 304 | "openbsd" 305 | ], 306 | "engines": { 307 | "node": ">=12" 308 | } 309 | }, 310 | "node_modules/@esbuild/sunos-x64": { 311 | "version": "0.17.19", 312 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", 313 | "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", 314 | "cpu": [ 315 | "x64" 316 | ], 317 | "dev": true, 318 | "optional": true, 319 | "os": [ 320 | "sunos" 321 | ], 322 | "engines": { 323 | "node": ">=12" 324 | } 325 | }, 326 | "node_modules/@esbuild/win32-arm64": { 327 | "version": "0.17.19", 328 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", 329 | "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", 330 | "cpu": [ 331 | "arm64" 332 | ], 333 | "dev": true, 334 | "optional": true, 335 | "os": [ 336 | "win32" 337 | ], 338 | "engines": { 339 | "node": ">=12" 340 | } 341 | }, 342 | "node_modules/@esbuild/win32-ia32": { 343 | "version": "0.17.19", 344 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", 345 | "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", 346 | "cpu": [ 347 | "ia32" 348 | ], 349 | "dev": true, 350 | "optional": true, 351 | "os": [ 352 | "win32" 353 | ], 354 | "engines": { 355 | "node": ">=12" 356 | } 357 | }, 358 | "node_modules/@esbuild/win32-x64": { 359 | "version": "0.17.19", 360 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", 361 | "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", 362 | "cpu": [ 363 | "x64" 364 | ], 365 | "dev": true, 366 | "optional": true, 367 | "os": [ 368 | "win32" 369 | ], 370 | "engines": { 371 | "node": ">=12" 372 | } 373 | }, 374 | "node_modules/esbuild": { 375 | "version": "0.17.19", 376 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", 377 | "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", 378 | "dev": true, 379 | "hasInstallScript": true, 380 | "bin": { 381 | "esbuild": "bin/esbuild" 382 | }, 383 | "engines": { 384 | "node": ">=12" 385 | }, 386 | "optionalDependencies": { 387 | "@esbuild/android-arm": "0.17.19", 388 | "@esbuild/android-arm64": "0.17.19", 389 | "@esbuild/android-x64": "0.17.19", 390 | "@esbuild/darwin-arm64": "0.17.19", 391 | "@esbuild/darwin-x64": "0.17.19", 392 | "@esbuild/freebsd-arm64": "0.17.19", 393 | "@esbuild/freebsd-x64": "0.17.19", 394 | "@esbuild/linux-arm": "0.17.19", 395 | "@esbuild/linux-arm64": "0.17.19", 396 | "@esbuild/linux-ia32": "0.17.19", 397 | "@esbuild/linux-loong64": "0.17.19", 398 | "@esbuild/linux-mips64el": "0.17.19", 399 | "@esbuild/linux-ppc64": "0.17.19", 400 | "@esbuild/linux-riscv64": "0.17.19", 401 | "@esbuild/linux-s390x": "0.17.19", 402 | "@esbuild/linux-x64": "0.17.19", 403 | "@esbuild/netbsd-x64": "0.17.19", 404 | "@esbuild/openbsd-x64": "0.17.19", 405 | "@esbuild/sunos-x64": "0.17.19", 406 | "@esbuild/win32-arm64": "0.17.19", 407 | "@esbuild/win32-ia32": "0.17.19", 408 | "@esbuild/win32-x64": "0.17.19" 409 | } 410 | }, 411 | "node_modules/fsevents": { 412 | "version": "2.3.2", 413 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 414 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 415 | "dev": true, 416 | "hasInstallScript": true, 417 | "optional": true, 418 | "os": [ 419 | "darwin" 420 | ], 421 | "engines": { 422 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 423 | } 424 | }, 425 | "node_modules/nanoid": { 426 | "version": "3.3.6", 427 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", 428 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", 429 | "dev": true, 430 | "funding": [ 431 | { 432 | "type": "github", 433 | "url": "https://github.com/sponsors/ai" 434 | } 435 | ], 436 | "bin": { 437 | "nanoid": "bin/nanoid.cjs" 438 | }, 439 | "engines": { 440 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 441 | } 442 | }, 443 | "node_modules/picocolors": { 444 | "version": "1.0.0", 445 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 446 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 447 | "dev": true 448 | }, 449 | "node_modules/postcss": { 450 | "version": "8.4.24", 451 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", 452 | "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", 453 | "dev": true, 454 | "funding": [ 455 | { 456 | "type": "opencollective", 457 | "url": "https://opencollective.com/postcss/" 458 | }, 459 | { 460 | "type": "tidelift", 461 | "url": "https://tidelift.com/funding/github/npm/postcss" 462 | }, 463 | { 464 | "type": "github", 465 | "url": "https://github.com/sponsors/ai" 466 | } 467 | ], 468 | "dependencies": { 469 | "nanoid": "^3.3.6", 470 | "picocolors": "^1.0.0", 471 | "source-map-js": "^1.0.2" 472 | }, 473 | "engines": { 474 | "node": "^10 || ^12 || >=14" 475 | } 476 | }, 477 | "node_modules/rollup": { 478 | "version": "3.26.0", 479 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.0.tgz", 480 | "integrity": "sha512-YzJH0eunH2hr3knvF3i6IkLO/jTjAEwU4HoMUbQl4//Tnl3ou0e7P5SjxdDr8HQJdeUJShlbEHXrrnEHy1l7Yg==", 481 | "dev": true, 482 | "bin": { 483 | "rollup": "dist/bin/rollup" 484 | }, 485 | "engines": { 486 | "node": ">=14.18.0", 487 | "npm": ">=8.0.0" 488 | }, 489 | "optionalDependencies": { 490 | "fsevents": "~2.3.2" 491 | } 492 | }, 493 | "node_modules/source-map-js": { 494 | "version": "1.0.2", 495 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 496 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 497 | "dev": true, 498 | "engines": { 499 | "node": ">=0.10.0" 500 | } 501 | }, 502 | "node_modules/vite": { 503 | "version": "4.3.9", 504 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", 505 | "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==", 506 | "dev": true, 507 | "dependencies": { 508 | "esbuild": "^0.17.5", 509 | "postcss": "^8.4.23", 510 | "rollup": "^3.21.0" 511 | }, 512 | "bin": { 513 | "vite": "bin/vite.js" 514 | }, 515 | "engines": { 516 | "node": "^14.18.0 || >=16.0.0" 517 | }, 518 | "optionalDependencies": { 519 | "fsevents": "~2.3.2" 520 | }, 521 | "peerDependencies": { 522 | "@types/node": ">= 14", 523 | "less": "*", 524 | "sass": "*", 525 | "stylus": "*", 526 | "sugarss": "*", 527 | "terser": "^5.4.0" 528 | }, 529 | "peerDependenciesMeta": { 530 | "@types/node": { 531 | "optional": true 532 | }, 533 | "less": { 534 | "optional": true 535 | }, 536 | "sass": { 537 | "optional": true 538 | }, 539 | "stylus": { 540 | "optional": true 541 | }, 542 | "sugarss": { 543 | "optional": true 544 | }, 545 | "terser": { 546 | "optional": true 547 | } 548 | } 549 | } 550 | } 551 | } 552 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite": "^4.3.9" 13 | }, 14 | "dependencies": { 15 | "@carrotsearch/foamtree": "^3.5.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/style.css: -------------------------------------------------------------------------------- 1 | #app { 2 | width: 100vw; 3 | height: 100vh; 4 | } 5 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { } }: 2 | pkgs.mkShell { 3 | buildInputs = with pkgs; [ 4 | llvmPackages_16.clang 5 | llvmPackages_16.bintools 6 | llvmPackages_16.libllvm 7 | rustup 8 | ]; 9 | # https://github.com/rust-lang/rust-bindgen#environment-variables 10 | LIBCLANG_PATH = pkgs.lib.makeLibraryPath [ pkgs.llvmPackages_latest.libclang.lib ]; 11 | shellHook = '' 12 | export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin 13 | export PATH=$PATH:''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-x86_64-unknown-linux-gnu/bin/ 14 | ''; 15 | # Add precompiled library to rustc search path 16 | RUSTFLAGS = (builtins.map (a: ''-L ${a}/lib'') [ 17 | # add libraries here (e.g. pkgs.libvmi) 18 | ]); 19 | # Add glibc, clang, glib and other headers to bindgen search path 20 | BINDGEN_EXTRA_CLANG_ARGS = 21 | # Includes with normal include path 22 | (builtins.map (a: ''-I"${a}/include"'') [ 23 | # add dev libraries here (e.g. pkgs.libvmi.dev) 24 | pkgs.glibc.dev 25 | ]) 26 | # Includes with special directory paths 27 | ++ [ 28 | ''-I"${pkgs.llvmPackages_latest.libclang.lib}/lib/clang/${pkgs.llvmPackages_latest.libclang.version}/include"'' 29 | ''-I"${pkgs.glib.dev}/include/glib-2.0"'' 30 | ''-I${pkgs.glib.out}/lib/glib-2.0/include/'' 31 | ]; 32 | # The Nix packages provided in the environment 33 | packages = (with pkgs; [ 34 | # The package provided by our custom overlay. Includes cargo, Clippy, cargo-fmt, 35 | # rustdoc, rustfmt, and other tools. 36 | python3 37 | git 38 | nodejs 39 | ]); 40 | } 41 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod symbols; 2 | 3 | use std::ops::Range; 4 | 5 | use eyre::{eyre, Context, Result}; 6 | use object::{File, Object, ObjectSection, ObjectSymbol}; 7 | use rustc_hash::FxHashMap; 8 | use serde::Serialize; 9 | 10 | use crate::symbols::symbol_components; 11 | 12 | #[derive(serde::Serialize)] 13 | struct SerGroup { 14 | id: String, 15 | label: String, 16 | #[serde(skip_serializing_if = "Vec::is_empty")] 17 | groups: Vec, 18 | } 19 | 20 | fn main() -> Result<()> { 21 | let path = std::env::args() 22 | .skip(1) 23 | .filter(|arg| !arg.starts_with("-")) 24 | .next() 25 | .unwrap_or("./target/debug/my-binary-is-thicc-af".into()); 26 | 27 | let rodata = std::env::args().any(|arg| arg == "--rodata"); 28 | 29 | let data = std::fs::read(&path).wrap_err_with(|| format!("error opening `{path}`"))?; 30 | let object = object::File::parse(data.as_slice()).context("could not parse object file")?; 31 | 32 | if !rodata { 33 | analyze_sym_modules(object)?; 34 | } else { 35 | let all_sections = object 36 | .sections() 37 | .filter(|section| section.name().is_ok_and(|name| name.contains(".rodata"))) 38 | .map(|section| { 39 | let range = section.address()..(section.address() + section.size()); 40 | symbol_sizes_in(&object, range) 41 | }) 42 | .collect::>>()?; 43 | let mut symbol_sizes = all_sections.into_iter().flatten().collect::>(); 44 | symbol_sizes.sort_by_key(|&(_, size)| size); 45 | 46 | if symbol_sizes.is_empty() { 47 | eprintln!("no symbols found"); 48 | } else { 49 | for (sym, size) in symbol_sizes { 50 | println!("{:10} - {}", size.to_string(), sym); 51 | } 52 | } 53 | } 54 | 55 | Ok(()) 56 | } 57 | 58 | fn analyze_sym_modules(object: File<'_>) -> Result<()> { 59 | let limit = 100; 60 | 61 | let text = object 62 | .section_by_name(".text") 63 | .ok_or_else(|| eyre!("could not find .text section"))?; 64 | 65 | let text_range = text.address()..(text.address() + text.size()); 66 | 67 | let symbol_sizes = symbol_sizes_in(&object, text_range)?; 68 | 69 | let mut root_groups = Groups(FxHashMap::default()); 70 | 71 | for (sym, size) in symbol_sizes { 72 | let mut components = symbol_components(sym).with_context(|| sym.to_string())?; 73 | if components.len() > limit { 74 | components.truncate(limit); 75 | } 76 | 77 | eprintln!("{}", rustc_demangle::demangle(sym).to_string()); 78 | 79 | add_to_group(&mut root_groups, components, size); 80 | } 81 | 82 | root_groups.0.values_mut().for_each(|g| { 83 | propagate_weight(g); 84 | }); 85 | 86 | println!( 87 | "{}", 88 | serde_json::to_string(&root_groups).wrap_err("failed to serialize groups")? 89 | ); 90 | Ok(()) 91 | } 92 | 93 | #[derive(Debug)] 94 | struct Groups(FxHashMap); 95 | 96 | #[derive(Debug)] 97 | struct Group { 98 | weight: u64, 99 | children: Groups, 100 | } 101 | 102 | impl Serialize for Groups { 103 | fn serialize(&self, serializer: S) -> std::result::Result 104 | where 105 | S: serde::Serializer, 106 | { 107 | use serde::ser::SerializeSeq; 108 | 109 | #[derive(Serialize)] 110 | struct ChildGroup<'a> { 111 | id: &'a str, 112 | label: &'a str, 113 | weight: u64, 114 | groups: &'a Groups, 115 | } 116 | 117 | let mut seq = serializer.serialize_seq(Some(self.0.len()))?; 118 | 119 | for (name, grp) in &self.0 { 120 | seq.serialize_element(&ChildGroup { 121 | id: name, 122 | label: name, 123 | weight: grp.weight, 124 | groups: &grp.children, 125 | })?; 126 | } 127 | 128 | seq.end() 129 | } 130 | } 131 | 132 | fn add_to_group(mut cur_groups: &mut Groups, components: Vec, sym_size: u64) { 133 | for head in components { 134 | let grp = cur_groups.0.entry(head).or_insert(Group { 135 | weight: sym_size, // NOTE: This is a dummy value for everything but the innermost nesting. 136 | children: Groups(FxHashMap::default()), 137 | }); 138 | cur_groups = &mut grp.children; 139 | } 140 | } 141 | 142 | fn propagate_weight(group: &mut Group) -> u64 { 143 | if group.children.0.is_empty() { 144 | return group.weight; 145 | } 146 | let total_weight: u64 = group.children.0.values_mut().map(propagate_weight).sum(); 147 | group.weight = total_weight; 148 | total_weight 149 | } 150 | 151 | fn symbol_sizes_in<'a>(object: &'a File<'a>, range: Range) -> Result> { 152 | let symbols = object 153 | .symbols() 154 | .into_iter() 155 | .filter(|sym| range.contains(&sym.address())) 156 | .collect::>(); 157 | 158 | let mut symbol_sizes = Vec::new(); 159 | 160 | for sym in symbols { 161 | let sym_name = sym.name().wrap_err("symbol name has invalid UTF-8")?; 162 | symbol_sizes.push((sym_name, sym.size())); 163 | } 164 | 165 | symbol_sizes.sort_by_key(|&(_, size)| size); 166 | symbol_sizes.reverse(); 167 | Ok(symbol_sizes) 168 | } 169 | -------------------------------------------------------------------------------- /src/symbols.rs: -------------------------------------------------------------------------------- 1 | //! This is really hacky code where we best-effort extract some sort of tree 2 | //! from symbols. It's bad. 3 | #![allow(dead_code)] 4 | 5 | use std::{fmt::Debug, iter::Peekable, str::CharIndices}; 6 | 7 | use eyre::{bail, Context, ContextCompat, Result}; 8 | 9 | pub fn symbol_components(sym: &str) -> Result> { 10 | let demangled = rustc_demangle::demangle(sym).to_string(); 11 | 12 | // If the symbol is a qualified path (`::m`), then we need to parse 13 | // it as such. 14 | let components = if demangled.starts_with('<') { 15 | parse_qpath(&demangled) 16 | .wrap_err("invalid qpath") 17 | .and_then(|qpath| qpath_components(qpath)) 18 | .unwrap_or_else(|_| demangled.split("::").collect::>()) 19 | } else { 20 | // This is not a 21 | demangled.split("::").collect::>() 22 | }; 23 | 24 | let components = components 25 | .into_iter() 26 | .map(ToOwned::to_owned) 27 | .collect::>(); 28 | 29 | // qpath 30 | Ok(components) 31 | } 32 | 33 | #[derive(PartialEq)] 34 | pub struct Path<'a>(Vec>); 35 | 36 | #[derive(PartialEq)] 37 | pub struct PathSegment<'a> { 38 | path: &'a str, 39 | generic_args: Vec>, 40 | } 41 | 42 | impl Debug for Path<'_> { 43 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 44 | write!( 45 | f, 46 | "{}", 47 | self.0 48 | .iter() 49 | .map(|s| format!("{s:?}")) 50 | .collect::>() 51 | .join(",") 52 | ) 53 | } 54 | } 55 | 56 | impl Debug for PathSegment<'_> { 57 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 58 | if self.generic_args.is_empty() { 59 | write!(f, "{}", &self.path) 60 | } else { 61 | write!( 62 | f, 63 | "{}[{}]", 64 | &self.path, 65 | self.generic_args 66 | .iter() 67 | .map(|p| format!("{p:?}")) 68 | .collect::>() 69 | .join(", ") 70 | ) 71 | } 72 | } 73 | } 74 | 75 | #[derive(PartialEq)] 76 | enum PathFinished { 77 | Yes, 78 | No, 79 | } 80 | 81 | pub fn parse_path<'a>(path: &'a str, chars: &mut Peekable>) -> Result> { 82 | let mut segments = Vec::new(); 83 | 84 | while let Some((idx, c)) = chars.next() { 85 | match c { 86 | ':' => { 87 | if let Some((_, ':')) = chars.peek() { 88 | chars.next(); 89 | } 90 | } 91 | '<' => { 92 | unreachable!("path cannot start with <") 93 | } 94 | '>' => { 95 | // generic args closing, we're done. 96 | return Ok(Path(segments)); 97 | } 98 | _ => { 99 | let (segment, finished) = parse_path_segment(path, chars, idx)?; 100 | dbg!(&segment); 101 | 102 | segments.push(segment); 103 | 104 | if finished != PathFinished::Yes && !matches!(chars.next(), Some((_, ':'))) { 105 | bail!("Colon must be followed by second colon"); 106 | } 107 | 108 | // we're done. 109 | if finished == PathFinished::Yes { 110 | return Ok(Path(segments)); 111 | } 112 | } 113 | } 114 | } 115 | Ok(Path(segments)) 116 | } 117 | 118 | fn parse_path_segment<'a>( 119 | path: &'a str, 120 | chars: &mut Peekable>, 121 | start_of_path: usize, 122 | ) -> Result<(PathSegment<'a>, PathFinished)> { 123 | let mut generic_args = Vec::new(); 124 | 125 | // TODO: Paths can start with < like . In this case, just treat the entire thing as opaque. 126 | 127 | while let Some((idx, c)) = chars.next() { 128 | match c { 129 | ':' | '>' => { 130 | let component = &path[start_of_path..idx]; 131 | return Ok(( 132 | PathSegment { 133 | path: component, 134 | generic_args, 135 | }, 136 | if c == '>' { 137 | PathFinished::Yes 138 | } else { 139 | PathFinished::No 140 | }, 141 | )); 142 | } 143 | '<' => { 144 | let arg = parse_path(path, chars)?; 145 | generic_args.push(arg); 146 | // > has been eaten by parse_path. 147 | let component = &path[start_of_path..idx]; 148 | return Ok(( 149 | PathSegment { 150 | path: component, 151 | generic_args, 152 | }, 153 | PathFinished::No, 154 | )); 155 | } 156 | _ => {} 157 | } 158 | } 159 | 160 | Ok(( 161 | PathSegment { 162 | path: &path[start_of_path..], 163 | generic_args, 164 | }, 165 | PathFinished::Yes, 166 | )) 167 | } 168 | 169 | #[derive(Debug, Clone, Copy, PartialEq)] 170 | struct QPath<'a> { 171 | qself: &'a str, 172 | trait_: &'a str, 173 | pathy_bit: &'a str, 174 | } 175 | 176 | fn qpath_components(qpath: QPath<'_>) -> Result> { 177 | if qpath.qself.starts_with('<') { 178 | if let Ok(sub_qpath) = parse_qpath(qpath.qself) { 179 | let mut sub_components = qpath_components(sub_qpath)?; 180 | sub_components.extend(qpath.pathy_bit.split("::")); 181 | Ok(sub_components) 182 | } else { 183 | Ok(qpath 184 | .qself 185 | .split("::") 186 | .chain(qpath.pathy_bit.split("::")) 187 | .collect()) 188 | } 189 | } else { 190 | Ok(qpath 191 | .qself 192 | .split("::") 193 | .chain(qpath.pathy_bit.split("::")) 194 | .collect()) 195 | } 196 | } 197 | 198 | // FIXME: Apparently the symbol `std::os::linux::process:: for std::os::fd::owned::OwnedFd>::from` exists in std 199 | // I have no clue what to do about that. 200 | 201 | fn parse_qpath(s: &str) -> Result> { 202 | let mut chars = s.char_indices().skip(1); 203 | let mut angle_brackets = 1u64; 204 | 205 | let mut result = None; 206 | let mut as_idx = None; 207 | 208 | while let Some((idx, char)) = chars.next() { 209 | match char { 210 | '<' => angle_brackets += 1, 211 | '>' => { 212 | angle_brackets -= 1; 213 | if angle_brackets == 0 { 214 | result = Some(idx); 215 | break; 216 | } 217 | } 218 | ' ' => { 219 | if angle_brackets == 1 && as_idx == None { 220 | as_idx = Some(idx); 221 | } 222 | } 223 | _ => {} 224 | } 225 | } 226 | 227 | let q_close_idx = result.wrap_err_with(|| { 228 | format!("qualified symbol `{s}` does not end qualified part with > properly") 229 | })?; 230 | 231 | let as_idx = 232 | as_idx.wrap_err_with(|| format!("qualified symbol `{s}` does not contain ` as `"))?; 233 | 234 | let q = &s[..q_close_idx]; 235 | let pathy_bit = &s[q_close_idx + 1..]; 236 | let pathy_bit = pathy_bit.strip_prefix("::").wrap_err_with(|| { 237 | format!("path after qualification does not start with `::`: `{pathy_bit}`") 238 | })?; 239 | 240 | let qself = &q[1..as_idx]; 241 | let trait_ = &q[(as_idx + " as ".len())..]; 242 | 243 | Ok(QPath { 244 | qself, 245 | trait_, 246 | pathy_bit, 247 | }) 248 | } 249 | 250 | #[cfg(test)] 251 | mod tests { 252 | use crate::symbol_components; 253 | use crate::symbols::PathSegment; 254 | 255 | use super::Path; 256 | use super::QPath; 257 | 258 | use super::parse_qpath; 259 | 260 | fn vec(i: impl IntoIterator>) -> Vec { 261 | i.into_iter().map(Into::into).collect::>() 262 | } 263 | 264 | fn parse_path(s: &str) -> Path { 265 | super::parse_path(s, &mut s.char_indices().peekable()).unwrap() 266 | } 267 | 268 | #[test] 269 | fn paths() { 270 | let seg = |path| PathSegment { 271 | path, 272 | generic_args: Vec::new(), 273 | }; 274 | let seg_gen = |path, generic_args| PathSegment { path, generic_args }; 275 | let single_path = |path| Path(vec![seg(path)]); 276 | 277 | assert_eq!( 278 | parse_path("core::panicking::panic_nounwind::h078e837899a661cc"), 279 | Path(vec![ 280 | seg("core"), 281 | seg("panicking"), 282 | seg_gen("panic_nounwind", vec![single_path("T")]), 283 | seg("h078e837899a661cc") 284 | ]) 285 | ); 286 | 287 | assert_eq!( 288 | parse_path("core::panicking::panic_nounwind::h078e837899a661cc"), 289 | Path(vec![ 290 | seg("core"), 291 | seg("panicking"), 292 | seg("panic_nounwind"), 293 | seg("h078e837899a661cc") 294 | ]) 295 | ); 296 | } 297 | 298 | #[test] 299 | fn components() { 300 | assert_eq!( 301 | symbol_components("core::panicking::panic_nounwind::h078e837899a661cc").unwrap(), 302 | vec(["core", "panicking", "panic_nounwind", "h078e837899a661cc"]) 303 | ); 304 | assert_eq!( 305 | symbol_components("std::sync::once_lock::OnceLock::initialize::h37ee4f85094ef3f6") 306 | .unwrap(), 307 | vec([ 308 | "std", 309 | "sync", 310 | "once_lock", 311 | "OnceLock", 312 | "initialize", 313 | "h37ee4f85094ef3f6" 314 | ]) 315 | ); 316 | assert_eq!( 317 | symbol_components("<&T as core::fmt::Debug>::fmt::h59637bc6facdc591").unwrap(), 318 | vec(["&T", "fmt", "h59637bc6facdc591"]) 319 | ); 320 | assert_eq!( 321 | symbol_components( 322 | "core::ptr::drop_in_place::h180b14c72fab0876" 323 | ) 324 | .unwrap(), 325 | vec(["core", "ptr", "drop_in_place"]) 326 | ); 327 | } 328 | 329 | #[test] 330 | fn parse_qpaths() { 331 | assert_eq!( 332 | parse_qpath("::fmt").unwrap(), 333 | QPath { 334 | qself: "std::path::Components", 335 | trait_: "core::fmt::Debug", 336 | pathy_bit: "fmt", 337 | } 338 | ); 339 | 340 | assert_eq!( 341 | parse_qpath("<::fmt::DebugHelper as core::fmt::Debug>::fmt").unwrap(), 342 | QPath { 343 | qself: "::fmt::DebugHelper", 344 | trait_: "core::fmt::Debug", 345 | pathy_bit: "fmt", 346 | } 347 | ); 348 | } 349 | } 350 | --------------------------------------------------------------------------------