├── .github └── workflows │ └── build.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── Rocket.toml ├── example.rs ├── resources ├── favicon.ico ├── positioning.css ├── style-default.css ├── svg-pan-zoom.js └── zoom_mir.js ├── rust-toolchain └── src ├── main.rs ├── render ├── graphviz.rs ├── locals.rs ├── mod.rs └── source.rs ├── step.rs └── watch ├── mod.rs └── stack_trace.rs /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | # Toolchain installation should appear as a seperate step for timing purposes 12 | - run: rustup show 13 | - name: Setup Graphviz 14 | uses: ts-graphviz/setup-graphviz@v1 15 | - uses: actions-rs/cargo@v1 16 | with: 17 | command: check 18 | fmt: 19 | name: Rustfmt 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v2 23 | - run: rustup component add rustfmt 24 | - uses: actions-rs/cargo@v1 25 | with: 26 | command: fmt 27 | args: --all -- --check 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | flame_graph* 3 | config.json 4 | -------------------------------------------------------------------------------- /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 = "aead" 13 | version = "0.2.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "4cf01b9b56e767bb57b94ebf91a58b338002963785cdd7013e21c0d4679471e4" 16 | dependencies = [ 17 | "generic-array", 18 | ] 19 | 20 | [[package]] 21 | name = "aes" 22 | version = "0.3.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "54eb1d8fe354e5fc611daf4f2ea97dd45a765f4f1e4512306ec183ae2e8f20c9" 25 | dependencies = [ 26 | "aes-soft", 27 | "aesni", 28 | "block-cipher-trait", 29 | ] 30 | 31 | [[package]] 32 | name = "aes-gcm" 33 | version = "0.5.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "834a6bda386024dbb7c8fc51322856c10ffe69559f972261c868485f5759c638" 36 | dependencies = [ 37 | "aead", 38 | "aes", 39 | "block-cipher-trait", 40 | "ghash", 41 | "subtle 2.4.0", 42 | "zeroize", 43 | ] 44 | 45 | [[package]] 46 | name = "aes-soft" 47 | version = "0.3.3" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d" 50 | dependencies = [ 51 | "block-cipher-trait", 52 | "byteorder", 53 | "opaque-debug", 54 | ] 55 | 56 | [[package]] 57 | name = "aesni" 58 | version = "0.6.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100" 61 | dependencies = [ 62 | "block-cipher-trait", 63 | "opaque-debug", 64 | ] 65 | 66 | [[package]] 67 | name = "aho-corasick" 68 | version = "0.7.15" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" 71 | dependencies = [ 72 | "memchr", 73 | ] 74 | 75 | [[package]] 76 | name = "atty" 77 | version = "0.2.14" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 80 | dependencies = [ 81 | "hermit-abi", 82 | "libc", 83 | "winapi", 84 | ] 85 | 86 | [[package]] 87 | name = "autocfg" 88 | version = "1.0.1" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 91 | 92 | [[package]] 93 | name = "base64" 94 | version = "0.9.3" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" 97 | dependencies = [ 98 | "byteorder", 99 | "safemem", 100 | ] 101 | 102 | [[package]] 103 | name = "base64" 104 | version = "0.12.3" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 107 | 108 | [[package]] 109 | name = "base64" 110 | version = "0.13.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 113 | 114 | [[package]] 115 | name = "bincode" 116 | version = "1.3.2" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "d175dfa69e619905c4c3cdb7c3c203fa3bdd5d51184e3afdb2742c0280493772" 119 | dependencies = [ 120 | "byteorder", 121 | "serde", 122 | ] 123 | 124 | [[package]] 125 | name = "bitflags" 126 | version = "1.2.1" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 129 | 130 | [[package]] 131 | name = "block-buffer" 132 | version = "0.7.3" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 135 | dependencies = [ 136 | "block-padding", 137 | "byte-tools", 138 | "byteorder", 139 | "generic-array", 140 | ] 141 | 142 | [[package]] 143 | name = "block-cipher-trait" 144 | version = "0.6.2" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" 147 | dependencies = [ 148 | "generic-array", 149 | ] 150 | 151 | [[package]] 152 | name = "block-padding" 153 | version = "0.1.5" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 156 | dependencies = [ 157 | "byte-tools", 158 | ] 159 | 160 | [[package]] 161 | name = "byte-tools" 162 | version = "0.3.1" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 165 | 166 | [[package]] 167 | name = "byteorder" 168 | version = "1.3.4" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 171 | 172 | [[package]] 173 | name = "cc" 174 | version = "1.0.67" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" 177 | 178 | [[package]] 179 | name = "cfg-if" 180 | version = "0.1.10" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 183 | 184 | [[package]] 185 | name = "cfg-if" 186 | version = "1.0.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 189 | 190 | [[package]] 191 | name = "cgraph" 192 | version = "0.1.1" 193 | source = "git+https://github.com/oli-obk/cgraph.git?rev=5e6a6d4527522609772a9a9a1565b74c9bfe1560#5e6a6d4527522609772a9a9a1565b74c9bfe1560" 194 | dependencies = [ 195 | "pkg-config", 196 | ] 197 | 198 | [[package]] 199 | name = "chrono" 200 | version = "0.4.19" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 203 | dependencies = [ 204 | "num-integer", 205 | "num-traits", 206 | ] 207 | 208 | [[package]] 209 | name = "cookie" 210 | version = "0.11.3" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "5795cda0897252e34380a27baf884c53aa7ad9990329cdad96d4c5d027015d44" 213 | dependencies = [ 214 | "aes-gcm", 215 | "base64 0.12.3", 216 | "hkdf", 217 | "hmac", 218 | "percent-encoding 2.1.0", 219 | "rand", 220 | "sha2", 221 | "time", 222 | ] 223 | 224 | [[package]] 225 | name = "crc32fast" 226 | version = "1.2.1" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 229 | dependencies = [ 230 | "cfg-if 1.0.0", 231 | ] 232 | 233 | [[package]] 234 | name = "crypto-mac" 235 | version = "0.7.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" 238 | dependencies = [ 239 | "generic-array", 240 | "subtle 1.0.0", 241 | ] 242 | 243 | [[package]] 244 | name = "devise" 245 | version = "0.2.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "74e04ba2d03c5fa0d954c061fc8c9c288badadffc272ebb87679a89846de3ed3" 248 | dependencies = [ 249 | "devise_codegen", 250 | "devise_core", 251 | ] 252 | 253 | [[package]] 254 | name = "devise_codegen" 255 | version = "0.2.0" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "066ceb7928ca93a9bedc6d0e612a8a0424048b0ab1f75971b203d01420c055d7" 258 | dependencies = [ 259 | "devise_core", 260 | "quote 0.6.13", 261 | ] 262 | 263 | [[package]] 264 | name = "devise_core" 265 | version = "0.2.0" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "cf41c59b22b5e3ec0ea55c7847e5f358d340f3a8d6d53a5cf4f1564967f96487" 268 | dependencies = [ 269 | "bitflags", 270 | "proc-macro2 0.4.30", 271 | "quote 0.6.13", 272 | "syn 0.15.44", 273 | ] 274 | 275 | [[package]] 276 | name = "digest" 277 | version = "0.8.1" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 280 | dependencies = [ 281 | "generic-array", 282 | ] 283 | 284 | [[package]] 285 | name = "env_logger" 286 | version = "0.7.1" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 289 | dependencies = [ 290 | "atty", 291 | "humantime", 292 | "log 0.4.14", 293 | "regex", 294 | "termcolor", 295 | ] 296 | 297 | [[package]] 298 | name = "fake-simd" 299 | version = "0.1.2" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 302 | 303 | [[package]] 304 | name = "flate2" 305 | version = "1.0.20" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" 308 | dependencies = [ 309 | "cfg-if 1.0.0", 310 | "crc32fast", 311 | "libc", 312 | "miniz_oxide", 313 | ] 314 | 315 | [[package]] 316 | name = "fnv" 317 | version = "1.0.7" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 320 | 321 | [[package]] 322 | name = "generic-array" 323 | version = "0.12.3" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 326 | dependencies = [ 327 | "typenum", 328 | ] 329 | 330 | [[package]] 331 | name = "getrandom" 332 | version = "0.1.16" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 335 | dependencies = [ 336 | "cfg-if 1.0.0", 337 | "libc", 338 | "wasi 0.9.0+wasi-snapshot-preview1", 339 | ] 340 | 341 | [[package]] 342 | name = "getrandom" 343 | version = "0.2.2" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" 346 | dependencies = [ 347 | "cfg-if 1.0.0", 348 | "libc", 349 | "wasi 0.10.2+wasi-snapshot-preview1", 350 | ] 351 | 352 | [[package]] 353 | name = "ghash" 354 | version = "0.2.3" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "9f0930ed19a7184089ea46d2fedead2f6dc2b674c5db4276b7da336c7cd83252" 357 | dependencies = [ 358 | "polyval", 359 | ] 360 | 361 | [[package]] 362 | name = "glob" 363 | version = "0.3.0" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 366 | 367 | [[package]] 368 | name = "hashbrown" 369 | version = "0.9.1" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 372 | 373 | [[package]] 374 | name = "hermit-abi" 375 | version = "0.1.18" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 378 | dependencies = [ 379 | "libc", 380 | ] 381 | 382 | [[package]] 383 | name = "hex" 384 | version = "0.4.2" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" 387 | 388 | [[package]] 389 | name = "hkdf" 390 | version = "0.8.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "3fa08a006102488bd9cd5b8013aabe84955cf5ae22e304c2caf655b633aefae3" 393 | dependencies = [ 394 | "digest", 395 | "hmac", 396 | ] 397 | 398 | [[package]] 399 | name = "hmac" 400 | version = "0.7.1" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" 403 | dependencies = [ 404 | "crypto-mac", 405 | "digest", 406 | ] 407 | 408 | [[package]] 409 | name = "horrorshow" 410 | version = "0.8.3" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "87ce7e0a1bc8e4489896abc94e5664e811a502a151bebfe113b3214fa181d3fb" 413 | 414 | [[package]] 415 | name = "httparse" 416 | version = "1.3.5" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691" 419 | 420 | [[package]] 421 | name = "humantime" 422 | version = "1.3.0" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 425 | dependencies = [ 426 | "quick-error", 427 | ] 428 | 429 | [[package]] 430 | name = "hyper" 431 | version = "0.10.16" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" 434 | dependencies = [ 435 | "base64 0.9.3", 436 | "httparse", 437 | "language-tags", 438 | "log 0.3.9", 439 | "mime", 440 | "num_cpus", 441 | "time", 442 | "traitobject", 443 | "typeable", 444 | "unicase", 445 | "url", 446 | ] 447 | 448 | [[package]] 449 | name = "idna" 450 | version = "0.1.5" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" 453 | dependencies = [ 454 | "matches", 455 | "unicode-bidi", 456 | "unicode-normalization", 457 | ] 458 | 459 | [[package]] 460 | name = "indexmap" 461 | version = "1.6.1" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b" 464 | dependencies = [ 465 | "autocfg", 466 | "hashbrown", 467 | ] 468 | 469 | [[package]] 470 | name = "itoa" 471 | version = "0.4.7" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 474 | 475 | [[package]] 476 | name = "language-tags" 477 | version = "0.2.2" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" 480 | 481 | [[package]] 482 | name = "lazy_static" 483 | version = "1.4.0" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 486 | 487 | [[package]] 488 | name = "lazycell" 489 | version = "1.3.0" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 492 | 493 | [[package]] 494 | name = "libc" 495 | version = "0.2.86" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" 498 | 499 | [[package]] 500 | name = "line-wrap" 501 | version = "0.1.1" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" 504 | dependencies = [ 505 | "safemem", 506 | ] 507 | 508 | [[package]] 509 | name = "linked-hash-map" 510 | version = "0.5.4" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" 513 | 514 | [[package]] 515 | name = "log" 516 | version = "0.3.9" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 519 | dependencies = [ 520 | "log 0.4.14", 521 | ] 522 | 523 | [[package]] 524 | name = "log" 525 | version = "0.4.14" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 528 | dependencies = [ 529 | "cfg-if 1.0.0", 530 | ] 531 | 532 | [[package]] 533 | name = "matches" 534 | version = "0.1.8" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 537 | 538 | [[package]] 539 | name = "memchr" 540 | version = "2.3.4" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 543 | 544 | [[package]] 545 | name = "mime" 546 | version = "0.2.6" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" 549 | dependencies = [ 550 | "log 0.3.9", 551 | ] 552 | 553 | [[package]] 554 | name = "miniz_oxide" 555 | version = "0.4.4" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 558 | dependencies = [ 559 | "adler", 560 | "autocfg", 561 | ] 562 | 563 | [[package]] 564 | name = "miri" 565 | version = "0.1.0" 566 | source = "git+https://github.com/rust-lang/miri.git?rev=ae964207bb17911cf96d9744d9469fa2734093a8#ae964207bb17911cf96d9744d9469fa2734093a8" 567 | dependencies = [ 568 | "env_logger", 569 | "getrandom 0.2.2", 570 | "hex", 571 | "libc", 572 | "log 0.4.14", 573 | "rand", 574 | "rustc-workspace-hack", 575 | "shell-escape", 576 | "smallvec", 577 | ] 578 | 579 | [[package]] 580 | name = "num-integer" 581 | version = "0.1.44" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 584 | dependencies = [ 585 | "autocfg", 586 | "num-traits", 587 | ] 588 | 589 | [[package]] 590 | name = "num-traits" 591 | version = "0.2.14" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 594 | dependencies = [ 595 | "autocfg", 596 | ] 597 | 598 | [[package]] 599 | name = "num_cpus" 600 | version = "1.13.0" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 603 | dependencies = [ 604 | "hermit-abi", 605 | "libc", 606 | ] 607 | 608 | [[package]] 609 | name = "once_cell" 610 | version = "1.7.0" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "10acf907b94fc1b1a152d08ef97e7759650268cf986bf127f387e602b02c7e5a" 613 | 614 | [[package]] 615 | name = "onig" 616 | version = "6.1.1" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "30b46fd9edbc018f0be4e366c24c46db44fac49cd01c039ae85308088b089dd5" 619 | dependencies = [ 620 | "bitflags", 621 | "lazy_static", 622 | "libc", 623 | "onig_sys", 624 | ] 625 | 626 | [[package]] 627 | name = "onig_sys" 628 | version = "69.6.0" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "ed063c96cf4c0f2e5d09324409d158b38a0a85a7b90fbd68c8cad75c495d5775" 631 | dependencies = [ 632 | "cc", 633 | "pkg-config", 634 | ] 635 | 636 | [[package]] 637 | name = "opaque-debug" 638 | version = "0.2.3" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 641 | 642 | [[package]] 643 | name = "open" 644 | version = "1.4.0" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "7c283bf0114efea9e42f1a60edea9859e8c47528eae09d01df4b29c1e489cc48" 647 | dependencies = [ 648 | "winapi", 649 | ] 650 | 651 | [[package]] 652 | name = "pear" 653 | version = "0.1.4" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "5320f212db967792b67cfe12bd469d08afd6318a249bd917d5c19bc92200ab8a" 656 | dependencies = [ 657 | "pear_codegen", 658 | ] 659 | 660 | [[package]] 661 | name = "pear_codegen" 662 | version = "0.1.4" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "bfc1c836fdc3d1ef87c348b237b5b5c4dff922156fb2d968f57734f9669768ca" 665 | dependencies = [ 666 | "proc-macro2 0.4.30", 667 | "quote 0.6.13", 668 | "syn 0.15.44", 669 | "version_check 0.9.2", 670 | "yansi", 671 | ] 672 | 673 | [[package]] 674 | name = "percent-encoding" 675 | version = "1.0.1" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 678 | 679 | [[package]] 680 | name = "percent-encoding" 681 | version = "2.1.0" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 684 | 685 | [[package]] 686 | name = "pkg-config" 687 | version = "0.3.19" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 690 | 691 | [[package]] 692 | name = "plist" 693 | version = "1.1.0" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "679104537029ed2287c216bfb942bbf723f48ee98f0aef15611634173a74ef21" 696 | dependencies = [ 697 | "base64 0.13.0", 698 | "chrono", 699 | "indexmap", 700 | "line-wrap", 701 | "serde", 702 | "xml-rs", 703 | ] 704 | 705 | [[package]] 706 | name = "polyval" 707 | version = "0.3.3" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "7ec3341498978de3bfd12d1b22f1af1de22818f5473a11e8a6ef997989e3a212" 710 | dependencies = [ 711 | "cfg-if 0.1.10", 712 | "universal-hash", 713 | ] 714 | 715 | [[package]] 716 | name = "ppv-lite86" 717 | version = "0.2.10" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 720 | 721 | [[package]] 722 | name = "priroda" 723 | version = "0.1.0" 724 | dependencies = [ 725 | "cgraph", 726 | "env_logger", 727 | "horrorshow", 728 | "lazy_static", 729 | "log 0.4.14", 730 | "miri", 731 | "open", 732 | "regex", 733 | "rocket", 734 | "serde", 735 | "serde_json", 736 | "syntect", 737 | ] 738 | 739 | [[package]] 740 | name = "proc-macro2" 741 | version = "0.4.30" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 744 | dependencies = [ 745 | "unicode-xid 0.1.0", 746 | ] 747 | 748 | [[package]] 749 | name = "proc-macro2" 750 | version = "1.0.24" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 753 | dependencies = [ 754 | "unicode-xid 0.2.1", 755 | ] 756 | 757 | [[package]] 758 | name = "quick-error" 759 | version = "1.2.3" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 762 | 763 | [[package]] 764 | name = "quote" 765 | version = "0.6.13" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 768 | dependencies = [ 769 | "proc-macro2 0.4.30", 770 | ] 771 | 772 | [[package]] 773 | name = "quote" 774 | version = "1.0.9" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 777 | dependencies = [ 778 | "proc-macro2 1.0.24", 779 | ] 780 | 781 | [[package]] 782 | name = "rand" 783 | version = "0.7.3" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 786 | dependencies = [ 787 | "getrandom 0.1.16", 788 | "libc", 789 | "rand_chacha", 790 | "rand_core", 791 | "rand_hc", 792 | ] 793 | 794 | [[package]] 795 | name = "rand_chacha" 796 | version = "0.2.2" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 799 | dependencies = [ 800 | "ppv-lite86", 801 | "rand_core", 802 | ] 803 | 804 | [[package]] 805 | name = "rand_core" 806 | version = "0.5.1" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 809 | dependencies = [ 810 | "getrandom 0.1.16", 811 | ] 812 | 813 | [[package]] 814 | name = "rand_hc" 815 | version = "0.2.0" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 818 | dependencies = [ 819 | "rand_core", 820 | ] 821 | 822 | [[package]] 823 | name = "regex" 824 | version = "1.4.3" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" 827 | dependencies = [ 828 | "aho-corasick", 829 | "memchr", 830 | "regex-syntax", 831 | "thread_local", 832 | ] 833 | 834 | [[package]] 835 | name = "regex-syntax" 836 | version = "0.6.22" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" 839 | 840 | [[package]] 841 | name = "rocket" 842 | version = "0.4.7" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "7febfdfd4d43facfc7daba20349ebe2c310c6735bd6a2a9255ea8bc425b4cb13" 845 | dependencies = [ 846 | "atty", 847 | "base64 0.12.3", 848 | "log 0.4.14", 849 | "memchr", 850 | "num_cpus", 851 | "pear", 852 | "rocket_codegen", 853 | "rocket_http", 854 | "state", 855 | "time", 856 | "toml", 857 | "version_check 0.9.2", 858 | "yansi", 859 | ] 860 | 861 | [[package]] 862 | name = "rocket_codegen" 863 | version = "0.4.7" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "ceac2c55b2c8b1cdc53add64332defa5fc227f64263b86b4114d1386286d42a3" 866 | dependencies = [ 867 | "devise", 868 | "glob", 869 | "indexmap", 870 | "quote 0.6.13", 871 | "rocket_http", 872 | "version_check 0.9.2", 873 | "yansi", 874 | ] 875 | 876 | [[package]] 877 | name = "rocket_http" 878 | version = "0.4.7" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "ce364100ed7a1bf39257b69ebd014c1d5b4979b0d365d8c9ab0aa9c79645493d" 881 | dependencies = [ 882 | "cookie", 883 | "hyper", 884 | "indexmap", 885 | "pear", 886 | "percent-encoding 1.0.1", 887 | "smallvec", 888 | "state", 889 | "time", 890 | "unicode-xid 0.1.0", 891 | ] 892 | 893 | [[package]] 894 | name = "rustc-workspace-hack" 895 | version = "1.0.0" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb" 898 | 899 | [[package]] 900 | name = "ryu" 901 | version = "1.0.5" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 904 | 905 | [[package]] 906 | name = "safemem" 907 | version = "0.3.3" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" 910 | 911 | [[package]] 912 | name = "same-file" 913 | version = "1.0.6" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 916 | dependencies = [ 917 | "winapi-util", 918 | ] 919 | 920 | [[package]] 921 | name = "serde" 922 | version = "1.0.123" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" 925 | dependencies = [ 926 | "serde_derive", 927 | ] 928 | 929 | [[package]] 930 | name = "serde_derive" 931 | version = "1.0.123" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" 934 | dependencies = [ 935 | "proc-macro2 1.0.24", 936 | "quote 1.0.9", 937 | "syn 1.0.60", 938 | ] 939 | 940 | [[package]] 941 | name = "serde_json" 942 | version = "1.0.63" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "43535db9747a4ba938c0ce0a98cc631a46ebf943c9e1d604e091df6007620bf6" 945 | dependencies = [ 946 | "itoa", 947 | "ryu", 948 | "serde", 949 | ] 950 | 951 | [[package]] 952 | name = "sha2" 953 | version = "0.8.2" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" 956 | dependencies = [ 957 | "block-buffer", 958 | "digest", 959 | "fake-simd", 960 | "opaque-debug", 961 | ] 962 | 963 | [[package]] 964 | name = "shell-escape" 965 | version = "0.1.5" 966 | source = "registry+https://github.com/rust-lang/crates.io-index" 967 | checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" 968 | 969 | [[package]] 970 | name = "smallvec" 971 | version = "1.6.1" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 974 | 975 | [[package]] 976 | name = "state" 977 | version = "0.4.2" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | checksum = "3015a7d0a5fd5105c91c3710d42f9ccf0abfb287d62206484dcc67f9569a6483" 980 | 981 | [[package]] 982 | name = "subtle" 983 | version = "1.0.0" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" 986 | 987 | [[package]] 988 | name = "subtle" 989 | version = "2.4.0" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" 992 | 993 | [[package]] 994 | name = "syn" 995 | version = "0.15.44" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" 998 | dependencies = [ 999 | "proc-macro2 0.4.30", 1000 | "quote 0.6.13", 1001 | "unicode-xid 0.1.0", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "syn" 1006 | version = "1.0.60" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" 1009 | dependencies = [ 1010 | "proc-macro2 1.0.24", 1011 | "quote 1.0.9", 1012 | "unicode-xid 0.2.1", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "syntect" 1017 | version = "4.5.0" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "2bfac2b23b4d049dc9a89353b4e06bbc85a8f42020cccbe5409a115cf19031e5" 1020 | dependencies = [ 1021 | "bincode", 1022 | "bitflags", 1023 | "flate2", 1024 | "fnv", 1025 | "lazy_static", 1026 | "lazycell", 1027 | "onig", 1028 | "plist", 1029 | "regex-syntax", 1030 | "serde", 1031 | "serde_derive", 1032 | "serde_json", 1033 | "walkdir", 1034 | "yaml-rust", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "termcolor" 1039 | version = "1.1.2" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 1042 | dependencies = [ 1043 | "winapi-util", 1044 | ] 1045 | 1046 | [[package]] 1047 | name = "thread_local" 1048 | version = "1.1.3" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" 1051 | dependencies = [ 1052 | "once_cell", 1053 | ] 1054 | 1055 | [[package]] 1056 | name = "time" 1057 | version = "0.1.43" 1058 | source = "registry+https://github.com/rust-lang/crates.io-index" 1059 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 1060 | dependencies = [ 1061 | "libc", 1062 | "winapi", 1063 | ] 1064 | 1065 | [[package]] 1066 | name = "tinyvec" 1067 | version = "1.1.1" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" 1070 | dependencies = [ 1071 | "tinyvec_macros", 1072 | ] 1073 | 1074 | [[package]] 1075 | name = "tinyvec_macros" 1076 | version = "0.1.0" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1079 | 1080 | [[package]] 1081 | name = "toml" 1082 | version = "0.4.10" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" 1085 | dependencies = [ 1086 | "serde", 1087 | ] 1088 | 1089 | [[package]] 1090 | name = "traitobject" 1091 | version = "0.1.0" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" 1094 | 1095 | [[package]] 1096 | name = "typeable" 1097 | version = "0.1.2" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" 1100 | 1101 | [[package]] 1102 | name = "typenum" 1103 | version = "1.12.0" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 1106 | 1107 | [[package]] 1108 | name = "unicase" 1109 | version = "1.4.2" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" 1112 | dependencies = [ 1113 | "version_check 0.1.5", 1114 | ] 1115 | 1116 | [[package]] 1117 | name = "unicode-bidi" 1118 | version = "0.3.4" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 1121 | dependencies = [ 1122 | "matches", 1123 | ] 1124 | 1125 | [[package]] 1126 | name = "unicode-normalization" 1127 | version = "0.1.17" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" 1130 | dependencies = [ 1131 | "tinyvec", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "unicode-xid" 1136 | version = "0.1.0" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 1139 | 1140 | [[package]] 1141 | name = "unicode-xid" 1142 | version = "0.2.1" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 1145 | 1146 | [[package]] 1147 | name = "universal-hash" 1148 | version = "0.3.0" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "df0c900f2f9b4116803415878ff48b63da9edb268668e08cf9292d7503114a01" 1151 | dependencies = [ 1152 | "generic-array", 1153 | "subtle 2.4.0", 1154 | ] 1155 | 1156 | [[package]] 1157 | name = "url" 1158 | version = "1.7.2" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" 1161 | dependencies = [ 1162 | "idna", 1163 | "matches", 1164 | "percent-encoding 1.0.1", 1165 | ] 1166 | 1167 | [[package]] 1168 | name = "version_check" 1169 | version = "0.1.5" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 1172 | 1173 | [[package]] 1174 | name = "version_check" 1175 | version = "0.9.2" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 1178 | 1179 | [[package]] 1180 | name = "walkdir" 1181 | version = "2.3.1" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 1184 | dependencies = [ 1185 | "same-file", 1186 | "winapi", 1187 | "winapi-util", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "wasi" 1192 | version = "0.9.0+wasi-snapshot-preview1" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1195 | 1196 | [[package]] 1197 | name = "wasi" 1198 | version = "0.10.2+wasi-snapshot-preview1" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1201 | 1202 | [[package]] 1203 | name = "winapi" 1204 | version = "0.3.9" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1207 | dependencies = [ 1208 | "winapi-i686-pc-windows-gnu", 1209 | "winapi-x86_64-pc-windows-gnu", 1210 | ] 1211 | 1212 | [[package]] 1213 | name = "winapi-i686-pc-windows-gnu" 1214 | version = "0.4.0" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1217 | 1218 | [[package]] 1219 | name = "winapi-util" 1220 | version = "0.1.5" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1223 | dependencies = [ 1224 | "winapi", 1225 | ] 1226 | 1227 | [[package]] 1228 | name = "winapi-x86_64-pc-windows-gnu" 1229 | version = "0.4.0" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1232 | 1233 | [[package]] 1234 | name = "xml-rs" 1235 | version = "0.8.3" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" 1238 | 1239 | [[package]] 1240 | name = "yaml-rust" 1241 | version = "0.4.5" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 1244 | dependencies = [ 1245 | "linked-hash-map", 1246 | ] 1247 | 1248 | [[package]] 1249 | name = "yansi" 1250 | version = "0.5.0" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" 1253 | 1254 | [[package]] 1255 | name = "zeroize" 1256 | version = "1.2.0" 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" 1258 | checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36" 1259 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Oliver Schneider "] 3 | description = "A graphical debugger for Rust MIR." 4 | license = "MIT/Apache-2.0" 5 | name = "priroda" 6 | repository = "https://github.com/oli-obk/priroda" 7 | version = "0.1.0" 8 | edition = "2018" 9 | 10 | 11 | [dependencies] 12 | regex = "1.3" 13 | lazy_static = "1.4.0" 14 | rocket = "0.4.7" 15 | # This needs to be the version of miri for the nightly in the `rust-toolchain` file 16 | miri = { git = "https://github.com/rust-lang/miri.git", rev = "ae964207bb17911cf96d9744d9469fa2734093a8" } 17 | 18 | log = "0.4" 19 | env_logger = "0.7" 20 | 21 | serde = { version = "1.0", features = ["derive"] } 22 | serde_json = "1.0" 23 | 24 | open = "1.4.0" 25 | syntect = "4.2" 26 | horrorshow = "0.8" 27 | cgraph = { git = "https://github.com/oli-obk/cgraph.git", rev = "5e6a6d4527522609772a9a9a1565b74c9bfe1560" } 28 | 29 | # Uncomment to use local checkout of miri 30 | # [patch."https://github.com/rust-lang/miri.git"] 31 | # miri = { path = "../miri" } 32 | 33 | [features] 34 | static_resources = [] 35 | 36 | [package.metadata.rust-analyzer] 37 | rustc_private = true 38 | 39 | [profile.dev] 40 | debug = 1 41 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 The Miri Developers 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 The Miri Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Priroda 2 | 3 | Priroda is a graphical (UI in browser) debugger for Rust programs 4 | 5 | ## Setup 6 | 7 | You need a few things before you can get started. At the very minimum you need to have 8 | the graphviz libraries present. 9 | 10 | * debian/ubuntu: `apt install libgraphviz-dev` 11 | 12 | Next, you're going to want a libstd with full MIR. The easiest way to obtain this is via 13 | `cargo miri`: 14 | 15 | ```bash 16 | # Install cargo miri: 17 | rustup component add miri 18 | # Compile libstd: 19 | cargo miri setup 20 | # Set the MIRI_SYSROOT environment variable to the path printed by the setup command: 21 | export MIRI_SYSROOT=... 22 | ``` 23 | 24 | ## Features 25 | 26 | * Supports commands known from gdb 27 | * next, step, continue 28 | * Inspect memory of all stack frames visually 29 | * Follow pointers by clicking hyperlinks 30 | * Track your progress through a function in a graph of the MIR 31 | * Style your debugging experience with CSS 32 | 33 | ## Usage 34 | 35 | `cargo run some_rust_file.rs` will automatically start a http server and open a 36 | browser. UI is changing rapidly right now, so you need to figure out how to use 37 | it by yourself (or by asking on irc) for now. 38 | 39 | ## Contributing and getting help 40 | 41 | Check out the issues on this GitHub repository for some ideas. There's lots that 42 | needs to be done that I haven't documented in the issues yet, however. For more 43 | ideas or help with running or hacking on Priroda, you can ask at [`#miri`] on the 44 | rust-lang zulip. 45 | 46 | [`#miri`]: https://rust-lang.zulipchat.com/#narrow/stream/269128-miri 47 | 48 | ### Miri 49 | 50 | This project depends entirely on [Miri](https://github.com/rust-lang/miri). 51 | So if you want to improve something that we fail to interpret, add a unit test 52 | to Miri and fix it there. 53 | 54 | ## License 55 | 56 | Licensed under either of 57 | 58 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE)) 59 | * MIT license ([LICENSE-MIT](LICENSE-MIT)) 60 | 61 | ### Contribution 62 | 63 | Unless you explicitly state otherwise, any contribution intentionally submitted 64 | for inclusion in the work by you shall be dual licensed as above, without any 65 | additional terms or conditions. 66 | -------------------------------------------------------------------------------- /Rocket.toml: -------------------------------------------------------------------------------- 1 | [development] 2 | address = "localhost" 3 | port = 54321 4 | #spawn_browser = true 5 | 6 | [production] 7 | address = "0.0.0.0" 8 | port = 8080 9 | spawn_browser = false -------------------------------------------------------------------------------- /example.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | fn some_fn() { 4 | let mut val = 2; 5 | let mut my_wrapper = MyWrapper(&mut val); 6 | let wrapper_ref = &mut my_wrapper; 7 | for i in 0..16 { 8 | *wrapper_ref.0 = i; 9 | } 10 | } 11 | 12 | struct MyWrapper<'a>(&'a mut u8); 13 | 14 | struct SomeRandomStruct<'a, A> { 15 | ernrer: u8, 16 | feijoc: i64, 17 | ioieoe: bool, 18 | fewije: char, 19 | chr_ref: &'a char, 20 | efoiri: A, 21 | irrfio: SomeOtherStruct, 22 | } 23 | 24 | struct SomeOtherStruct { 25 | efufrr: u8, 26 | frireg: u16, 27 | } 28 | 29 | union SomeUnion { 30 | a: bool, 31 | b: u64, 32 | } 33 | 34 | fn main() { 35 | let chr = '4'; 36 | let _rer = SomeRandomStruct { 37 | ernrer: 24, 38 | feijoc: -34438, 39 | ioieoe: true, 40 | fewije: '@', 41 | chr_ref: &chr, 42 | efoiri: Some(2u16), 43 | irrfio: SomeOtherStruct { 44 | efufrr: 34, 45 | frireg: 45804, 46 | }, 47 | }; 48 | let u = SomeUnion { a: true }; 49 | let abc = Box::new(42); 50 | some_fn(); 51 | let f = 1.4f64; 52 | let _sum = 1f64 53 | + 54 | f; 55 | let _s = "ieeoe"; 56 | let _bcd: Box<[u8]> = Box::new([0, 1]); 57 | let _d = true; 58 | format!("ewioio: {}", abc); 59 | } 60 | -------------------------------------------------------------------------------- /resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oli-obk/priroda/0cc9d44c37266e93822b0c4d4db96226d1368a50/resources/favicon.ico -------------------------------------------------------------------------------- /resources/positioning.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0px; 7 | height: 100vh; 8 | overflow-y: hidden; 9 | } 10 | 11 | #left, #right { 12 | display: inline-block; 13 | width: calc(50vw - 4px); 14 | height: 100vh; 15 | margin: 2px; 16 | vertical-align: top; 17 | overflow: auto; 18 | } 19 | 20 | #mir { 21 | width: 100%; 22 | height: calc(100% - 150px); 23 | } 24 | 25 | #mir > svg { 26 | display: block; 27 | max-width: 100%; 28 | max-height: 100%; 29 | width: auto; 30 | height: auto; 31 | } 32 | 33 | #mir > svg:first-child { 34 | width: 100%; 35 | height: 100%; 36 | } 37 | 38 | #stack, #locals { 39 | overflow: auto; 40 | } 41 | 42 | .vertical { 43 | writing-mode: tb-rl; 44 | } 45 | 46 | @media only screen and (max-width: 600px) { 47 | body { 48 | height: unset; 49 | overflow-y: scroll; 50 | } 51 | #left, #right { 52 | display: block; 53 | width: 100vw; 54 | height: unset; 55 | margin: 2px 0; 56 | padding: 0 5px; 57 | } 58 | #mir { 59 | width: calc(100vw - 100px) !important; 60 | height: unset; 61 | } 62 | #mir:first-child { 63 | height: 50vh !important; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /resources/style-default.css: -------------------------------------------------------------------------------- 1 | #mir { 2 | border: 2px grey solid; 3 | } 4 | 5 | #stack { 6 | border: grey 2px solid; 7 | margin: 2px 0; 8 | padding: 2px; 9 | } 10 | 11 | #stack table tr td:nth-child(1) { 12 | color: red; 13 | } 14 | 15 | #locals { 16 | border: grey 2px solid; 17 | margin: 2px 0; 18 | } 19 | 20 | #locals table { 21 | border-collapse:collapse; 22 | border: none; 23 | font-family: monospace; 24 | } 25 | 26 | /* left top corner */ 27 | #locals table tbody :first-child td { 28 | border: none; 29 | } 30 | 31 | #locals table tr th { 32 | background-color: lightgrey; 33 | } 34 | 35 | #locals table tr th { 36 | min-width: 14px; 37 | font-size: 12px 38 | } 39 | 40 | #return_ptr { 41 | display: inline-block; 42 | border: 1px solid black; 43 | } 44 | 45 | #commands { 46 | display: flex; 47 | width: 100%; 48 | flex-wrap: wrap; 49 | padding: 2px; 50 | } 51 | 52 | #commands > a { 53 | text-decoration: none; 54 | padding-right: 2px; 55 | margin: 3px; 56 | } 57 | 58 | #commands > a > div { 59 | padding: 5px 7px; 60 | background: #4479BA; 61 | color: #FFF; 62 | border-radius: 4px; 63 | border: solid 1px #20538D; 64 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.4); 65 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 1px 1px rgba(0, 0, 0, 0.2); 66 | transition-duration: 0.2s; 67 | user-select:none; 68 | } 69 | 70 | #commands > a > div:hover { 71 | background: #356094; 72 | border: solid 1px #2A4E77; 73 | text-decoration: none; 74 | } 75 | 76 | #commands > a > div:active { 77 | box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6); 78 | background: #2E5481; 79 | border: solid 1px #203E5F; 80 | } 81 | 82 | #stack table { 83 | border: none; 84 | border-collapse: collapse; 85 | } 86 | 87 | #stack table tr :first-child { 88 | border: none; 89 | } 90 | 91 | td { 92 | padding: 0px; 93 | } 94 | -------------------------------------------------------------------------------- /resources/svg-pan-zoom.js: -------------------------------------------------------------------------------- 1 | // svg-pan-zoom v3.5.2 2 | // https://github.com/ariutta/svg-pan-zoom 3 | !function t(e,o,n){function i(r,a){if(!o[r]){if(!e[r]){var l="function"==typeof require&&require;if(!a&&l)return l(r,!0);if(s)return s(r,!0);var u=new Error("Cannot find module '"+r+"'");throw u.code="MODULE_NOT_FOUND",u}var h=o[r]={exports:{}};e[r][0].call(h.exports,function(t){var o=e[r][1][t];return i(o?o:t)},h,h.exports,t,e,o,n)}return o[r].exports}for(var s="function"==typeof require&&require,r=0;r=0;n--)this.eventListeners.hasOwnProperty(o[n])&&delete this.eventListeners[o[n]]}for(var i in this.eventListeners)(this.options.eventsListenerElement||this.svg).addEventListener(i,this.eventListeners[i],!1);this.options.mouseWheelZoomEnabled&&(this.options.mouseWheelZoomEnabled=!1,this.enableMouseWheelZoom())},l.prototype.enableMouseWheelZoom=function(){if(!this.options.mouseWheelZoomEnabled){var t=this;this.wheelListener=function(e){return t.handleMouseWheel(e)},n.on(this.options.eventsListenerElement||this.svg,this.wheelListener,!1),this.options.mouseWheelZoomEnabled=!0}},l.prototype.disableMouseWheelZoom=function(){this.options.mouseWheelZoomEnabled&&(n.off(this.options.eventsListenerElement||this.svg,this.wheelListener,!1),this.options.mouseWheelZoomEnabled=!1)},l.prototype.handleMouseWheel=function(t){if(this.options.zoomEnabled&&"none"===this.state){this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1);var e=t.deltaY||1,o=Date.now()-this.lastMouseWheelEventTime,n=3+Math.max(0,30-o);this.lastMouseWheelEventTime=Date.now(),"deltaMode"in t&&0===t.deltaMode&&t.wheelDelta&&(e=0===t.deltaY?0:Math.abs(t.wheelDelta)/t.deltaY),e=-.30?1:-1)*Math.log(Math.abs(e)+10)/n;var i=this.svg.getScreenCTM().inverse(),s=r.getEventPoint(t,this.svg).matrixTransform(i),a=Math.pow(1+this.options.zoomScaleSensitivity,-1*e);this.zoomAtPoint(a,s)}},l.prototype.zoomAtPoint=function(t,e,o){var n=this.viewport.getOriginalState();o?(t=Math.max(this.options.minZoom*n.zoom,Math.min(this.options.maxZoom*n.zoom,t)),t/=this.getZoom()):this.getZoom()*tthis.options.maxZoom*n.zoom&&(t=this.options.maxZoom*n.zoom/this.getZoom());var i=this.viewport.getCTM(),s=e.matrixTransform(i.inverse()),r=this.svg.createSVGMatrix().translate(s.x,s.y).scale(t).translate(-s.x,-s.y),a=i.multiply(r);a.a!==i.a&&this.viewport.setCTM(a)},l.prototype.zoom=function(t,e){this.zoomAtPoint(t,r.getSvgCenterPoint(this.svg,this.width,this.height),e)},l.prototype.publicZoom=function(t,e){e&&(t=this.computeFromRelativeZoom(t)),this.zoom(t,e)},l.prototype.publicZoomAtPoint=function(t,e,o){if(o&&(t=this.computeFromRelativeZoom(t)),"SVGPoint"!==s.getType(e)){if(!("x"in e&&"y"in e))throw new Error("Given point is invalid");e=r.createSVGPoint(this.svg,e.x,e.y)}this.zoomAtPoint(t,e,o)},l.prototype.getZoom=function(){return this.viewport.getZoom()},l.prototype.getRelativeZoom=function(){return this.viewport.getRelativeZoom()},l.prototype.computeFromRelativeZoom=function(t){return t*this.viewport.getOriginalState().zoom},l.prototype.resetZoom=function(){var t=this.viewport.getOriginalState();this.zoom(t.zoom,!0)},l.prototype.resetPan=function(){this.pan(this.viewport.getOriginalState())},l.prototype.reset=function(){this.resetZoom(),this.resetPan()},l.prototype.handleDblClick=function(t){if(this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),this.options.controlIconsEnabled){var e=t.target.getAttribute("class")||"";if(e.indexOf("svg-pan-zoom-control")>-1)return!1}var o;o=t.shiftKey?1/(2*(1+this.options.zoomScaleSensitivity)):2*(1+this.options.zoomScaleSensitivity);var n=r.getEventPoint(t,this.svg).matrixTransform(this.svg.getScreenCTM().inverse());this.zoomAtPoint(o,n)},l.prototype.handleMouseDown=function(t,e){this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),s.mouseAndTouchNormalize(t,this.svg),this.options.dblClickZoomEnabled&&s.isDblClick(t,e)?this.handleDblClick(t):(this.state="pan",this.firstEventCTM=this.viewport.getCTM(),this.stateOrigin=r.getEventPoint(t,this.svg).matrixTransform(this.firstEventCTM.inverse()))},l.prototype.handleMouseMove=function(t){if(this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),"pan"===this.state&&this.options.panEnabled){var e=r.getEventPoint(t,this.svg).matrixTransform(this.firstEventCTM.inverse()),o=this.firstEventCTM.translate(e.x-this.stateOrigin.x,e.y-this.stateOrigin.y);this.viewport.setCTM(o)}},l.prototype.handleMouseUp=function(t){this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),"pan"===this.state&&(this.state="none")},l.prototype.fit=function(){var t=this.viewport.getViewBox(),e=Math.min(this.width/t.width,this.height/t.height);this.zoom(e,!0)},l.prototype.contain=function(){var t=this.viewport.getViewBox(),e=Math.max(this.width/t.width,this.height/t.height);this.zoom(e,!0)},l.prototype.center=function(){var t=this.viewport.getViewBox(),e=.5*(this.width-(t.width+2*t.x)*this.getZoom()),o=.5*(this.height-(t.height+2*t.y)*this.getZoom());this.getPublicInstance().pan({x:e,y:o})},l.prototype.updateBBox=function(){this.viewport.simpleViewBoxCache()},l.prototype.pan=function(t){var e=this.viewport.getCTM();e.e=t.x,e.f=t.y,this.viewport.setCTM(e)},l.prototype.panBy=function(t){var e=this.viewport.getCTM();e.e+=t.x,e.f+=t.y,this.viewport.setCTM(e)},l.prototype.getPan=function(){var t=this.viewport.getState();return{x:t.x,y:t.y}},l.prototype.resize=function(){var t=r.getBoundingClientRectNormalized(this.svg);this.width=t.width,this.height=t.height;var e=this.viewport;e.options.width=this.width,e.options.height=this.height,e.processCTM(),this.options.controlIconsEnabled&&(this.getPublicInstance().disableControlIcons(),this.getPublicInstance().enableControlIcons())},l.prototype.destroy=function(){var t=this;this.beforeZoom=null,this.onZoom=null,this.beforePan=null,this.onPan=null,this.onUpdatedCTM=null,null!=this.options.customEventsHandler&&this.options.customEventsHandler.destroy({svgElement:this.svg,eventsListenerElement:this.options.eventsListenerElement,instance:this.getPublicInstance()});for(var e in this.eventListeners)(this.options.eventsListenerElement||this.svg).removeEventListener(e,this.eventListeners[e],!1);this.disableMouseWheelZoom(),this.getPublicInstance().disableControlIcons(),this.reset(),h=h.filter(function(e){return e.svg!==t.svg}),delete this.options,delete this.viewport,delete this.publicInstance,delete this.pi,this.getPublicInstance=function(){return null}},l.prototype.getPublicInstance=function(){var t=this;return this.publicInstance||(this.publicInstance=this.pi={enablePan:function(){return t.options.panEnabled=!0,t.pi},disablePan:function(){return t.options.panEnabled=!1,t.pi},isPanEnabled:function(){return!!t.options.panEnabled},pan:function(e){return t.pan(e),t.pi},panBy:function(e){return t.panBy(e),t.pi},getPan:function(){return t.getPan()},setBeforePan:function(e){return t.options.beforePan=null===e?null:s.proxy(e,t.publicInstance),t.pi},setOnPan:function(e){return t.options.onPan=null===e?null:s.proxy(e,t.publicInstance),t.pi},enableZoom:function(){return t.options.zoomEnabled=!0,t.pi},disableZoom:function(){return t.options.zoomEnabled=!1,t.pi},isZoomEnabled:function(){return!!t.options.zoomEnabled},enableControlIcons:function(){return t.options.controlIconsEnabled||(t.options.controlIconsEnabled=!0,i.enable(t)),t.pi},disableControlIcons:function(){return t.options.controlIconsEnabled&&(t.options.controlIconsEnabled=!1,i.disable(t)),t.pi},isControlIconsEnabled:function(){return!!t.options.controlIconsEnabled},enableDblClickZoom:function(){return t.options.dblClickZoomEnabled=!0,t.pi},disableDblClickZoom:function(){return t.options.dblClickZoomEnabled=!1,t.pi},isDblClickZoomEnabled:function(){return!!t.options.dblClickZoomEnabled},enableMouseWheelZoom:function(){return t.enableMouseWheelZoom(),t.pi},disableMouseWheelZoom:function(){return t.disableMouseWheelZoom(),t.pi},isMouseWheelZoomEnabled:function(){return!!t.options.mouseWheelZoomEnabled},setZoomScaleSensitivity:function(e){return t.options.zoomScaleSensitivity=e,t.pi},setMinZoom:function(e){return t.options.minZoom=e,t.pi},setMaxZoom:function(e){return t.options.maxZoom=e,t.pi},setBeforeZoom:function(e){return t.options.beforeZoom=null===e?null:s.proxy(e,t.publicInstance),t.pi},setOnZoom:function(e){return t.options.onZoom=null===e?null:s.proxy(e,t.publicInstance),t.pi},zoom:function(e){return t.publicZoom(e,!0),t.pi},zoomBy:function(e){return t.publicZoom(e,!1),t.pi},zoomAtPoint:function(e,o){return t.publicZoomAtPoint(e,o,!0),t.pi},zoomAtPointBy:function(e,o){return t.publicZoomAtPoint(e,o,!1),t.pi},zoomIn:function(){return this.zoomBy(1+t.options.zoomScaleSensitivity),t.pi},zoomOut:function(){return this.zoomBy(1/(1+t.options.zoomScaleSensitivity)),t.pi},getZoom:function(){return t.getRelativeZoom()},setOnUpdatedCTM:function(e){return t.options.onUpdatedCTM=null===e?null:s.proxy(e,t.publicInstance),t.pi},resetZoom:function(){return t.resetZoom(),t.pi},resetPan:function(){return t.resetPan(),t.pi},reset:function(){return t.reset(),t.pi},fit:function(){return t.fit(),t.pi},contain:function(){return t.contain(),t.pi},center:function(){return t.center(),t.pi},updateBBox:function(){return t.updateBBox(),t.pi},resize:function(){return t.resize(),t.pi},getSizes:function(){return{width:t.width,height:t.height,realZoom:t.getZoom(),viewBox:t.viewport.getViewBox()}},destroy:function(){return t.destroy(),t.pi}}),this.publicInstance};var h=[],c=function(t,e){var o=s.getSvg(t);if(null===o)return null;for(var n=h.length-1;n>=0;n--)if(h[n].svg===o)return h[n].instance.getPublicInstance();return h.push({svg:o,instance:new l(o,e)}),h[h.length-1].instance.getPublicInstance()};e.exports=c},{"./control-icons":2,"./shadow-viewport":3,"./svg-utilities":5,"./uniwheel":6,"./utilities":7}],5:[function(t,e,o){var n=t("./utilities"),i="unknown";document.documentMode&&(i="ie"),e.exports={svgNS:"http://www.w3.org/2000/svg",xmlNS:"http://www.w3.org/XML/1998/namespace",xmlnsNS:"http://www.w3.org/2000/xmlns/",xlinkNS:"http://www.w3.org/1999/xlink",evNS:"http://www.w3.org/2001/xml-events",getBoundingClientRectNormalized:function(t){if(t.clientWidth&&t.clientHeight)return{width:t.clientWidth,height:t.clientHeight};if(t.getBoundingClientRect())return t.getBoundingClientRect();throw new Error("Cannot get BoundingClientRect for SVG.")},getOrCreateViewport:function(t,e){var o=null;if(o=n.isElement(e)?e:t.querySelector(e),!o){var i=Array.prototype.slice.call(t.childNodes||t.children).filter(function(t){return"defs"!==t.nodeName&&"#text"!==t.nodeName});1===i.length&&"g"===i[0].nodeName&&null===i[0].getAttribute("transform")&&(o=i[0])}if(!o){var s="viewport-"+(new Date).toISOString().replace(/\D/g,"");o=document.createElementNS(this.svgNS,"g"),o.setAttribute("id",s);var r=t.childNodes||t.children;if(r&&r.length>0)for(var a=r.length;a>0;a--)"defs"!==r[r.length-a].nodeName&&o.appendChild(r[r.length-a]);t.appendChild(o)}var l=[];return o.getAttribute("class")&&(l=o.getAttribute("class").split(" ")),~l.indexOf("svg-pan-zoom_viewport")||(l.push("svg-pan-zoom_viewport"),o.setAttribute("class",l.join(" "))),o},setupSvgAttributes:function(t){if(t.setAttribute("xmlns",this.svgNS),t.setAttributeNS(this.xmlnsNS,"xmlns:xlink",this.xlinkNS),t.setAttributeNS(this.xmlnsNS,"xmlns:ev",this.evNS),null!==t.parentNode){var e=t.getAttribute("style")||"";e.toLowerCase().indexOf("overflow")===-1&&t.setAttribute("style","overflow: hidden; "+e)}},internetExplorerRedisplayInterval:300,refreshDefsGlobal:n.throttle(function(){for(var t=document.querySelectorAll("defs"),e=t.length,o=0;oe?(clearTimeout(a),a=null,l=h,s=t.apply(n,i),a||(n=i=null)):a||o.trailing===!1||(a=setTimeout(u,c)),s}},createRequestAnimationFrame:function(t){var e=null;return"auto"!==t&&t<60&&t>1&&(e=Math.floor(1e3/t)),null===e?window.requestAnimationFrame||n(33):n(e)}}},{}]},{},[1]); -------------------------------------------------------------------------------- /resources/zoom_mir.js: -------------------------------------------------------------------------------- 1 | function enable_mir_mousewheel() { 2 | var svg = svgPanZoom("#mir > svg", { 3 | controlIconsEnabled: true, 4 | zoomScaleSensitivity: 0.5, 5 | maxZoom: 50.0, 6 | minZoom: 1.0, 7 | fit: true, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2021-03-13" 3 | components = ["miri", "rustc-dev", "llvm-tools-preview"] 4 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private, decl_macro, plugin, try_blocks, proc_macro_hygiene)] 2 | #![feature(never_type)] 3 | #![allow(unused_attributes)] 4 | #![recursion_limit = "5000"] 5 | #![warn(rust_2018_idioms)] 6 | 7 | extern crate rustc_driver; 8 | extern crate rustc_hir; 9 | extern crate rustc_index; 10 | extern crate rustc_interface; 11 | extern crate rustc_middle; 12 | extern crate rustc_mir; 13 | extern crate rustc_span; 14 | extern crate rustc_target; 15 | extern crate rustc_type_ir; 16 | 17 | #[macro_use] 18 | extern crate rocket; 19 | 20 | mod render; 21 | mod step; 22 | mod watch; 23 | 24 | use std::ops::FnOnce; 25 | use std::path::PathBuf; 26 | use std::sync::mpsc; 27 | use std::sync::{Arc, Mutex}; 28 | 29 | use miri::Evaluator; 30 | use rustc_driver::Compilation; 31 | use rustc_hir::def_id::LOCAL_CRATE; 32 | use rustc_interface::interface; 33 | use rustc_middle::mir; 34 | use rustc_middle::ty::TyCtxt; 35 | 36 | use rocket::response::content::*; 37 | use rocket::response::status::BadRequest; 38 | use rocket::response::NamedFile; 39 | use rocket::State; 40 | 41 | use serde::Deserialize; 42 | use step::{step, ShouldContinue}; 43 | 44 | use crate::step::BreakpointTree; 45 | 46 | fn should_hide_stmt(stmt: &mir::Statement<'_>) -> bool { 47 | use rustc_middle::mir::StatementKind::*; 48 | match stmt.kind { 49 | StorageLive(_) | StorageDead(_) | Nop => true, 50 | _ => false, 51 | } 52 | } 53 | 54 | type InterpCx<'tcx> = rustc_mir::interpret::InterpCx<'tcx, 'tcx, Evaluator<'tcx, 'tcx>>; 55 | 56 | pub struct PrirodaContext<'a, 'tcx: 'a> { 57 | ecx: InterpCx<'tcx>, 58 | step_count: &'a mut u128, 59 | traces: watch::Traces<'tcx>, 60 | config: &'a mut Config, 61 | skip_to_main: bool, 62 | } 63 | 64 | impl<'a, 'tcx: 'a> PrirodaContext<'a, 'tcx> { 65 | fn restart(&mut self) { 66 | self.ecx = create_ecx(self.ecx.tcx.tcx); 67 | *self.step_count = 0; 68 | self.traces.clear(); // Cleanup all traces 69 | self.to_main(); 70 | } 71 | 72 | // Step to main 73 | fn to_main(&mut self) { 74 | if self.skip_to_main { 75 | let main_id = self 76 | .ecx 77 | .tcx 78 | .tcx 79 | .entry_fn(LOCAL_CRATE) 80 | .expect("no main or start function found") 81 | .0 82 | .to_def_id(); 83 | 84 | let _ = step(self, |ecx| { 85 | let frame = ecx.frame(); 86 | if main_id == frame.instance.def_id() { 87 | ShouldContinue::Stop 88 | } else { 89 | ShouldContinue::Continue 90 | } 91 | }); 92 | } 93 | } 94 | } 95 | 96 | #[derive(Deserialize)] 97 | pub struct Config { 98 | #[serde(default = "true_bool")] 99 | auto_refresh: bool, 100 | #[serde(default = "default_theme")] 101 | theme: String, 102 | #[serde(default)] 103 | bptree: BreakpointTree, 104 | } 105 | 106 | fn true_bool() -> bool { 107 | true 108 | } 109 | fn default_theme() -> String { 110 | "default".to_string() 111 | } 112 | 113 | impl Default for Config { 114 | fn default() -> Self { 115 | ::std::fs::File::open("config.json") 116 | .map(|f| serde_json::from_reader(f).unwrap()) 117 | .unwrap_or(Config { 118 | auto_refresh: true, 119 | theme: "default".to_string(), 120 | bptree: step::BreakpointTree::default(), 121 | }) 122 | } 123 | } 124 | 125 | type RResult = Result>; 126 | 127 | fn create_ecx<'mir, 'tcx>(tcx: TyCtxt<'tcx>) -> InterpCx<'tcx> { 128 | let (main_id, _) = tcx 129 | .entry_fn(LOCAL_CRATE) 130 | .expect("no main or start function found"); 131 | 132 | miri::create_ecx( 133 | tcx, 134 | main_id.to_def_id(), 135 | miri::MiriConfig { 136 | args: vec![], 137 | communicate: true, 138 | excluded_env_vars: vec![], 139 | ignore_leaks: true, 140 | seed: None, 141 | tracked_pointer_tag: None, 142 | tracked_alloc_id: None, 143 | tracked_call_id: None, 144 | validate: true, 145 | stacked_borrows: false, 146 | check_alignment: miri::AlignmentCheck::None, 147 | track_raw: false, 148 | data_race_detector: false, 149 | cmpxchg_weak_failure_rate: 0.8, 150 | }, 151 | ) 152 | .unwrap() 153 | .0 154 | } 155 | 156 | pub struct PrirodaSender( 157 | Mutex<::std::sync::mpsc::Sender) + Send>>>, 158 | ); 159 | 160 | impl PrirodaSender { 161 | fn do_work<'r, T, F>(&self, f: F) -> Result> 162 | where 163 | T: rocket::response::Responder<'r> + Send + 'static, 164 | F: FnOnce(&mut PrirodaContext<'_, '_>) -> T + Send + 'static, 165 | { 166 | let (tx, rx) = mpsc::sync_channel(0); 167 | let sender = self.0.lock().unwrap_or_else(|err| err.into_inner()); 168 | match sender.send(Box::new(move |pcx: &mut PrirodaContext<'_, '_>| { 169 | tx.send(f(pcx)).unwrap(); 170 | })) { 171 | Ok(()) => match rx.recv() { 172 | Ok(val) => Ok(val), 173 | Err(mpsc::RecvError) => Err(Html( 174 | "

