├── .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 |
--------------------------------------------------------------------------------