Miri crashed please go to index

" 175 | .to_string(), 176 | )), 177 | }, 178 | Err(_) => Err(Html( 179 | "

Miri crashed too often. Please restart priroda.

" 180 | .to_string(), 181 | )), 182 | } 183 | } 184 | } 185 | 186 | #[get("/please_panic")] 187 | fn please_panic(sender: State<'_, PrirodaSender>) -> RResult<()> { 188 | sender.do_work(|_pcx| panic!("You requested a panic")) 189 | } 190 | 191 | #[cfg(not(feature = "static_resources"))] 192 | #[get("/resources/")] 193 | fn resources(path: PathBuf) -> Result { 194 | let mut res_path = PathBuf::from("./resources/"); 195 | res_path.push(path); 196 | NamedFile::open(res_path) 197 | } 198 | 199 | #[cfg(feature = "static_resources")] 200 | #[get("/resources/")] 201 | fn resources(path: PathBuf) -> Result, std::io::Error> { 202 | use rocket::http::ContentType; 203 | use std::io::{Error, ErrorKind}; 204 | match path.as_os_str().to_str() { 205 | Some("svg-pan-zoom.js") => Ok(Content( 206 | ContentType::JavaScript, 207 | include_str!("../resources/svg-pan-zoom.js"), 208 | )), 209 | Some("zoom_mir.js") => Ok(Content( 210 | ContentType::JavaScript, 211 | include_str!("../resources/zoom_mir.js"), 212 | )), 213 | Some("style-default.css") => Ok(Content( 214 | ContentType::CSS, 215 | include_str!("../resources/style-default.css"), 216 | )), 217 | Some("positioning.css") => Ok(Content( 218 | ContentType::CSS, 219 | include_str!("../resources/positioning.css"), 220 | )), 221 | _ => Err(Error::new(ErrorKind::InvalidInput, "Unknown resource")), 222 | } 223 | } 224 | 225 | #[get("/step_count")] 226 | fn step_count(sender: State<'_, PrirodaSender>) -> RResult { 227 | sender.do_work(|pcx| format!("{}", pcx.step_count)) 228 | } 229 | 230 | fn server(sender: PrirodaSender) { 231 | use rocket::config::Value; 232 | rocket::ignite() 233 | .manage(sender) 234 | .mount("/", routes![please_panic, resources, step_count]) 235 | .mount("/", render::routes::routes()) 236 | .mount("/breakpoints", step::bp_routes::routes()) 237 | .mount("/step", step::step_routes::routes()) 238 | .mount("/watch", watch::routes()) 239 | .attach(rocket::fairing::AdHoc::on_launch( 240 | "Priroda, because code has no privacy rights", 241 | |rocket| { 242 | let config = rocket.config(); 243 | if config.extras.get("spawn_browser") == Some(&Value::Boolean(true)) { 244 | let addr = format!("http://{}:{}", config.address, config.port); 245 | if open::that(&addr).is_err() { 246 | println!("open {} in your browser", addr); 247 | } 248 | } 249 | }, 250 | )) 251 | .launch(); 252 | } 253 | 254 | // Copied from miri/bin/miri.rs 255 | fn find_sysroot() -> String { 256 | if let Ok(sysroot) = std::env::var("MIRI_SYSROOT") { 257 | return sysroot; 258 | } 259 | 260 | // Taken from https://github.com/rust-lang/rust-clippy/pull/911 261 | let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME")); 262 | let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN")); 263 | match (home, toolchain) { 264 | (Some(home), Some(toolchain)) => format!("{}/toolchains/{}", home, toolchain), 265 | _ => option_env!("RUST_SYSROOT") 266 | .expect("need to specify RUST_SYSROOT env var or use rustup or multirust") 267 | .to_owned(), 268 | } 269 | } 270 | 271 | struct PrirodaCompilerCalls { 272 | step_count: Arc>, 273 | config: Arc>, 274 | receiver: Arc) + Send>>>>, 275 | skip_to_main: bool, 276 | } 277 | 278 | impl rustc_driver::Callbacks for PrirodaCompilerCalls { 279 | fn after_analysis<'tcx>( 280 | &mut self, 281 | compiler: &interface::Compiler, 282 | queries: &'tcx rustc_interface::Queries<'tcx>, 283 | ) -> Compilation { 284 | compiler.session().abort_if_errors(); 285 | 286 | queries.global_ctxt().unwrap().peek_mut().enter(|tcx| { 287 | let mut step_count = self 288 | .step_count 289 | .lock() 290 | .unwrap_or_else(|err| err.into_inner()); 291 | let mut config = self.config.lock().unwrap_or_else(|err| err.into_inner()); 292 | 293 | let mut pcx = PrirodaContext { 294 | ecx: create_ecx(tcx), 295 | step_count: &mut *step_count, 296 | traces: watch::Traces::new(), 297 | config: &mut *config, 298 | skip_to_main: self.skip_to_main, 299 | }; 300 | // Whether we reset to the same place where miri crashed 301 | let mut reset = false; 302 | // Step to the position where miri crashed if it crashed 303 | for _ in 0..*pcx.step_count { 304 | reset = true; 305 | match pcx.ecx.step() { 306 | Ok(true) => {} 307 | res => panic!("Miri is not deterministic causing error {:?}", res), 308 | } 309 | } 310 | 311 | // Just ignore poisoning by panicking 312 | let receiver = self.receiver.lock().unwrap_or_else(|err| err.into_inner()); 313 | 314 | // At the very beginning, go to the start of main unless we have already crashed and are trying to reset 315 | if !reset { 316 | pcx.to_main(); 317 | } 318 | // process commands 319 | for command in receiver.iter() { 320 | command(&mut pcx); 321 | } 322 | }); 323 | 324 | compiler.session().abort_if_errors(); 325 | 326 | Compilation::Stop 327 | } 328 | } 329 | 330 | fn main() { 331 | rustc_driver::init_rustc_env_logger(); 332 | let mut args: Vec = std::env::args().collect(); 333 | 334 | // TODO: Move to a 'proper' argument parser 335 | // Maybe use xflags? 336 | 337 | let sysroot_flag = String::from("--sysroot"); 338 | if !args.contains(&sysroot_flag) { 339 | args.push(sysroot_flag); 340 | args.push(find_sysroot()); 341 | } 342 | 343 | // Use the --no-main flag to stop 344 | let stops_at_start = args 345 | .iter() 346 | .enumerate() 347 | .find(|(_, v)| v.as_str() == "--no-main") 348 | .map(|(i, _)| i); 349 | 350 | if let Some(index) = stops_at_start { 351 | // We pass these arguments to miri. 352 | // This is the lowest-effort way to not pass our custom argument to miri 353 | args.remove(index); 354 | } 355 | // Eagerly initialise syntect static 356 | // Makes highlighting performance profiles clearer 357 | render::initialise_statics(); 358 | 359 | // setup http server and similar 360 | let (sender, receiver) = mpsc::channel(); 361 | let sender = PrirodaSender(Mutex::new(sender)); 362 | let step_count = Arc::new(Mutex::new(0)); 363 | let config = Arc::new(Mutex::new(Config::default())); 364 | 365 | let handle = std::thread::spawn(move || { 366 | let args = Arc::new(args); 367 | let receiver = Arc::new(Mutex::new(receiver)); 368 | for i in 0..5 { 369 | if i != 0 { 370 | println!( 371 | "\n============== Miri crashed - restart try {} ==============\n", 372 | i 373 | ); 374 | } 375 | let step_count = step_count.clone(); 376 | let config = config.clone(); 377 | let receiver = receiver.clone(); 378 | let args = args.clone(); 379 | // Ignore result to restart in case of a crash 380 | let _ = std::thread::spawn(move || { 381 | let _ = rustc_driver::catch_fatal_errors(move || { 382 | rustc_driver::RunCompiler::new( 383 | &*args, 384 | &mut PrirodaCompilerCalls { 385 | step_count, 386 | config, 387 | receiver, 388 | skip_to_main: stops_at_start.is_none(), 389 | }, 390 | ) 391 | .run() 392 | }); 393 | }) 394 | .join(); 395 | std::thread::sleep(std::time::Duration::from_millis(200)); 396 | } 397 | println!("\n============== Miri crashed too often. Aborting ==============\n"); 398 | }); 399 | server(sender); 400 | handle.join().unwrap(); 401 | } 402 | -------------------------------------------------------------------------------- /src/render/graphviz.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | use crate::step::LocalBreakpoints; 12 | use miri::{FrameData, Tag}; 13 | use rustc_middle::mir::*; 14 | use rustc_mir::interpret::Frame; 15 | use std::fmt::{self, Debug, Write}; 16 | 17 | pub fn render_html<'tcx>( 18 | frame: &Frame<'_, '_, Tag, FrameData<'_>>, 19 | breakpoints: LocalBreakpoints<'_>, 20 | ) -> String { 21 | let mut rendered = String::new(); 22 | 23 | render_mir_svg(&frame.body, breakpoints, &mut rendered, None).unwrap(); 24 | 25 | let (block, statement_index) = if let Some(location) = frame.current_loc().ok() { 26 | (location.block, location.statement_index) 27 | } else { 28 | rendered.push_str("
Unwinding
"); 29 | return rendered; 30 | }; 31 | 32 | let (bb, stmt) = { 33 | let blck = &frame.body.basic_blocks()[block]; 34 | ( 35 | block.index() + 1, 36 | if statement_index == blck.statements.len() { 37 | if blck.statements.is_empty() { 38 | 6 39 | } else { 40 | blck.statements.len() + 7 41 | } 42 | } else { 43 | assert!(statement_index < blck.statements.len()); 44 | statement_index + 6 45 | }, 46 | ) 47 | }; 48 | let edge_colors = { 49 | let blck = &frame.body.basic_blocks()[block]; 50 | let (targets, unwind) = if statement_index == blck.statements.len() { 51 | use rustc_middle::mir::TerminatorKind::*; 52 | match blck.terminator().kind { 53 | Goto { target } => (vec![target], None), 54 | SwitchInt { ref targets, .. } => (targets.all_targets().to_owned(), None), 55 | Drop { target, unwind, .. } | DropAndReplace { target, unwind, .. } => { 56 | (vec![target], unwind) 57 | } 58 | Call { 59 | ref destination, 60 | cleanup, 61 | .. 62 | } => { 63 | if let Some((_, target)) = *destination { 64 | (vec![target], cleanup) 65 | } else { 66 | (vec![], cleanup) 67 | } 68 | } 69 | _ => (vec![], None), 70 | } 71 | } else { 72 | (vec![], None) 73 | }; 74 | format!( 75 | "let edge_colors = {{{}}};", 76 | targets 77 | .into_iter() 78 | .map(|target| (block, target, "green")) 79 | .chain(unwind.into_iter().map(|target| (block, target, "red"))) 80 | .map(|(from, to, color)| format!( 81 | "'bb{}->bb{}':'{}'", 82 | from.index(), 83 | to.index(), 84 | color 85 | )) 86 | .collect::>() 87 | .join(",") 88 | ) 89 | }; 90 | rendered 91 | .write_fmt(format_args!( 92 | r##" 108 | "##, 117 | bb, 118 | stmt, 119 | edge_colors = edge_colors 120 | )) 121 | .unwrap(); 122 | rendered 123 | } 124 | 125 | /// Write a graphviz DOT graph of a list of MIRs. 126 | pub fn render_mir_svg( 127 | mir: &Body<'_>, 128 | breakpoints: LocalBreakpoints<'_>, 129 | w: &mut W, 130 | promoted: Option, 131 | ) -> fmt::Result { 132 | let mut dot = String::new(); 133 | if let Some(promoted) = promoted { 134 | writeln!(dot, "digraph promoted{} {{", promoted)?; 135 | } else { 136 | writeln!(dot, "digraph Body {{")?; 137 | } 138 | 139 | // Global graph properties 140 | writeln!(dot, r#" graph [fontname="monospace"];"#)?; 141 | writeln!(dot, r#" node [fontname="monospace"];"#)?; 142 | writeln!(dot, r#" edge [fontname="monospace"];"#)?; 143 | 144 | // Nodes 145 | for (block, _) in mir.basic_blocks().iter_enumerated() { 146 | write_node(block, mir, breakpoints, promoted, &mut dot)?; 147 | } 148 | 149 | // Edges 150 | for (source, _) in mir.basic_blocks().iter_enumerated() { 151 | write_edges(source, mir, &mut dot)?; 152 | } 153 | writeln!(dot, "}}")?; 154 | w.write_str( 155 | ::std::str::from_utf8(&::cgraph::Graph::parse(dot).unwrap().render_dot().unwrap()).unwrap(), 156 | ) 157 | } 158 | 159 | /// Write a graphviz HTML-styled label for the given basic block, with 160 | /// all necessary escaping already performed. (This is suitable for 161 | /// emitting directly, as is done in this module, or for use with the 162 | /// `LabelText::HtmlStr` from libgraphviz.) 163 | fn write_node_label( 164 | block: BasicBlock, 165 | mir: &Body<'_>, 166 | breakpoints: LocalBreakpoints<'_>, 167 | promoted: Option, 168 | w: &mut W, 169 | ) -> fmt::Result { 170 | let data = &mir[block]; 171 | 172 | write!(w, r#""#)?; 173 | 174 | // Basic block number at the top. 175 | write!( 176 | w, 177 | r#""#, 178 | blk = node(promoted, block) 179 | )?; 180 | 181 | // List of statements in the middle. 182 | if !data.statements.is_empty() { 183 | write!(w, r#"")?; 200 | } 201 | 202 | // Terminator head at the bottom, not including the list of successor blocks. Those will be 203 | // displayed as labels on the edges between blocks. 204 | let mut terminator_head = String::new(); 205 | data.terminator() 206 | .kind 207 | .fmt_head(&mut terminator_head) 208 | .unwrap(); 209 | write!( 210 | w, 211 | r#""#, 212 | escape_html(&terminator_head) 213 | )?; 214 | 215 | // Close the table 216 | writeln!(w, "
{blk}
"#)?; 184 | for (statement_index, statement) in data.statements.iter().enumerate() { 185 | if breakpoints.breakpoint_exists(Some(Location { 186 | block, 187 | statement_index, 188 | })) { 189 | write!(w, "+ ")?; 190 | } else { 191 | write!(w, "  ")?; 192 | } 193 | if crate::should_hide_stmt(statement) { 194 | write!(w, "<+>
")?; 195 | } else { 196 | write!(w, "{}
", escape(statement))?; 197 | } 198 | } 199 | write!(w, "
{}
") 217 | } 218 | 219 | /// Write a graphviz DOT node for the given basic block. 220 | fn write_node( 221 | block: BasicBlock, 222 | mir: &Body<'_>, 223 | breakpoints: LocalBreakpoints<'_>, 224 | promoted: Option, 225 | w: &mut W, 226 | ) -> fmt::Result { 227 | // Start a new node with the label to follow, in one of DOT's pseudo-HTML tables. 228 | write!( 229 | w, 230 | r#" "{}" [shape="none", label=<"#, 231 | node(promoted, block) 232 | )?; 233 | write_node_label(block, mir, breakpoints, promoted, w)?; 234 | // Close the node label and the node itself. 235 | writeln!(w, ">];") 236 | } 237 | 238 | /// Write graphviz DOT edges with labels between the given basic block and all of its successors. 239 | fn write_edges(source: BasicBlock, mir: &Body<'_>, w: &mut W) -> fmt::Result { 240 | let terminator = mir[source].terminator(); 241 | let labels = terminator.kind.fmt_successor_labels(); 242 | 243 | for (&target, label) in terminator.successors().zip(labels) { 244 | writeln!( 245 | w, 246 | r#" {} -> {} [label="{}"];"#, 247 | node(None, source), 248 | node(None, target), 249 | label 250 | )?; 251 | } 252 | 253 | Ok(()) 254 | } 255 | 256 | fn node(promoted: Option, block: BasicBlock) -> String { 257 | if let Some(promoted) = promoted { 258 | format!("promoted{}.{}", promoted, block.index()) 259 | } else { 260 | format!("bb{}", block.index()) 261 | } 262 | } 263 | 264 | fn escape(t: &T) -> String { 265 | escape_html(&format!("{:?}", t)).into_owned() 266 | } 267 | 268 | fn escape_html(s: &str) -> ::std::borrow::Cow<'_, str> { 269 | ::rocket::http::RawStr::from_str(s).html_escape() 270 | } 271 | -------------------------------------------------------------------------------- /src/render/locals.rs: -------------------------------------------------------------------------------- 1 | use rustc_middle::mir::{ 2 | self, 3 | interpret::{InterpError, UndefinedBehaviorInfo}, 4 | }; 5 | use rustc_middle::ty::{subst::Subst, ParamEnv, TyKind, TypeAndMut}; 6 | use rustc_mir::interpret::{ 7 | Allocation, Frame, Immediate, InterpResult, MemPlaceMeta, OpTy, Operand, Pointer, Scalar, 8 | ScalarMaybeUninit, 9 | }; 10 | use rustc_target::abi::{Abi, Size}; 11 | 12 | use miri::{AllocExtra, Tag}; 13 | 14 | use horrorshow::prelude::*; 15 | use horrorshow::Template; 16 | 17 | use crate::InterpCx; 18 | 19 | pub fn render_locals<'tcx>( 20 | ecx: &InterpCx<'tcx>, 21 | frame: &Frame<'tcx, 'tcx, Tag, miri::FrameData<'tcx>>, 22 | ) -> String { 23 | let &Frame { 24 | ref body, 25 | ref return_place, 26 | ref instance, 27 | .. 28 | } = frame; 29 | 30 | // name ty alloc val style 31 | let locals: Vec<(String, String, Option, String, &str)> = body 32 | .local_decls 33 | .iter_enumerated() 34 | .map(|(id, local_decl)| { 35 | let name = body 36 | .var_debug_info 37 | .iter() 38 | .find(|var_debug_info| match var_debug_info.value { 39 | mir::VarDebugInfoContents::Place(place) => { 40 | place.projection.is_empty() && place.local == id 41 | } 42 | _ => false, 43 | }) 44 | .map(|var_debug_info| var_debug_info.name.as_str().to_string()) 45 | .unwrap_or_else(String::new); 46 | 47 | // FIXME Don't panic when trying to read from uninit variable. 48 | // Panic message: 49 | // > error: internal compiler error: src/librustc_mir/interpret/eval_context.rs:142: 50 | // > The type checker should prevent reading from a never-written local 51 | let op_ty = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { 52 | if id == mir::RETURN_PLACE { 53 | return_place 54 | .map(|p| ecx.place_to_op(&p).unwrap()) 55 | .ok_or(false) 56 | } else { 57 | ecx.access_local(frame, id, None).map_err(|_| false) 58 | } 59 | })) { 60 | Ok(op_ty) => op_ty, 61 | Err(_) => Err(true), 62 | }; 63 | 64 | let (alloc, val, style) = match op_ty { 65 | Err(false) => (None, "<dead>".to_owned(), "font-size: 0;"), 66 | Err(true) => (None, "<uninit>".to_owned(), "color: darkmagenta;"), 67 | Ok(op_ty) => match print_operand(ecx, op_ty) { 68 | Ok((alloc, text)) => (alloc, text, ""), 69 | Err(()) => (None, "<error>".to_owned(), "color: red;"), 70 | }, 71 | }; 72 | let ty = ecx.tcx.normalize_erasing_regions( 73 | ParamEnv::reveal_all(), 74 | local_decl.ty.subst(ecx.tcx.tcx, instance.substs), 75 | ); 76 | (name, ty.to_string(), alloc, val, style) 77 | }) 78 | .collect(); 79 | 80 | let (arg_count, var_count, tmp_count) = ( 81 | body.args_iter().count(), 82 | body.vars_iter().count(), 83 | body.temps_iter().count(), 84 | ); 85 | 86 | (horrorshow::html! { 87 | table(border="1") { 88 | tr { 89 | td(width="20px"); 90 | th { : "id" } 91 | th { : "name" } 92 | th { : "alloc" } 93 | th { : "memory" } 94 | th { : "type" } 95 | } 96 | @ for (i, &(ref name, ref ty, alloc, ref text, ref style)) in locals.iter().enumerate() { 97 | tr(style=style) { 98 | @if i == 0 { 99 | th(rowspan=1) { span(class="vertical") { : "Return" } } 100 | } else if i == 1 && arg_count != 0 { 101 | th(rowspan=arg_count) { span(class="vertical") { : "Arguments" } } 102 | } else if i == arg_count + 1 && var_count != 0 { 103 | th(rowspan=var_count) { span(class="vertical") { : "Variables" } } 104 | } else if i == var_count + arg_count + 1 && tmp_count != 0 { 105 | th(rowspan=tmp_count) { span(class="vertical") { : "Temporaries" } } 106 | } 107 | td { : format!("_{}", i) } 108 | td { : name } 109 | @if let Some(alloc) = alloc { 110 | td { : alloc.to_string() } 111 | } else { 112 | td; 113 | } 114 | td { : Raw(text) } 115 | td { : ty } 116 | } 117 | } 118 | } 119 | }).into_string() 120 | .unwrap() 121 | } 122 | 123 | fn print_scalar_maybe_undef(val: ScalarMaybeUninit) -> String { 124 | match val { 125 | ScalarMaybeUninit::Uninit => "<undef >".to_string(), 126 | ScalarMaybeUninit::Scalar(val) => print_scalar(val), 127 | } 128 | } 129 | 130 | fn print_scalar(val: Scalar) -> String { 131 | match val { 132 | Scalar::Ptr(ptr) => format!( 133 | "Pointer({alloc})[{offset}]", 134 | alloc = ptr.alloc_id.0, 135 | offset = ptr.offset.bytes() 136 | ), 137 | Scalar::Int(int) => { 138 | if int.size().bytes() == 0 { 139 | // Can't use `Debug` impl directly because it would be unescaped 140 | "<zst>".to_string() 141 | } else { 142 | format!("0x{:X}", int) 143 | } 144 | } 145 | } 146 | } 147 | 148 | fn pp_operand<'tcx>( 149 | ecx: &InterpCx<'tcx>, 150 | op_ty: OpTy<'tcx, miri::Tag>, 151 | ) -> InterpResult<'tcx, String> { 152 | let err = || InterpError::UndefinedBehavior(UndefinedBehaviorInfo::Ub(String::new())); 153 | match op_ty.layout.ty.kind() { 154 | TyKind::RawPtr(TypeAndMut { ty, .. }) | TyKind::Ref(_, ty, _) 155 | if matches!(ty.kind(), TyKind::Str) => 156 | { 157 | if let Operand::Immediate(val) = *op_ty { 158 | if let Immediate::ScalarPair( 159 | ScalarMaybeUninit::Scalar(Scalar::Ptr(ptr)), 160 | ScalarMaybeUninit::Scalar(Scalar::Int(int)), 161 | ) = val 162 | { 163 | if let Ok(allocation) = ecx.memory.get_raw(ptr.alloc_id) { 164 | let offset = ptr.offset.bytes(); 165 | if (offset as u128) < allocation.len() as u128 { 166 | let start = offset as usize; 167 | let len = int.assert_bits(int.size()); 168 | let end = start.checked_add(len as usize).ok_or(err())?; 169 | let alloc_bytes = &allocation 170 | .inspect_with_uninit_and_ptr_outside_interpreter(start..end); 171 | let s = String::from_utf8_lossy(alloc_bytes); 172 | return Ok(format!("\"{}\"", s)); 173 | } 174 | } 175 | } 176 | } 177 | } 178 | TyKind::Adt(adt_def, _substs) => { 179 | if let Operand::Immediate(Immediate::Scalar(ScalarMaybeUninit::Uninit)) = *op_ty { 180 | Err(err())?; 181 | } 182 | 183 | let variant = ecx.read_discriminant(&op_ty)?.1; 184 | let adt_fields = &adt_def.variants[variant].fields; 185 | 186 | let should_collapse = adt_fields.len() > 1; 187 | 188 | //println!("{:?} {:?} {:?}", val, ty, adt_def.variants); 189 | let mut pretty = ecx 190 | .tcx 191 | .def_path_str(adt_def.did) 192 | .replace("<", "<") 193 | .replace(">", ">") 194 | .to_string(); 195 | 196 | if adt_def.is_enum() { 197 | pretty.push_str("::"); 198 | pretty.push_str(&*adt_def.variants[variant].ident.as_str()); 199 | } 200 | pretty.push_str(" { "); 201 | 202 | if should_collapse { 203 | pretty.push_str("
"); 204 | } 205 | 206 | for (i, adt_field) in adt_fields.iter().enumerate() { 207 | let field_pretty: InterpResult<'_, String> = try { 208 | let field_op_ty = ecx.operand_field(&op_ty, i as usize)?; 209 | pp_operand(ecx, field_op_ty)? 210 | }; 211 | 212 | pretty.push_str(&format!( 213 | "{}: {}, ", 214 | adt_field.ident.as_str(), 215 | match field_pretty { 216 | Ok(field_pretty) => field_pretty, 217 | Err(_err) => "<err>".to_string(), 218 | } 219 | )); 220 | if should_collapse { 221 | pretty.push_str("
"); 222 | } 223 | } 224 | 225 | if should_collapse { 226 | pretty.push_str("
"); 227 | } 228 | 229 | pretty.push_str("}"); 230 | println!("pretty adt: {}", pretty); 231 | return Ok(pretty); 232 | } 233 | _ => {} 234 | } 235 | 236 | if op_ty.layout.size.bytes() == 0 { 237 | Err(err())?; 238 | } 239 | if let Abi::Scalar(_) = op_ty.layout.abi { 240 | } else { 241 | Err(err())?; 242 | } 243 | let scalar = ecx.read_scalar(&op_ty)?; 244 | if let ScalarMaybeUninit::Scalar(Scalar::Ptr(_)) = &scalar { 245 | return Ok(print_scalar_maybe_undef(scalar)); // If the value is a ptr, print it 246 | } 247 | match op_ty.layout.ty.kind() { 248 | TyKind::Bool => { 249 | if scalar.to_bool()? { 250 | Ok("true".to_string()) 251 | } else { 252 | Ok("false".to_string()) 253 | } 254 | } 255 | TyKind::Char => { 256 | let chr = scalar.to_char()?; 257 | if chr.is_ascii() { 258 | Ok(format!("'{}'", chr)) 259 | } else { 260 | Err(err().into()) 261 | } 262 | } 263 | TyKind::Uint(_) => Ok(format!("{0}", scalar.to_u64()?)), 264 | TyKind::Int(_) => Ok(format!("{0}", scalar.to_i64()?)), 265 | TyKind::Float(float_ty) => match float_ty { 266 | rustc_type_ir::FloatTy::F32 => Ok(format!("{}", scalar.to_f32()?)), 267 | rustc_type_ir::FloatTy::F64 => Ok(format!("{}", scalar.to_f64()?)), 268 | }, 269 | _ => Err(err().into()), 270 | } 271 | } 272 | 273 | pub fn print_operand<'a, 'tcx: 'a>( 274 | ecx: &InterpCx<'tcx>, 275 | op_ty: OpTy<'tcx, miri::Tag>, 276 | ) -> Result<(Option, String), ()> { 277 | let pretty = pp_operand(ecx, op_ty); 278 | 279 | let (alloc, txt) = match *op_ty { 280 | Operand::Indirect(place) => { 281 | let size: u64 = op_ty.layout.size.bytes(); 282 | if place.meta == MemPlaceMeta::None { 283 | let ptr = place.to_ref().to_scalar().unwrap(); 284 | if ptr.is_ptr() { 285 | let (alloc, txt, _len) = print_ptr(ecx, ptr.assert_ptr(), Some(size))?; 286 | (alloc, txt) 287 | } else { 288 | (None, format!("{:?}", ptr)) 289 | } 290 | } else { 291 | (None, format!("{:?}", place)) // FIXME better printing for unsized locals 292 | } 293 | } 294 | Operand::Immediate(Immediate::Scalar(scalar)) => (None, print_scalar_maybe_undef(scalar)), 295 | Operand::Immediate(Immediate::ScalarPair(val, extra)) => ( 296 | None, 297 | format!( 298 | "{}, {}", 299 | print_scalar_maybe_undef(val), 300 | print_scalar_maybe_undef(extra) 301 | ), 302 | ), 303 | }; 304 | let txt = if let Ok(pretty) = pretty { 305 | format!("{} ({})", pretty, txt) 306 | } else { 307 | txt 308 | }; 309 | Ok((alloc, txt)) 310 | } 311 | 312 | pub fn print_ptr( 313 | ecx: &InterpCx<'_>, 314 | ptr: Pointer, 315 | size: Option, 316 | ) -> Result<(Option, String, u64), ()> { 317 | if let Ok(alloc) = ecx.memory.get_raw(ptr.alloc_id) { 318 | let s = print_alloc(ecx.tcx.data_layout.pointer_size.bytes(), ptr, alloc, size); 319 | debug_assert!(ecx.memory.get_fn(ptr.into()).is_err()); 320 | Ok((Some(ptr.alloc_id.0), s, alloc.len() as u64)) 321 | } else if let Ok(_) = ecx.memory.get_fn(ptr.into()) { 322 | // FIXME: print function name 323 | Ok((None, "function pointer".to_string(), 16)) 324 | } else { 325 | Err(()) 326 | } 327 | } 328 | 329 | pub fn print_alloc( 330 | ptr_size: u64, 331 | ptr: Pointer, 332 | alloc: &Allocation, 333 | size: Option, 334 | ) -> String { 335 | use std::fmt::Write; 336 | let end = size 337 | .map(|s| s + ptr.offset.bytes()) 338 | .unwrap_or(alloc.len() as u64); 339 | let mut s = String::new(); 340 | let mut i = ptr.offset.bytes(); 341 | while i < end { 342 | if let Some((_tag, reloc)) = alloc.relocations().get(&Size::from_bytes(i)) { 343 | i += ptr_size; 344 | write!(&mut s, 345 | "┠{nil:─", 346 | alloc = reloc, 347 | offset = ptr.offset.bytes(), 348 | nil = "", 349 | wdt = (ptr_size * 2 - 2) as usize, 350 | ).unwrap(); 351 | } else { 352 | if alloc 353 | .init_mask() 354 | .is_range_initialized(Size::from_bytes(i), Size::from_bytes(i + 1)) 355 | .is_ok() 356 | { 357 | let byte = alloc 358 | .inspect_with_uninit_and_ptr_outside_interpreter(i as usize..i as usize + 1)[0]; 359 | write!(&mut s, "{:02x}", byte).unwrap(); 360 | } else { 361 | let ub_chars = [ 362 | '∅', '∆', '∇', '∓', '∞', '⊙', '⊠', '⊘', '⊗', '⊛', '⊝', '⊡', '⊠', 363 | ]; 364 | let c1 = (ptr.alloc_id.0 * 769 + i as u64 * 5689) as usize % ub_chars.len(); 365 | let c2 = (ptr.alloc_id.0 * 997 + i as u64 * 7193) as usize % ub_chars.len(); 366 | write!(&mut s, "{}{}", ub_chars[c1], ub_chars[c2]).unwrap(); 367 | } 368 | i += 1; 369 | } 370 | } 371 | s 372 | } 373 | -------------------------------------------------------------------------------- /src/render/mod.rs: -------------------------------------------------------------------------------- 1 | mod graphviz; 2 | pub mod locals; 3 | mod source; 4 | 5 | pub use source::initialise_statics; 6 | 7 | use rustc_hir::definitions::DefPathData; 8 | use rustc_mir::interpret::{AllocId, Machine, Pointer}; 9 | use rustc_target::abi::Size; 10 | 11 | use horrorshow::{Raw, Template}; 12 | use rocket::response::content::Html; 13 | 14 | use crate::step::Breakpoint; 15 | use crate::PrirodaContext; 16 | 17 | pub fn template(pcx: &PrirodaContext<'_, '_>, title: String, t: impl Template) -> Html { 18 | let mut buf = String::new(); 19 | (horrorshow::html! { 20 | html { 21 | head { 22 | title { : title } 23 | meta(charset = "UTF-8") {} 24 | script(src="/resources/svg-pan-zoom.js") {} 25 | script(src="/resources/zoom_mir.js") {} 26 | : Raw(refresh_script(pcx)) 27 | } 28 | body(onload="enable_mir_mousewheel()") { 29 | link(rel="stylesheet", href="/resources/positioning.css"); 30 | link(rel="stylesheet", href=format!("/resources/style-{}.css", pcx.config.theme)); 31 | : t 32 | } 33 | } 34 | }) 35 | .write_to_string(&mut buf) 36 | .unwrap(); 37 | Html(buf) 38 | } 39 | 40 | pub fn refresh_script(pcx: &PrirodaContext<'_, '_>) -> String { 41 | if pcx.config.auto_refresh { 42 | r#""# 57 | .replace("#step_count#", &format!("{}", pcx.step_count)) 58 | } else { 59 | String::new() 60 | } 61 | } 62 | 63 | pub fn render_main_window( 64 | pcx: &PrirodaContext<'_, '_>, 65 | display_frame: Option, 66 | message: String, 67 | ) -> Html { 68 | let is_active_stack_frame = match display_frame { 69 | Some(n) => n == Machine::stack(&pcx.ecx).len() - 1, 70 | None => true, 71 | }; 72 | let frame = display_frame 73 | .and_then(|frame| Machine::stack(&pcx.ecx).get(frame)) 74 | .or_else(|| Machine::stack(&pcx.ecx).last()); 75 | let stack: Vec<(String, String, String)> = Machine::stack(&pcx.ecx) 76 | .iter() 77 | .map(|frame| { 78 | let instance = &frame.instance; 79 | let span = frame.current_source_info().unwrap().span; 80 | let name = if pcx 81 | .ecx 82 | .tcx 83 | .def_key(instance.def_id()) 84 | .disambiguated_data 85 | .data 86 | == DefPathData::ClosureExpr 87 | { 88 | "inside call to closure".to_string() 89 | } else { 90 | instance.to_string() 91 | }; 92 | let span = self::source::pretty_src_path(span); 93 | (name, span, format!("{:?}", instance.def_id())) 94 | }) 95 | .collect(); 96 | let rendered_breakpoints: Vec = pcx 97 | .config 98 | .bptree 99 | .iter() 100 | .map(|&Breakpoint(def_id, bb, stmt)| format!("{:?}@{}:{}", def_id, bb.index(), stmt)) 101 | .collect(); 102 | let rendered_locals = frame 103 | .map(|frame| locals::render_locals(&pcx.ecx, frame)) 104 | .unwrap_or_else(String::new); 105 | 106 | let rendered_source = source::render_source(pcx.ecx.tcx.tcx, frame); 107 | 108 | let mir_graph = frame.map(|frame| { 109 | graphviz::render_html(frame, pcx.config.bptree.for_def_id(frame.instance.def_id())) 110 | }); 111 | 112 | let filename = pcx 113 | .ecx 114 | .tcx 115 | .sess 116 | .local_crate_source_file 117 | .as_ref() 118 | .map(|f| f.display().to_string()) 119 | .unwrap_or_else(|| "no file name".to_string()); 120 | template( 121 | pcx, 122 | filename, 123 | horrorshow::html! { 124 | div(id="left") { 125 | div(id="commands") { 126 | @ if is_active_stack_frame { 127 | a(href="/step/single") { div(title="Execute next MIR statement/terminator") { : "Step" } } 128 | a(href="/step/next") { div(title="Run until after the next MIR statement/terminator") { : "Next" } } 129 | a(href="/step/return") { div(title="Run until the function returns") { : "Return" } } 130 | a(href="/step/single_back") { div(title="Execute previous MIR statement/terminator (restarts and steps till one stmt before the current stmt)") { : "Step back (slow)" } } 131 | a(href="/step/continue") { div(title="Run until termination or breakpoint") { : "Continue" } } 132 | a(href="/step/restart") { div(title="Abort execution and restart") { : "Restart" } } 133 | a(href="/breakpoints/add_here") { div(title="Add breakpoint at current location") { : "Add breakpoint here"} } 134 | a(href="/breakpoints/remove_all") { div(title="Remove all breakpoints") { : "Remove all breakpoints"} } 135 | } else { 136 | a(href="/") { div(title="Go to active stack frame") { : "Go back to active stack frame" } } 137 | } 138 | } 139 | div(id="messages") { 140 | p { : message } 141 | } 142 | div(id="mir") { 143 | : Raw(mir_graph.unwrap_or_else(|| "no current function".to_string())) 144 | } 145 | } 146 | div(id="right") { 147 | div { 148 | : format!("Step count: {}", pcx.step_count); 149 | } 150 | div(id="stack") { 151 | table(border="1") { 152 | @ for (i, &(ref s, ref span, ref def_id)) in stack.iter().enumerate().rev() { 153 | tr { 154 | @ if i == display_frame.unwrap_or(stack.len() - 1) { td { : Raw("→") } } else { td; } 155 | td { : s } 156 | td { : span } 157 | td { : def_id } 158 | @ if i == display_frame.unwrap_or(stack.len() - 1) { td; } else { td { a(href=format!("/frame/{}", i)) { : "View" } } } 159 | } 160 | } 161 | } 162 | } 163 | div(id="breakpoints") { 164 | : "Breakpoints: "; br; 165 | table(border="1") { 166 | @ for bp in rendered_breakpoints { 167 | tr { 168 | td { : &bp } 169 | td { a(href=format!("/breakpoints/remove/{}", bp)) { : "remove" } } 170 | } 171 | } 172 | } 173 | } 174 | div(id="locals") { 175 | : Raw(rendered_locals) 176 | } 177 | div(id="source") { 178 | : rendered_source 179 | } 180 | } 181 | }, 182 | ) 183 | } 184 | 185 | pub fn render_reverse_ptr(pcx: &PrirodaContext<'_, '_>, alloc_id: u64) -> Html { 186 | let allocs: Vec<_> = pcx.ecx.memory.alloc_map().iter(|values| { 187 | values 188 | .filter_map(|(&id, (_kind, alloc))| { 189 | alloc 190 | .relocations() 191 | .values() 192 | .find(|&&(_tag, reloc)| reloc == id) 193 | .map(|_| id) 194 | }) 195 | .collect() 196 | }); 197 | template( 198 | pcx, 199 | format!("Allocations with pointers to Allocation {}", alloc_id), 200 | horrorshow::html! { 201 | @for id in allocs { 202 | a(href=format!("/ptr/{}", id)) { : format!("Allocation {}", id) } 203 | br; 204 | } 205 | }, 206 | ) 207 | } 208 | 209 | pub fn render_ptr_memory( 210 | pcx: &PrirodaContext<'_, '_>, 211 | alloc_id: AllocId, 212 | offset: u64, 213 | ) -> Html { 214 | let (mem, offset, rest) = if let Ok((_, mem, bytes)) = locals::print_ptr( 215 | &pcx.ecx, 216 | Pointer::new(alloc_id, Size::from_bytes(offset)) 217 | .with_tag(miri::Tag::Untagged) 218 | .into(), 219 | None, 220 | ) { 221 | if bytes * 2 > offset { 222 | (mem, offset, (bytes * 2 - offset - 1) as usize) 223 | } else if bytes * 2 == 0 && offset == 0 { 224 | (mem, 0, 0) 225 | } else { 226 | ("out of bounds offset".to_string(), 0, 0) 227 | } 228 | } else { 229 | ("unknown memory".to_string(), 0, 0) 230 | }; 231 | template( 232 | pcx, 233 | format!("Allocation {}", alloc_id), 234 | horrorshow::html! { 235 | span(style="font-family: monospace") { 236 | : format!("{nil:. ::rocket::request::FromRequest<'a, 'r> for FlashString { 249 | type Error = !; 250 | fn from_request(request: &'a rocket::Request<'r>) -> rocket::request::Outcome { 251 | rocket::Outcome::Success(FlashString( 252 | Option::>::from_request(request)? 253 | .map(|flash| flash.msg().to_string()) 254 | .unwrap_or_else(String::new), 255 | )) 256 | } 257 | } 258 | 259 | pub mod routes { 260 | use super::*; 261 | use crate::*; 262 | 263 | pub fn routes() -> Vec<::rocket::Route> { 264 | routes![index, frame, frame_invalid, ptr, reverse_ptr] 265 | } 266 | 267 | #[get("/")] 268 | pub fn index( 269 | sender: rocket::State<'_, crate::PrirodaSender>, 270 | flash: FlashString, 271 | ) -> crate::RResult> { 272 | sender.do_work(move |pcx| render::render_main_window(pcx, None, flash.0)) 273 | } 274 | 275 | #[get("/frame/")] 276 | pub fn frame( 277 | sender: rocket::State<'_, crate::PrirodaSender>, 278 | flash: FlashString, 279 | frame: usize, 280 | ) -> crate::RResult> { 281 | sender.do_work(move |pcx| render::render_main_window(pcx, Some(frame), flash.0)) 282 | } 283 | 284 | #[get("/frame/", rank = 42)] // Error handler 285 | fn frame_invalid(frame: String) -> BadRequest { 286 | BadRequest(Some(format!( 287 | "not a number: {:?}", 288 | frame.parse::().unwrap_err() 289 | ))) 290 | } 291 | #[get("/ptr//")] 292 | pub fn ptr( 293 | sender: rocket::State<'_, crate::PrirodaSender>, 294 | alloc_id: u64, 295 | offset: u64, 296 | ) -> crate::RResult> { 297 | sender.do_work(move |pcx| render::render_ptr_memory(pcx, AllocId(alloc_id), offset)) 298 | } 299 | 300 | #[get("/reverse_ptr/")] 301 | fn reverse_ptr( 302 | sender: rocket::State<'_, crate::PrirodaSender>, 303 | ptr: u64, 304 | ) -> crate::RResult> { 305 | sender.do_work(move |pcx| render::render_reverse_ptr(pcx, ptr)) 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/render/source.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::{cell::RefCell, ops::Range}; 3 | 4 | use miri::{FrameData, Tag}; 5 | use rustc_middle::ty::TyCtxt; 6 | use rustc_mir::interpret::Frame; 7 | use rustc_span::Span; 8 | 9 | use horrorshow::prelude::*; 10 | use syntect::easy::HighlightLines; 11 | use syntect::highlighting::{Style, ThemeSet}; 12 | use syntect::html::{styled_line_to_highlighted_html, IncludeBackground}; 13 | use syntect::parsing::SyntaxSet; 14 | use syntect::util::{split_at, LinesWithEndings}; 15 | 16 | lazy_static::lazy_static! { 17 | static ref SYNTAX_SET: SyntaxSet = SyntaxSet::load_defaults_nonewlines(); 18 | static ref THEME_SET: ThemeSet = ThemeSet::load_defaults(); 19 | 20 | static ref RUST_SOURCE: regex::Regex = regex::Regex::new("/rustc/\\w+/").unwrap(); 21 | static ref STD_SRC: Option = { 22 | if let Ok(output) = std::process::Command::new("rustc").arg("--print").arg("sysroot").output() { 23 | if let Ok(sysroot) = String::from_utf8(output.stdout) { 24 | Some(sysroot.trim().to_string() + "/lib/rustlib/src/rust/") 25 | } else { 26 | None 27 | } 28 | } else { 29 | None 30 | } 31 | }; 32 | } 33 | 34 | pub fn initialise_statics() { 35 | let _ = (&*SYNTAX_SET, &*THEME_SET); 36 | } 37 | 38 | pub fn pretty_src_path(span: Span) -> String { 39 | let span = format!("{:?}", span); 40 | let span = RUST_SOURCE.replace(span.as_ref(), "/").to_string(); 41 | if let Some(std_src) = &*STD_SRC { 42 | span.replace(std_src, "/") 43 | } else { 44 | span 45 | } 46 | } 47 | 48 | thread_local! { 49 | // This is a thread local, because a `Span` is only valid within one thread 50 | static CACHED_HIGHLIGHTED_FILES: RefCell> = { 51 | RefCell::new(HashMap::new()) 52 | }; 53 | } 54 | 55 | pub struct HighlightCacheEntry { 56 | pub string: String, 57 | pub highlighted: Vec<(Style, Range)>, 58 | } 59 | 60 | pub fn render_source( 61 | tcx: TyCtxt<'_>, 62 | frame: Option<&Frame<'_, '_, Tag, FrameData<'_>>>, 63 | ) -> Box { 64 | let before_time = ::std::time::Instant::now(); 65 | 66 | if frame.is_none() { 67 | return Box::new(FnRenderer::new(|_| {})); 68 | } 69 | let frame = frame.unwrap(); 70 | let mut instr_spans = if let Some(location) = frame.current_loc().ok() { 71 | let stmt = location.statement_index; 72 | let block = location.block; 73 | if stmt == frame.body[block].statements.len() { 74 | vec![frame.body[block].terminator().source_info.span] 75 | } else { 76 | vec![frame.body[block].statements[stmt].source_info.span] 77 | } 78 | } else { 79 | vec![frame.body.span] 80 | }; 81 | // Get the original macro caller 82 | while let Some(span) = instr_spans 83 | .last() 84 | .unwrap() 85 | .macro_backtrace() 86 | .next() 87 | .map(|b| b.call_site) 88 | { 89 | instr_spans.push(span); 90 | } 91 | 92 | let highlighted_sources = instr_spans 93 | .into_iter() 94 | .rev() 95 | .map(|sp| { 96 | let (src, lo, hi) = match get_file_source_for_span(tcx, sp) { 97 | Ok(res) => res, 98 | Err(err) => return (format!("{:?}", sp), err), 99 | }; 100 | 101 | CACHED_HIGHLIGHTED_FILES.with(|highlight_cache| { 102 | use std::collections::hash_map::DefaultHasher; 103 | use std::hash::{Hash, Hasher}; 104 | 105 | let mut hasher = DefaultHasher::new(); 106 | src.hash(&mut hasher); 107 | let hash = hasher.finish(); 108 | 109 | let mut cache = highlight_cache.borrow_mut(); 110 | let entry = cache.entry(hash).or_insert_with(|| { 111 | let before_time = ::std::time::Instant::now(); 112 | let highlighted = syntax_highlight(&src); 113 | let after_time = ::std::time::Instant::now(); 114 | println!("h: {:?}", after_time - before_time); 115 | HighlightCacheEntry { 116 | string: src, 117 | highlighted, 118 | } 119 | }); 120 | ( 121 | pretty_src_path(sp), 122 | mark_span(&entry.string, &entry.highlighted, lo, hi), 123 | ) 124 | }) 125 | }) 126 | .collect::>(); 127 | 128 | let after_time = ::std::time::Instant::now(); 129 | println!("s: {:?}", after_time - before_time); 130 | 131 | let style = if let Some(bg_color) = THEME_SET.themes["Solarized (dark)"].settings.background { 132 | format!( 133 | "background-color: #{:02x}{:02x}{:02x}; display: block;", 134 | bg_color.r, bg_color.g, bg_color.b 135 | ) 136 | } else { 137 | String::new() 138 | }; 139 | 140 | horrorshow::box_html! { 141 | pre { 142 | code(id="the_code", style=style) { 143 | @ for (sp, source) in highlighted_sources { 144 | span(style = "color: aqua;") { 145 | :sp; br; 146 | } 147 | : Raw(source); 148 | br; br; 149 | } 150 | } 151 | } 152 | } 153 | } 154 | 155 | fn get_file_source_for_span(tcx: TyCtxt<'_>, sp: Span) -> Result<(String, usize, usize), String> { 156 | let source_map = tcx.sess.source_map(); 157 | let _ = source_map.span_to_snippet(sp); // Ensure file src is loaded 158 | 159 | let src = if let Ok(file_lines) = source_map.span_to_lines(sp) { 160 | if let Some(ref src) = file_lines.file.src { 161 | src.to_string() 162 | } else if let Some(src) = file_lines.file.external_src.borrow().get_source() { 163 | src.to_string() 164 | } else { 165 | return Err("".to_string()); 166 | } 167 | } else { 168 | return Err("".to_string()); 169 | }; 170 | let lo = source_map.bytepos_to_file_charpos(sp.lo()).0; 171 | let hi = source_map.bytepos_to_file_charpos(sp.hi()).0; 172 | Ok((src, lo, hi)) 173 | } 174 | 175 | fn syntax_highlight<'a, 's>(src: &'s str) -> Vec<(Style, Range)> { 176 | let theme = &THEME_SET.themes["Solarized (dark)"]; 177 | let mut h = HighlightLines::new( 178 | &SYNTAX_SET 179 | .find_syntax_by_extension("rs") 180 | .unwrap() 181 | .to_owned(), 182 | theme, 183 | ); 184 | let mut index = 0; 185 | let mut highlighted = Vec::new(); 186 | for line in LinesWithEndings::from(src) { 187 | highlighted.extend( 188 | h.highlight(line, &SYNTAX_SET) 189 | .into_iter() 190 | .map(|(style, str)| { 191 | let idx = index; 192 | index += str.len(); 193 | (style, idx..index) 194 | }), 195 | ); 196 | } 197 | highlighted 198 | } 199 | 200 | fn mark_span(file_contents: &str, src: &[(Style, Range)], lo: usize, hi: usize) -> String { 201 | let src = src 202 | .iter() 203 | .map(|(style, range)| (*style, &file_contents[range.clone()])) 204 | .collect::>(); 205 | let (before, with) = split_at(&src, lo); 206 | let (it, after) = split_at(&with, hi - lo); 207 | 208 | let before = styled_line_to_highlighted_html(&before, IncludeBackground::No); 209 | let it = styled_line_to_highlighted_html(&it, IncludeBackground::No); 210 | let after = styled_line_to_highlighted_html(&after, IncludeBackground::No); 211 | 212 | if lo == hi { 213 | assert_eq!(it.len(), 0); 214 | format!("{}{}", before, after) 215 | } else { 216 | assert_ne!(it.len(), 0); 217 | format!("{}{}{}", before, it, after) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/step.rs: -------------------------------------------------------------------------------- 1 | use rustc_hir::def_id::{CrateNum, DefId, DefIndex}; 2 | use rustc_index::vec::Idx; 3 | use rustc_middle::mir; 4 | use rustc_mir::interpret::Machine; 5 | use std::collections::{HashMap, HashSet}; 6 | use std::iter::Iterator; 7 | 8 | use miri::*; 9 | use serde::de::{Deserialize, Deserializer, Error as SerdeError}; 10 | 11 | use crate::{InterpCx, PrirodaContext}; 12 | 13 | pub enum ShouldContinue { 14 | Continue, 15 | Stop, 16 | } 17 | 18 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] 19 | pub struct Breakpoint(pub DefId, pub mir::BasicBlock, pub usize); 20 | 21 | #[derive(Default)] 22 | pub struct BreakpointTree(HashMap>); 23 | 24 | impl<'de> Deserialize<'de> for BreakpointTree { 25 | fn deserialize>(deser: D) -> Result { 26 | let mut map = HashMap::new(); 27 | for (k, v) in HashMap::>::deserialize(deser)? { 28 | let def_id = parse_def_id(&k).map_err(SerdeError::custom)?; 29 | map.insert( 30 | def_id, 31 | v.into_iter() 32 | .map(|(bb, instr)| Breakpoint(def_id, mir::BasicBlock::new(bb), instr)) 33 | .collect::>(), 34 | ); 35 | } 36 | Ok(BreakpointTree(map)) 37 | } 38 | } 39 | 40 | impl BreakpointTree { 41 | pub fn add_breakpoint(&mut self, bp: Breakpoint) { 42 | self.0.entry(bp.0).or_insert_with(HashSet::new).insert(bp); 43 | } 44 | 45 | pub fn remove_breakpoint(&mut self, bp: Breakpoint) -> bool { 46 | self.0 47 | .get_mut(&bp.0) 48 | .map(|local| local.remove(&bp)) 49 | .unwrap_or(false) 50 | } 51 | 52 | pub fn remove_all(&mut self) { 53 | self.0.clear(); 54 | } 55 | 56 | pub fn for_def_id(&self, def_id: DefId) -> LocalBreakpoints<'_> { 57 | if let Some(bps) = self.0.get(&def_id) { 58 | LocalBreakpoints::SomeBps(bps) 59 | } else { 60 | LocalBreakpoints::NoBp 61 | } 62 | } 63 | 64 | pub fn is_at_breakpoint(&self, ecx: &InterpCx<'_>) -> bool { 65 | let frame = ecx.frame(); 66 | self.for_def_id(frame.instance.def_id()) 67 | .breakpoint_exists(frame.current_loc().ok()) 68 | } 69 | 70 | pub fn iter(&self) -> impl Iterator { 71 | self.0.values().flat_map(|local| local.iter()) 72 | } 73 | } 74 | 75 | #[derive(Copy, Clone)] 76 | pub enum LocalBreakpoints<'a> { 77 | NoBp, 78 | SomeBps(&'a HashSet), 79 | } 80 | 81 | impl<'a> LocalBreakpoints<'a> { 82 | pub fn breakpoint_exists(self, location: Option) -> bool { 83 | if let Some(location) = location { 84 | match self { 85 | LocalBreakpoints::NoBp => false, 86 | LocalBreakpoints::SomeBps(bps) => bps 87 | .iter() 88 | .any(|bp| bp.1 == location.block && bp.2 == location.statement_index), 89 | } 90 | } else { 91 | // Unwinding, but no cleanup for this frame 92 | // FIXME make this configurable 93 | false 94 | } 95 | } 96 | } 97 | 98 | pub fn step(pcx: &mut PrirodaContext<'_, '_>, continue_while: F) -> String 99 | where 100 | F: Fn(&InterpCx<'_>) -> ShouldContinue, 101 | { 102 | let mut message = None; 103 | let ret: InterpResult<'_, _> = try { 104 | loop { 105 | let is_main_thread_active = pcx.ecx.get_active_thread() == 0.into(); 106 | if is_main_thread_active && Machine::stack(&pcx.ecx).len() <= 1 && is_ret(&pcx.ecx) { 107 | // When the main thread exists, the program terminates. However, 108 | // we want to prevent stepping out of the program. 109 | break; 110 | } 111 | match pcx.ecx.schedule()? { 112 | SchedulingAction::ExecuteStep => {} 113 | SchedulingAction::ExecuteTimeoutCallback => { 114 | pcx.ecx.run_timeout_callback()?; 115 | continue; 116 | } 117 | SchedulingAction::ExecuteDtors => { 118 | // This will either enable the thread again (so we go back 119 | // to `ExecuteStep`), or determine that this thread is done 120 | // for good. 121 | pcx.ecx.schedule_next_tls_dtor_for_active_thread()?; 122 | continue; 123 | } 124 | SchedulingAction::Stop => { 125 | message = Some("interpretation finished".to_string()); 126 | break; 127 | } 128 | } 129 | let info = pcx.ecx.preprocess_diagnostics(); 130 | if !pcx.ecx.step()? { 131 | message = Some("a terminated thread was scheduled for execution".to_string()); 132 | break; 133 | } 134 | pcx.ecx.process_diagnostics(info); 135 | 136 | *pcx.step_count += 1; 137 | crate::watch::step_callback(pcx); 138 | 139 | if let Some(frame) = Machine::stack(&pcx.ecx).last() { 140 | if let Some(location) = frame.current_loc().ok() { 141 | let blck = &frame.body.basic_blocks()[location.block]; 142 | if location.statement_index != blck.statements.len() 143 | && crate::should_hide_stmt(&blck.statements[location.statement_index]) 144 | && !pcx.config.bptree.is_at_breakpoint(&pcx.ecx) 145 | { 146 | continue; 147 | } 148 | } else { 149 | // Unwinding, but no cleanup for this frame 150 | // FIXME make step behaviour configurable 151 | } 152 | } 153 | if let ShouldContinue::Stop = continue_while(&pcx.ecx) { 154 | break; 155 | } 156 | if pcx.config.bptree.is_at_breakpoint(&pcx.ecx) { 157 | break; 158 | } 159 | } 160 | }; 161 | match ret { 162 | Err(e) => { 163 | message = Some(format!("{:?}", e)); 164 | } 165 | Ok(_) => {} 166 | } 167 | message.unwrap_or_else(String::new) 168 | } 169 | 170 | pub fn is_ret(ecx: &InterpCx<'_>) -> bool { 171 | if let Some(frame) = Machine::stack(&ecx).last() { 172 | if let Some(location) = frame.current_loc().ok() { 173 | let basic_block = &frame.body.basic_blocks()[location.block]; 174 | 175 | match basic_block.terminator().kind { 176 | rustc_middle::mir::TerminatorKind::Return 177 | | rustc_middle::mir::TerminatorKind::Resume => { 178 | location.statement_index >= basic_block.statements.len() 179 | } 180 | _ => false, 181 | } 182 | } else { 183 | true // Unwinding, but no cleanup necessary for current frame 184 | } 185 | } else { 186 | true 187 | } 188 | } 189 | 190 | fn parse_breakpoint_from_url(s: &str) -> Result { 191 | let regex = ::regex::Regex::new(r#"([^@]+)@(\d+):(\d+)"#).unwrap(); 192 | // DefId(1:14824 ~ mycrate::main)@1:3 193 | // ^ ^ ^ ^ 194 | // | | | statement 195 | // | | BasicBlock 196 | // | DefIndex::as_array_index() 197 | // CrateNum 198 | 199 | let s = s.replace("%20", " "); 200 | let caps = regex 201 | .captures(&s) 202 | .ok_or_else(|| format!("Invalid breakpoint {}", s))?; 203 | 204 | // Parse DefId 205 | let def_id = parse_def_id(caps.get(1).unwrap().as_str())?; 206 | 207 | // Parse block and stmt 208 | let bb = mir::BasicBlock::new( 209 | caps.get(2) 210 | .unwrap() 211 | .as_str() 212 | .parse::() 213 | .map_err(|_| "block id is not a positive integer")?, 214 | ); 215 | let stmt = caps 216 | .get(3) 217 | .unwrap() 218 | .as_str() 219 | .parse::() 220 | .map_err(|_| "stmt id is not a positive integer")?; 221 | 222 | Ok(Breakpoint(def_id, bb, stmt)) 223 | } 224 | 225 | fn parse_def_id(s: &str) -> Result { 226 | let regex = ::regex::Regex::new(r#"DefId\((\d+):(\d+) ~ [^\)]+\)"#).unwrap(); 227 | let caps = regex 228 | .captures(&s) 229 | .ok_or_else(|| format!("Invalid def_id {}", s))?; 230 | 231 | let crate_num = CrateNum::new(caps.get(1).unwrap().as_str().parse::().unwrap()); 232 | let index = caps 233 | .get(2) 234 | .unwrap() 235 | .as_str() 236 | .parse::() 237 | .map_err(|_| "index is not a positive integer".to_string())?; 238 | let def_index = DefIndex::from_usize(index); 239 | Ok(DefId { 240 | krate: crate_num, 241 | index: def_index, 242 | }) 243 | } 244 | 245 | pub mod step_routes { 246 | use rocket::response; 247 | 248 | use super::*; 249 | 250 | pub fn routes() -> Vec<::rocket::Route> { 251 | routes![restart, single, single_back, next, return_, continue_] 252 | } 253 | 254 | #[get("/restart")] 255 | fn restart( 256 | sender: rocket::State<'_, crate::PrirodaSender>, 257 | ) -> crate::RResult> { 258 | sender.do_work(move |pcx| { 259 | pcx.restart(); 260 | response::Flash::success(response::Redirect::to("/"), "restarted") 261 | }) 262 | } 263 | 264 | #[get("/single")] 265 | fn single( 266 | sender: rocket::State<'_, crate::PrirodaSender>, 267 | ) -> crate::RResult> { 268 | sender.do_work(move |pcx| { 269 | response::Flash::success( 270 | response::Redirect::to("/"), 271 | step(pcx, |_ecx| ShouldContinue::Stop), 272 | ) 273 | }) 274 | } 275 | 276 | #[get("/single_back")] 277 | fn single_back( 278 | sender: rocket::State<'_, crate::PrirodaSender>, 279 | ) -> crate::RResult> { 280 | sender.do_work(move |pcx| { 281 | let orig_step_count = *pcx.step_count; 282 | pcx.restart(); 283 | *pcx.step_count = orig_step_count; 284 | let msg = if *pcx.step_count > 0 { 285 | *pcx.step_count -= 1; 286 | for _ in 0..*pcx.step_count { 287 | match pcx.ecx.step() { 288 | Ok(true) => crate::watch::step_callback(pcx), // Rebuild traces till the current instruction 289 | res => { 290 | return response::Flash::warning( 291 | response::Redirect::to("/"), 292 | format!("Miri is not deterministic causing error {:?}", res), 293 | ) 294 | } 295 | } 296 | } 297 | "stepped back".to_string() 298 | } else { 299 | "already at the start".to_string() 300 | }; 301 | 302 | response::Flash::success(response::Redirect::to("/"), msg) 303 | }) 304 | } 305 | 306 | #[get("/next")] 307 | fn next( 308 | sender: rocket::State<'_, crate::PrirodaSender>, 309 | ) -> crate::RResult> { 310 | sender.do_work(move |pcx| { 311 | let frame = Machine::stack(&pcx.ecx).len(); 312 | let (block, stmt): (Option, _) = 313 | if let Some(location) = pcx.ecx.frame().current_loc().ok() { 314 | (Some(location.block), Some(location.statement_index)) 315 | } else { 316 | (None, None) 317 | }; 318 | let msg = step(pcx, move |ecx| { 319 | let (curr_block, curr_stmt) = if let Some(location) = ecx.frame().current_loc().ok() 320 | { 321 | (Some(location.block), Some(location.statement_index)) 322 | } else { 323 | (None, None) 324 | }; 325 | if Machine::stack(&ecx).len() <= frame && (block < curr_block || stmt < curr_stmt) { 326 | ShouldContinue::Stop 327 | } else { 328 | ShouldContinue::Continue 329 | } 330 | }); 331 | response::Flash::success(response::Redirect::to("/"), msg) 332 | }) 333 | } 334 | 335 | #[get("/return")] 336 | fn return_( 337 | sender: rocket::State<'_, crate::PrirodaSender>, 338 | ) -> crate::RResult> { 339 | sender.do_work(move |pcx| { 340 | let frame = Machine::stack(&pcx.ecx).len(); 341 | let msg = step(pcx, |ecx| { 342 | if Machine::stack(&ecx).len() <= frame && is_ret(&ecx) { 343 | ShouldContinue::Stop 344 | } else { 345 | ShouldContinue::Continue 346 | } 347 | }); 348 | response::Flash::success(response::Redirect::to("/"), msg) 349 | }) 350 | } 351 | 352 | #[get("/continue")] 353 | fn continue_( 354 | sender: rocket::State<'_, crate::PrirodaSender>, 355 | ) -> crate::RResult> { 356 | sender.do_work(move |pcx| { 357 | response::Flash::success( 358 | response::Redirect::to("/"), 359 | step(pcx, |_ecx| ShouldContinue::Continue), 360 | ) 361 | }) 362 | } 363 | } 364 | 365 | pub mod bp_routes { 366 | use super::*; 367 | use std::path::PathBuf; 368 | 369 | pub fn routes() -> Vec<::rocket::Route> { 370 | routes![add_here, add, remove, remove_all] 371 | } 372 | 373 | #[get("/add_here")] 374 | pub fn add_here( 375 | sender: rocket::State<'_, crate::PrirodaSender>, 376 | ) -> crate::RResult> { 377 | sender.do_work(move |pcx| { 378 | let frame = pcx.ecx.frame(); 379 | let msg = if let Some(location) = frame.current_loc().ok() { 380 | pcx.config.bptree.add_breakpoint(Breakpoint( 381 | frame.instance.def_id(), 382 | location.block, 383 | location.statement_index, 384 | )); 385 | format!( 386 | "Breakpoint added for {:?}@{}:{}", 387 | frame.instance.def_id(), 388 | location.block.index(), 389 | location.statement_index 390 | ) 391 | } else { 392 | format!("Can't set breakpoint for unwinding without cleanup yet") 393 | }; 394 | rocket::response::Flash::success(rocket::response::Redirect::to("/"), msg) 395 | }) 396 | } 397 | 398 | #[get("/add/")] 399 | pub fn add( 400 | sender: rocket::State<'_, crate::PrirodaSender>, 401 | path: PathBuf, 402 | ) -> crate::RResult> { 403 | sender.do_work(move |pcx| { 404 | let path = path.to_string_lossy(); 405 | let res = parse_breakpoint_from_url(&path); 406 | let msg = match res { 407 | Ok(breakpoint) => { 408 | pcx.config.bptree.add_breakpoint(breakpoint); 409 | format!( 410 | "Breakpoint added for {:?}@{}:{}", 411 | breakpoint.0, 412 | breakpoint.1.index(), 413 | breakpoint.2 414 | ) 415 | } 416 | Err(e) => e, 417 | }; 418 | rocket::response::Flash::success(rocket::response::Redirect::to("/"), msg) 419 | }) 420 | } 421 | 422 | #[get("/remove/")] 423 | pub fn remove( 424 | sender: rocket::State<'_, crate::PrirodaSender>, 425 | path: PathBuf, 426 | ) -> crate::RResult> { 427 | sender.do_work(move |pcx| { 428 | let path = path.to_string_lossy(); 429 | let res = parse_breakpoint_from_url(&path); 430 | let msg = match res { 431 | Ok(breakpoint) => { 432 | if pcx.config.bptree.remove_breakpoint(breakpoint) { 433 | format!( 434 | "Breakpoint removed for {:?}@{}:{}", 435 | breakpoint.0, 436 | breakpoint.1.index(), 437 | breakpoint.2 438 | ) 439 | } else { 440 | format!( 441 | "No breakpoint for for {:?}@{}:{}", 442 | breakpoint.0, 443 | breakpoint.1.index(), 444 | breakpoint.2 445 | ) 446 | } 447 | } 448 | Err(e) => e, 449 | }; 450 | rocket::response::Flash::success(rocket::response::Redirect::to("/"), msg) 451 | }) 452 | } 453 | 454 | #[get("/remove_all")] 455 | pub fn remove_all( 456 | sender: rocket::State<'_, crate::PrirodaSender>, 457 | ) -> crate::RResult> { 458 | sender.do_work(move |pcx| { 459 | pcx.config.bptree.remove_all(); 460 | rocket::response::Flash::success( 461 | rocket::response::Redirect::to("/"), 462 | "All breakpoints removed", 463 | ) 464 | }) 465 | } 466 | } 467 | -------------------------------------------------------------------------------- /src/watch/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt::Write; 3 | 4 | use rustc_middle::mir::interpret::{Allocation, Pointer, PointerArithmetic}; 5 | use rustc_middle::ty::Instance; 6 | use rustc_mir::interpret::AllocId; 7 | use rustc_target::abi::Size; 8 | 9 | use crate::*; 10 | 11 | mod stack_trace; 12 | 13 | #[derive(Debug)] 14 | pub struct Traces<'tcx> { 15 | alloc_traces: HashMap, 16 | stack_traces_cpu: Vec<(Vec<(Instance<'tcx>,)>, u128)>, 17 | stack_traces_mem: Vec<(Vec<(Instance<'tcx>,)>, u128)>, 18 | } 19 | 20 | impl<'tcx> Traces<'tcx> { 21 | pub fn new() -> Self { 22 | let alloc_traces = HashMap::new(); 23 | //for i in 0..700 { 24 | // alloc_traces.insert(AllocId(i), AllocTrace::new()); 25 | //} 26 | Traces { 27 | alloc_traces, 28 | stack_traces_cpu: Vec::new(), 29 | stack_traces_mem: Vec::new(), 30 | } 31 | } 32 | 33 | /// Clear the traces. This should be called before restarting the evaluation. 34 | pub fn clear(&mut self) { 35 | // We have to replace all values of alloc_traces by empty AllocTraces, 36 | // because stepping back will change the alloc id's 37 | self.alloc_traces.clear(); 38 | 39 | // We can just empty the stack traces, because they will be rebuild during stepping 40 | self.stack_traces_cpu.clear(); 41 | self.stack_traces_mem.clear(); 42 | } 43 | } 44 | 45 | #[derive(Debug)] 46 | struct AllocTrace { 47 | trace_points: Vec<(u128, AllocTracePoint)>, 48 | } 49 | 50 | impl AllocTrace { 51 | fn new() -> Self { 52 | AllocTrace { 53 | trace_points: Vec::new(), 54 | } 55 | } 56 | } 57 | 58 | #[derive(Debug)] 59 | enum AllocTracePoint { 60 | Changed(Allocation), 61 | Deallocated, 62 | } 63 | 64 | fn eq_alloc( 65 | a: &Allocation, 66 | b: &Allocation, 67 | ) -> bool { 68 | let Allocation { 69 | align: a_align, 70 | mutability: a_mut, 71 | extra: _, 72 | size: a_size, 73 | .. 74 | } = a; 75 | let Allocation { 76 | align: b_align, 77 | mutability: b_mut, 78 | extra: _, 79 | size: b_size, 80 | .. 81 | } = b; 82 | a_align == b_align 83 | && a_mut == b_mut 84 | && a_size == b_size 85 | && a.inspect_with_uninit_and_ptr_outside_interpreter(0..a.len()) 86 | == b.inspect_with_uninit_and_ptr_outside_interpreter(0..b.len()) 87 | && a.relocations() == b.relocations() 88 | && a.init_mask() == b.init_mask() 89 | } 90 | 91 | pub fn step_callback(pcx: &mut PrirodaContext<'_, '_>) { 92 | { 93 | let ecx = &pcx.ecx; 94 | let traces = &mut pcx.traces; 95 | 96 | // Collect alloc traces 97 | for (alloc_id, alloc_trace) in &mut traces.alloc_traces { 98 | if let Ok(alloc) = ecx.memory.get_raw(*alloc_id) { 99 | if let Some(&(prev_step_count, AllocTracePoint::Changed(ref prev_alloc))) = 100 | alloc_trace.trace_points.last() 101 | { 102 | if eq_alloc(alloc, prev_alloc) || *pcx.step_count == prev_step_count { 103 | continue; 104 | } 105 | } 106 | 107 | alloc_trace 108 | .trace_points 109 | .push((*pcx.step_count, AllocTracePoint::Changed(alloc.clone()))); 110 | } else if !matches!( 111 | alloc_trace.trace_points.last(), 112 | Some(&(_, AllocTracePoint::Deallocated)) 113 | ) && !alloc_trace.trace_points.is_empty() 114 | { 115 | alloc_trace 116 | .trace_points 117 | .push((*pcx.step_count, AllocTracePoint::Deallocated)); 118 | } 119 | } 120 | } 121 | 122 | stack_trace::step_callback(pcx); 123 | } 124 | 125 | pub fn routes() -> Vec<::rocket::Route> { 126 | routes![watch::show, watch::continue_and_show, watch::add] 127 | } 128 | 129 | #[get("/show")] 130 | fn show(sender: rocket::State<'_, crate::PrirodaSender>) -> crate::RResult> { 131 | sender.do_work(|pcx| { 132 | let mut buf = String::new(); 133 | 134 | stack_trace::show(pcx, &mut buf).unwrap(); 135 | 136 | let mut alloc_traces = pcx.traces.alloc_traces.iter().collect::>(); 137 | alloc_traces.sort_by_key(|(id, _)| id.0); 138 | for (alloc_id, alloc_trace) in alloc_traces { 139 | if alloc_trace.trace_points.is_empty() { 140 | writeln!(buf, "

Alloc {} has never existed

", alloc_id.0).unwrap(); 141 | continue; 142 | } 143 | 144 | writeln!(buf, "

Alloc {}

\n", alloc_id.0).unwrap(); 145 | for (step_count, trace_point) in &alloc_trace.trace_points { 146 | let content = match trace_point { 147 | AllocTracePoint::Changed(alloc) => crate::render::locals::print_alloc( 148 | pcx.ecx.memory.pointer_size().bytes(), 149 | Pointer::new(*alloc_id, Size::from_bytes(0)).with_tag(miri::Tag::Untagged), 150 | alloc, 151 | None, 152 | ), 153 | AllocTracePoint::Deallocated => "Dealloc".to_string(), 154 | }; 155 | writeln!( 156 | buf, 157 | "\n\n\n", 158 | step_count, content 159 | ) 160 | .unwrap(); 161 | } 162 | writeln!(buf, "
{}{}
\n").unwrap(); 163 | } 164 | 165 | Html(buf) 166 | }) 167 | } 168 | 169 | #[get("/continue_and_show")] 170 | pub fn continue_and_show(sender: State<'_, PrirodaSender>) -> RResult> { 171 | sender.do_work(move |pcx| { 172 | crate::step::step(pcx, |_ecx| crate::step::ShouldContinue::Continue); 173 | })?; 174 | show(sender) 175 | } 176 | 177 | #[get("/add/")] 178 | pub fn add( 179 | sender: rocket::State<'_, crate::PrirodaSender>, 180 | id: u64, 181 | ) -> crate::RResult { 182 | sender.do_work(move |pcx| { 183 | pcx.traces 184 | .alloc_traces 185 | .insert(AllocId(id), AllocTrace::new()); 186 | step_callback(pcx); 187 | rocket::response::Redirect::to("/") 188 | }) 189 | } 190 | -------------------------------------------------------------------------------- /src/watch/stack_trace.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | use std::io::{self, Write as IoWrite}; 3 | use std::process::{Command, Stdio}; 4 | 5 | use rustc_middle::ty::{self, Instance, InstanceDef, ParamEnv}; 6 | use rustc_mir::interpret::Machine; 7 | 8 | use crate::*; 9 | 10 | pub(super) fn step_callback(pcx: &mut PrirodaContext<'_, '_>) { 11 | let ecx = &pcx.ecx; 12 | let traces = &mut pcx.traces; 13 | 14 | let stack_trace = Machine::stack(ecx) 15 | .iter() 16 | .map(|frame| (frame.instance,)) 17 | .collect::>(); 18 | insert_stack_trace(&mut traces.stack_traces_cpu, stack_trace.clone(), 1); 19 | 20 | let location = if let Some(location) = ecx.frame().current_loc().ok() { 21 | location 22 | } else { 23 | return; // Unwinding, but no cleanup for current frame needed 24 | }; 25 | let mir::Location { 26 | block, 27 | statement_index: stmt, 28 | } = location; 29 | let blck = &ecx.frame().body.basic_blocks()[block]; 30 | 31 | if stmt == blck.statements.len() { 32 | use rustc_middle::mir::TerminatorKind::*; 33 | match &blck.terminator().kind { 34 | Call { func, args, .. } => { 35 | if let Some(instance) = instance_for_call_operand(ecx, &func) { 36 | insert_stack_traces_for_instance(pcx, stack_trace, instance, Some(&args)); 37 | } 38 | } 39 | Drop { place, .. } => { 40 | let location_ty = place.ty(ecx.frame().body, ecx.tcx.tcx).ty; 41 | let location_ty = ecx.tcx.subst_and_normalize_erasing_regions( 42 | ecx.frame().instance.substs, 43 | ParamEnv::reveal_all(), 44 | location_ty, 45 | ); 46 | let instance = Instance::resolve_drop_in_place(ecx.tcx.tcx, location_ty); 47 | insert_stack_traces_for_instance(pcx, stack_trace, instance, None); 48 | } 49 | _ => {} 50 | } 51 | } 52 | } 53 | 54 | fn instance_for_call_operand<'a, 'tcx: 'a>( 55 | ecx: &InterpCx<'tcx>, 56 | func: &'tcx rustc_middle::mir::Operand<'_>, 57 | ) -> Option> { 58 | let res: ::miri::InterpResult<'_, Instance<'_>> = try { 59 | let func = ecx.eval_operand(func, None)?; 60 | 61 | match func.layout.ty.kind() { 62 | ty::FnPtr(_) => { 63 | let fn_ptr = ecx.read_scalar(&func)?.check_init()?; 64 | if let Ok(instance) = ecx.memory.get_fn(fn_ptr)?.as_instance() { 65 | instance 66 | } else { 67 | return None; 68 | } 69 | } 70 | ty::FnDef(def_id, substs) => { 71 | let substs = ecx.tcx.subst_and_normalize_erasing_regions( 72 | ecx.frame().instance.substs, 73 | ParamEnv::reveal_all(), 74 | *substs, 75 | ); 76 | ty::Instance::resolve(*ecx.tcx, ParamEnv::reveal_all(), *def_id, substs) 77 | .unwrap() 78 | .unwrap() 79 | } 80 | _ => { 81 | panic!("can't handle callee of type {:?}", func.layout.ty); 82 | } 83 | } 84 | }; 85 | Some(res.unwrap()) 86 | } 87 | 88 | fn insert_stack_traces_for_instance<'a, 'tcx: 'a>( 89 | pcx: &mut PrirodaContext<'a, 'tcx>, 90 | mut stack_trace: Vec<(Instance<'tcx>,)>, 91 | instance: Instance<'tcx>, 92 | args: Option<&[mir::Operand<'tcx>]>, 93 | ) { 94 | let ecx = &pcx.ecx; 95 | let traces = &mut pcx.traces; 96 | 97 | let item_path = ecx.tcx.def_path_str(instance.def_id()); 98 | 99 | stack_trace.push((instance,)); 100 | insert_stack_trace(&mut traces.stack_traces_cpu, stack_trace.clone(), 1); 101 | 102 | let _: ::miri::InterpResult<'_> = try { 103 | let args = if let Some(args) = args { 104 | args.into_iter() 105 | .map(|op| ecx.eval_operand(op, None)) 106 | .collect::, _>>()? 107 | } else { 108 | return; 109 | }; 110 | 111 | match &item_path[..] { 112 | "alloc::alloc::::__rust_alloc" | "alloc::alloc::::__rust_alloc_zeroed" => { 113 | let size = ecx.read_scalar(&args[0])?.to_machine_usize(&ecx.tcx.tcx)?; 114 | insert_stack_trace(&mut traces.stack_traces_mem, stack_trace, size as u128); 115 | } 116 | "alloc::alloc::::__rust_realloc" => { 117 | let old_size = ecx.read_scalar(&args[1])?.to_machine_usize(&ecx.tcx.tcx)?; 118 | let new_size = ecx.read_scalar(&args[3])?.to_machine_usize(&ecx.tcx.tcx)?; 119 | if new_size > old_size { 120 | insert_stack_trace( 121 | &mut traces.stack_traces_mem, 122 | stack_trace, 123 | (new_size - old_size) as u128, 124 | ); 125 | } 126 | } 127 | _ => {} 128 | } 129 | }; 130 | } 131 | 132 | fn insert_stack_trace(traces: &mut Vec<(T, u128)>, trace: T, count: u128) { 133 | if let Some(t) = traces.last_mut() { 134 | if t.0 == trace { 135 | t.1 += count; 136 | return; 137 | } 138 | } 139 | traces.push((trace, count)); 140 | } 141 | 142 | pub(super) fn show(pcx: &PrirodaContext<'_, '_>, buf: &mut impl Write) -> io::Result<()> { 143 | writeln!(buf, "{}\n", crate::render::refresh_script(pcx)).unwrap(); 144 | create_flame_graph( 145 | &pcx.ecx, 146 | &mut *buf, 147 | &pcx.traces.stack_traces_cpu, 148 | "Cpu usage", 149 | "instructions", 150 | "java", 151 | "flame_graph_cpu", 152 | )?; 153 | create_flame_graph( 154 | &pcx.ecx, 155 | &mut *buf, 156 | &pcx.traces.stack_traces_mem, 157 | "Memory usage", 158 | "bytes", 159 | "mem", 160 | "flame_graph_mem", 161 | )?; 162 | print_stack_traces(&pcx.ecx, &mut *buf, &pcx.traces.stack_traces_cpu).unwrap(); 163 | Ok(()) 164 | } 165 | 166 | fn create_flame_graph<'a, 'tcx: 'a>( 167 | ecx: &InterpCx<'tcx>, 168 | mut buf: impl Write, 169 | traces: &[(Vec<(Instance<'tcx>,)>, u128)], 170 | name: &str, 171 | count_name: &str, 172 | color_scheme: &str, 173 | _file_name: &str, 174 | ) -> io::Result<()> { 175 | let mut flame_data = String::new(); 176 | for (stack_trace, count) in traces { 177 | let mut last_crate = rustc_hir::def_id::LOCAL_CRATE; 178 | writeln!( 179 | flame_data, 180 | "{} {}", 181 | stack_trace 182 | .iter() 183 | .map(|(instance,)| { 184 | let mut name = ecx.tcx.def_path_str(instance.def_id()); 185 | match instance.def { 186 | InstanceDef::Intrinsic(..) => name.push_str("_[k]"), 187 | InstanceDef::DropGlue(..) => name.push_str("_[k]"), 188 | _ => { 189 | if instance.def_id().is_local() { 190 | name.push_str("_[j]"); 191 | } 192 | } 193 | } 194 | if last_crate != instance.def_id().krate { 195 | name = "-;".to_string() + &name; 196 | last_crate = instance.def_id().krate; 197 | } 198 | name 199 | }) 200 | .collect::>() 201 | .join(";"), 202 | count 203 | ) 204 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 205 | } 206 | 207 | //::std::fs::write(format!("./resources/{}.txt", _file_name), flame_data.as_bytes())?; 208 | 209 | let child = Command::new("../FlameGraph/flamegraph.pl") 210 | .arg("-") 211 | .arg("--title") 212 | .arg(name) 213 | .arg("--countname") 214 | .arg(count_name) 215 | .arg("--colors") 216 | .arg(color_scheme) 217 | .arg("--minwidth") 218 | .arg("0.01") 219 | .stdin(Stdio::piped()) 220 | .stdout(Stdio::piped()) 221 | .spawn(); 222 | match child { 223 | Ok(mut child) => { 224 | child 225 | .stdin 226 | .as_mut() 227 | .unwrap() 228 | .write_all(flame_data.as_bytes())?; 229 | match child.wait_with_output() { 230 | Ok(output) => { 231 | let flame_graph = String::from_utf8(output.stdout).unwrap(); 232 | //::std::fs::write(format!("./resources/{}.svg", _file_name), flame_graph.as_bytes())?; 233 | writeln!(buf, "{}", flame_graph).unwrap() 234 | } 235 | Err(err) => writeln!(buf, "

Wait error: {:?}

", err).unwrap(), 236 | } 237 | } 238 | Err(err) => writeln!(buf, "

Spawn error: {:?}

", err).unwrap(), 239 | } 240 | 241 | Ok(()) 242 | } 243 | 244 | fn print_stack_traces<'a, 'tcx: 'a>( 245 | ecx: &InterpCx<'tcx>, 246 | mut buf: impl Write, 247 | traces: &[(Vec<(Instance<'tcx>,)>, u128)], 248 | ) -> ::std::fmt::Result { 249 | let name_for_instance = |i: Instance<'_>| { 250 | ecx.tcx 251 | .def_path_str(i.def_id()) 252 | .replace("<", "<") 253 | .replace(">", ">") 254 | }; 255 | writeln!(buf, "

Stack trace

\n
    \n")?; 256 | for (stack_trace, count) in traces { 257 | writeln!( 258 | buf, 259 | "
  • {2}{0} ({1})
  • \n", 260 | name_for_instance(stack_trace.last().unwrap().0), 261 | count, 262 | format!( 263 | "{nil: ")?; 271 | Ok(()) 272 | } 273 | --------------------------------------------------------------------------------