diff --git a/Cargo.lock b/Cargo.lock index b53eb71..d7c2846 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -56,6 +65,19 @@ dependencies = [ "libc", ] +[[package]] +name = "ansi-to-tui" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00c4af0bef1b514c9b6a32a773caf604c1390fa7913f4eaa23bfe76f251d6a42" +dependencies = [ + "nom", + "ratatui 0.28.1", + "simdutf8", + "smallvec", + "thiserror", +] + [[package]] name = "anstream" version = "0.6.12" @@ -72,9 +94,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" @@ -110,16 +132,137 @@ version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" +[[package]] +name = "arboard" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" +dependencies = [ + "clipboard-win", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "x11rb", +] + [[package]] name = "ariadne" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd002a6223f12c7a95cdd4b1cb3a0149d22d37f7a9ecdb2cb691a071fe236c29" dependencies = [ - "unicode-width", + "unicode-width 0.1.14", "yansi", ] +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.1.1", + "futures-lite 2.3.0", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite 1.13.0", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling", + "rustix 0.37.27", + "slab", + "socket2", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 1.13.0", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "atty" version = "0.2.14" @@ -137,12 +280,37 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.7.4", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "better-panic" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa9e1d11a268684cbd90ed36370d7577afb6c62d912ddff5c15fc34343e5036" +dependencies = [ + "backtrace", + "console", +] + [[package]] name = "bincode" version = "1.3.3" @@ -194,12 +362,49 @@ dependencies = [ "which 4.3.0", ] +[[package]] +name = "binsider" +version = "0.2.0" +source = "git+https://github.com/godzie44/binsider.git?branch=godzie44/run_as_widget#c224619d3ce5fd5ccc03889c34092543d853d7c5" +dependencies = [ + "ansi-to-tui", + "better-panic", + "bytesize", + "chrono", + "clap 4.5.18", + "console", + "elf", + "heh", + "itertools 0.13.0", + "lddtree", + "lurk-cli", + "nix 0.29.0", + "ratatui 0.28.1", + "rust-strings", + "sysinfo 0.31.4", + "termbg", + "textwrap", + "thiserror", + "tui-big-text", + "tui-input", + "tui-popup", + "unicode-width 0.2.0", + "webbrowser", + "which 6.0.3", +] + [[package]] name = "bit_field" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" + [[package]] name = "bitflags" version = "1.3.2" @@ -208,9 +413,33 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand 1.9.0", + "futures-lite 1.13.0", + "log", +] [[package]] name = "bugstalker" @@ -218,12 +447,13 @@ version = "0.2.3" dependencies = [ "anyhow", "ariadne", + "binsider", "bit_field", "bytes", "capstone", "chrono", "chumsky", - "clap 4.0.27", + "clap 4.5.18", "crossterm 0.27.0", "ctrlc", "env_logger 0.11.2", @@ -243,6 +473,7 @@ dependencies = [ "ouroboros", "proc-maps", "rand", + "ratatui 0.28.1", "rayon", "regex", "rustc-demangle", @@ -255,7 +486,7 @@ dependencies = [ "strum", "strum_macros", "syntect", - "sysinfo", + "sysinfo 0.30.0", "thiserror", "thread_db", "timeout-readwrite", @@ -264,11 +495,11 @@ dependencies = [ "tui-realm-stdlib", "tuirealm", "typed-arena", - "unicode-width", + "unicode-width 0.1.14", "unwind", "uuid", "walkdir", - "which 6.0.1", + "which 6.0.3", ] [[package]] @@ -289,6 +520,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +[[package]] +name = "bytesize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" + [[package]] name = "capstone" version = "0.11.0" @@ -317,18 +554,24 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "castaway" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" dependencies = [ "rustversion", ] [[package]] name = "cc" -version = "1.0.79" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cexpr" @@ -351,18 +594,24 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -399,37 +648,44 @@ dependencies = [ "bitflags 1.3.2", "clap_lex 0.2.4", "indexmap 1.9.1", - "strsim", + "strsim 0.10.0", "termcolor", "textwrap", ] [[package]] name = "clap" -version = "4.0.27" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0acbd8d28a0a60d7108d7ae850af6ba34cf2d1257fc646980e5f97ce14275966" +checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" dependencies = [ - "bitflags 1.3.2", + "clap_builder", "clap_derive", - "clap_lex 0.3.0", - "is-terminal", - "once_cell", - "strsim", - "termcolor", +] + +[[package]] +name = "clap_builder" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex 0.7.2", + "strsim 0.11.1", + "terminal_size", ] [[package]] name = "clap_derive" -version = "4.0.21" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck", - "proc-macro-error", + "heck 0.5.0", "proc-macro2 1.0.83", "quote 1.0.36", - "syn 1.0.99", + "syn 2.0.65", ] [[package]] @@ -443,18 +699,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.3.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" -dependencies = [ - "os_str_bytes", -] +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "clipboard-win" -version = "5.1.0" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec832972fefb8cf9313b45a0d1945e29c9c251f1d4c6eafc5fe2124c02d2e81" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" dependencies = [ "error-code", ] @@ -465,6 +718,28 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "comfy-table" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" +dependencies = [ + "crossterm 0.27.0", + "strum", + "strum_macros", + "unicode-width 0.1.14", +] + [[package]] name = "compact_str" version = "0.7.1" @@ -478,11 +753,57 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "compact_str" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width 0.1.14", + "windows-sys 0.52.0", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crc32fast" @@ -493,39 +814,62 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.13" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] -name = "crossbeam-utils" -version = "0.8.14" +name = "crossbeam-queue" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cfg-if", + "crossbeam-utils", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crossterm" version = "0.25.0" @@ -535,7 +879,7 @@ dependencies = [ "bitflags 1.3.2", "crossterm_winapi", "libc", - "mio", + "mio 0.8.5", "parking_lot", "signal-hook", "signal-hook-mio", @@ -548,16 +892,32 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.6.0", "crossterm_winapi", "libc", - "mio", + "mio 0.8.5", "parking_lot", "signal-hook", "signal-hook-mio", "winapi", ] +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.6.0", + "crossterm_winapi", + "mio 1.0.2", + "parking_lot", + "rustix 0.38.34", + "signal-hook", + "signal-hook-mio", + "winapi", +] + [[package]] name = "crossterm_winapi" version = "0.9.1" @@ -567,6 +927,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote 1.0.36", + "syn 1.0.99", +] + [[package]] name = "ctrlc" version = "3.4.0" @@ -577,6 +947,41 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2 1.0.83", + "quote 1.0.36", + "strsim 0.11.1", + "syn 2.0.65", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote 1.0.36", + "syn 2.0.65", +] + [[package]] name = "dashmap" version = "5.4.0" @@ -610,6 +1015,60 @@ dependencies = [ "syn 1.0.99", ] +[[package]] +name = "derive-getters" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" +dependencies = [ + "proc-macro2 1.0.83", + "quote 1.0.36", + "syn 2.0.65", +] + +[[package]] +name = "derive_builder" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38" +dependencies = [ + "darling", + "proc-macro2 1.0.83", + "quote 1.0.36", + "syn 2.0.65", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" +dependencies = [ + "derive_builder_core", + "syn 2.0.65", +] + +[[package]] +name = "derive_setters" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8ef033054e131169b8f0f9a7af8f5533a9436fadf3c500ed547f730f07090d" +dependencies = [ + "darling", + "proc-macro2 1.0.83", + "quote 1.0.36", + "syn 2.0.65", +] + [[package]] name = "dlopen" version = "0.1.8" @@ -633,12 +1092,33 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + [[package]] name = "either" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "endian-type" version = "0.1.2" @@ -724,12 +1204,33 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc" +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "fallible-iterator" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + [[package]] name = "fd-lock" version = "4.0.2" @@ -757,7 +1258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.5.3", ] [[package]] @@ -766,6 +1267,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "font8x8" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875488b8711a968268c7cf5d139578713097ca4635a76044e8fe8eedf831d07e" + [[package]] name = "foreign-types" version = "0.5.0" @@ -793,6 +1300,24 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + [[package]] name = "futures" version = "0.3.25" @@ -841,6 +1366,34 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.1.1", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-sink" version = "0.3.25" @@ -870,6 +1423,16 @@ dependencies = [ "slab", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + [[package]] name = "getrandom" version = "0.2.7" @@ -898,6 +1461,29 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "goblin" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" +dependencies = [ + "log", + "plain", + "scroll", +] + [[package]] name = "godzie44-tui-realm-treeview" version = "1.0.0" @@ -906,7 +1492,7 @@ checksum = "6e1ea2f5387de83bfe6a21f4179fe3358f9401c1cc5beb371c346a83a7b6f77a" dependencies = [ "orange-trees", "tuirealm", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -931,6 +1517,26 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "heh" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa7318185feb424960807011d20bebc4fde859261ae58d1af961d98e30195b73" +dependencies = [ + "arboard", + "clap 4.5.18", + "crossbeam", + "hex", + "memmap2", + "ratatui 0.28.1", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -942,12 +1548,15 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "home" @@ -975,7 +1584,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.51.1", ] [[package]] @@ -987,6 +1596,22 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -1008,25 +1633,33 @@ dependencies = [ ] [[package]] -name = "io-lifetimes" -version = "1.0.1" +name = "instability" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d367024b3f3414d8e01f437f704f41a9f64ab36f9067fa73e526ad4c763c87" +checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" dependencies = [ - "libc", - "windows-sys 0.42.0", + "quote 1.0.36", + "syn 2.0.65", ] [[package]] -name = "is-terminal" -version = "0.4.0" +name = "instant" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae5bc6e2eb41c9def29a3e0f1306382807764b9b53112030eff57435667352d" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ - "hermit-abi 0.2.6", - "io-lifetimes", - "rustix 0.36.3", - "windows-sys 0.42.0", + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", ] [[package]] @@ -1047,12 +1680,43 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "js-sys" version = "0.3.66" @@ -1062,6 +1726,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy-regex" version = "3.0.2" @@ -1097,11 +1770,22 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lddtree" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "735dc8281e12cf7450b3a343c50bccdb15625f41b127d1bd7063949fe367847d" +dependencies = [ + "fs-err", + "glob", + "goblin", +] + [[package]] name = "libc" -version = "0.2.153" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libloading" @@ -1149,11 +1833,21 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-personality" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1a976577555db53f0a98e9b236e1ebff1a0a1793227ed1b88d7b9a03dda93" +dependencies = [ + "bitflags 0.7.0", + "libc", +] + [[package]] name = "linux-raw-sys" -version = "0.1.3" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" @@ -1161,6 +1855,12 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.9" @@ -1178,6 +1878,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", + "value-bag", ] [[package]] @@ -1198,6 +1899,28 @@ dependencies = [ "hashbrown 0.14.1", ] +[[package]] +name = "lurk-cli" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f34b54232c17c04a0de4de4d5578f7b38508c5b1ff547d3f25e10fbd98ddad" +dependencies = [ + "anyhow", + "atty", + "byteorder", + "clap 4.5.18", + "comfy-table", + "console", + "libc", + "linux-personality", + "nix 0.29.0", + "regex", + "serde", + "serde_json", + "syscalls", + "users", +] + [[package]] name = "mach2" version = "0.4.1" @@ -1215,22 +1938,13 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.9.0" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1246,6 +1960,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.5" @@ -1258,6 +1981,25 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "nibble_vec" version = "0.1.0" @@ -1285,7 +2027,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.6.0", "cfg-if", "libc", ] @@ -1296,9 +2038,21 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases 0.1.1", + "libc", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", "cfg-if", - "cfg_aliases", + "cfg_aliases 0.2.1", "libc", ] @@ -1336,6 +2090,105 @@ dependencies = [ "autocfg", ] +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.6.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.6.0", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + [[package]] name = "object" version = "0.32.1" @@ -1414,7 +2267,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cad0c4b129e9696e37cb712b243777b90ef489a0bfaa0ac34e7d9b860e4f134" dependencies = [ - "heck", + "heck 0.4.1", "itertools 0.11.0", "proc-macro-error", "proc-macro2 1.0.83", @@ -1422,6 +2275,12 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1457,6 +2316,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1475,6 +2340,12 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "plist" version = "1.5.1" @@ -1489,6 +2360,22 @@ dependencies = [ "time", ] +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1635,22 +2522,43 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.26.3" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +dependencies = [ + "bitflags 2.6.0", + "cassowary", + "compact_str 0.7.1", + "crossterm 0.27.0", + "itertools 0.12.1", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.1.14", +] + +[[package]] +name = "ratatui" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.6.0", "cassowary", - "compact_str", - "crossterm 0.27.0", - "itertools 0.12.1", + "compact_str 0.8.0", + "crossterm 0.28.1", + "instability", + "itertools 0.13.0", "lru", "paste", - "stability", "strum", + "strum_macros", "unicode-segmentation", "unicode-truncate", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -1717,6 +2625,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "rust-strings" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d17559aaea24b0a7e99482f82273ea3177cea900dcdf78ebef7eef05dfda1476" + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -1731,16 +2645,16 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.36.3" +version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1fbb4dfc4eb1d390c02df47760bb19a84bb80b301ecc947ab5406394d8223e" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ "bitflags 1.3.2", - "errno 0.2.8", + "errno 0.3.8", "io-lifetimes", "libc", - "linux-raw-sys 0.1.3", - "windows-sys 0.42.0", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", ] [[package]] @@ -1749,7 +2663,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.6.0", "errno 0.3.8", "libc", "linux-raw-sys 0.4.13", @@ -1768,7 +2682,7 @@ version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.6.0", "cfg-if", "clipboard-win", "fd-lock", @@ -1779,7 +2693,7 @@ dependencies = [ "nix 0.28.0", "radix_trie", "unicode-segmentation", - "unicode-width", + "unicode-width 0.1.14", "utf8parse", "windows-sys 0.52.0", ] @@ -1833,6 +2747,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +dependencies = [ + "proc-macro2 1.0.83", + "quote 1.0.36", + "syn 2.0.65", +] + [[package]] name = "serde" version = "1.0.202" @@ -1864,6 +2798,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2 1.0.83", + "quote 1.0.36", + "syn 2.0.65", +] + [[package]] name = "serde_spanned" version = "0.6.6" @@ -1916,12 +2861,13 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio", + "mio 0.8.5", + "mio 1.0.2", "signal-hook", ] @@ -1934,6 +2880,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.7" @@ -1955,6 +2907,16 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "stability" version = "0.2.0" @@ -1996,6 +2958,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.26.1" @@ -2007,11 +2975,11 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.26.1" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2 1.0.83", "quote 1.0.36", "rustversion", @@ -2073,6 +3041,16 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "syscalls" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d0e35dc7d73976a53c7e6d7d177ef804a0c0ee774ec77bcc520c2216fd7cbe" +dependencies = [ + "serde", + "serde_repr", +] + [[package]] name = "sysinfo" version = "0.30.0" @@ -2085,7 +3063,30 @@ dependencies = [ "ntapi", "once_cell", "rayon", - "windows", + "windows 0.51.1", +] + +[[package]] +name = "sysinfo" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" +dependencies = [ + "core-foundation-sys", + "libc", + "windows 0.57.0", +] + +[[package]] +name = "termbg" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bc77395d133abffe2ff55fc005178ae410973936f706833b8f75272154887f2" +dependencies = [ + "async-std", + "crossterm 0.28.1", + "thiserror", + "winapi", ] [[package]] @@ -2097,22 +3098,32 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix 0.38.34", + "windows-sys 0.48.0", +] + [[package]] name = "textwrap" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ "smawk", "unicode-linebreak", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] @@ -2139,13 +3150,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2 1.0.83", "quote 1.0.36", - "syn 1.0.99", + "syn 2.0.65", ] [[package]] @@ -2203,6 +3214,21 @@ dependencies = [ "nix 0.26.2", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "toml" version = "0.8.13" @@ -2257,7 +3283,41 @@ dependencies = [ "cassowary", "crossterm 0.25.0", "unicode-segmentation", - "unicode-width", + "unicode-width 0.1.14", +] + +[[package]] +name = "tui-big-text" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399b145b30537641e56878b64bf648e04e1df996efb5d32cd7f19944152e3868" +dependencies = [ + "derive_builder", + "font8x8", + "itertools 0.13.0", + "ratatui 0.28.1", +] + +[[package]] +name = "tui-input" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd137780d743c103a391e06fe952487f914b299a4fe2c3626677f6a6339a7c6b" +dependencies = [ + "ratatui 0.28.1", + "unicode-width 0.1.14", +] + +[[package]] +name = "tui-popup" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0125f70f5b7b1ef457452c88db3f7ecf615922ccbfac70e292b53912695b53" +dependencies = [ + "derive-getters", + "derive_setters", + "document-features", + "ratatui 0.28.1", ] [[package]] @@ -2268,7 +3328,7 @@ checksum = "50ad506e872692d85265328353d92f4f633dd5add7f73ebd93e8d0368a708447" dependencies = [ "textwrap", "tuirealm", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -2277,10 +3337,10 @@ version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c358c39cb9e1b45702ea46afc77d39589d720ca9cf5f1e11a3106342ea6a4e76" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.6.0", "crossterm 0.27.0", "lazy-regex", - "ratatui", + "ratatui 0.26.3", "thiserror", "tui", "tuirealm_derive", @@ -2313,6 +3373,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -2325,6 +3391,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.10.1" @@ -2338,14 +3413,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" dependencies = [ "itertools 0.12.1", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unicode-xid" @@ -2374,6 +3455,27 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] + [[package]] name = "utf8parse" version = "0.2.1" @@ -2402,12 +3504,28 @@ dependencies = [ "syn 1.0.99", ] +[[package]] +name = "value-bag" +version = "1.0.0-alpha.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" +dependencies = [ + "ctor", + "version_check", +] + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + [[package]] name = "walkdir" version = "2.3.3" @@ -2449,6 +3567,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.89" @@ -2478,6 +3608,34 @@ version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +[[package]] +name = "web-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5f07fb9bc8de2ddfe6b24a71a75430673fd679e568c48b52716cef1cfae923" +dependencies = [ + "block2", + "core-foundation", + "home", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", +] + [[package]] name = "which" version = "4.3.0" @@ -2491,9 +3649,9 @@ dependencies = [ [[package]] name = "which" -version = "6.0.1" +version = "6.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", @@ -2538,10 +3696,20 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" dependencies = [ - "windows-core", + "windows-core 0.51.1", "windows-targets 0.48.5", ] +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.51.1" @@ -2551,6 +3719,49 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2 1.0.83", + "quote 1.0.36", + "syn 2.0.65", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2 1.0.83", + "quote 1.0.36", + "syn 2.0.65", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -2566,6 +3777,15 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2581,7 +3801,22 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -2601,17 +3836,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2628,9 +3864,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2646,9 +3882,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2664,9 +3900,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2682,9 +3924,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2700,9 +3942,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2718,9 +3960,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2736,9 +3978,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -2755,6 +3997,23 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "gethostname", + "rustix 0.38.34", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index b91c0d3..83fb598 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,11 +72,14 @@ serde = { version = "1.0.164", features = ["derive"] } toml = "0.8.13" home = "0.5.9" which = "6.0.1" +ratatui = { version = "0.28", optional = true } +binsider = { git = "https://github.com/godzie44/binsider.git", branch = "godzie44/run_as_widget", optional = true } [dev-dependencies] serial_test = "3.0.0" [features] -default = ["libunwind"] +default = ["libunwind", "binsider-integration"] libunwind = ["unwind"] +binsider-integration = ["binsider", "ratatui"] int_test = [] diff --git a/README.md b/README.md index 04910e7..94137b5 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ * [Other commands](#other-commands) * [Tui interface](#tui-interface) * [Configuration](#configuration) + * [Integrations](#integrations) + * [Binsider](#binsider) * [Oracles](#oracles) - [Contributing](#contributing) @@ -460,6 +462,18 @@ To override any of the defaults, begin by creating the corresponding file (from `~/.config/bs/keymap.toml`. You can change keybindings configuration file by exporting the `KEYMAP_FILE` environment variable. +## Integrations + +BugStalker has integration with some useful tools that can help you when researching programs. + +### Binsider + +According to [official site](https://binsider.dev/), `binsider` offers powerful static and dynamic analysis tools, +similar to readelf(1) and strace(1). +It lets you inspect strings, examine linked libraries, and perform hexdumps, all within a user-friendly TUI. + +- `binsider` - run binsider inside a debugger (alias: `bn`) + ## Oracles [demo console](https://github.com/godzie44/BugStalker/blob/master/doc/demo_oracle.gif) diff --git a/examples/vars/src/vars.rs b/examples/vars/src/vars.rs index 785ee8b..fa7324b 100644 --- a/examples/vars/src/vars.rs +++ b/examples/vars/src/vars.rs @@ -519,6 +519,25 @@ fn uuid() { let nop: Option = None; } +fn datetime() { + use std::ops::Add; + use std::time::{Duration, Instant, SystemTime}; + + let system_time = SystemTime::UNIX_EPOCH; + let instant = Instant::now().add(Duration::from_secs(10)); + + let nop: Option = None; +} + +fn thread_local_const_init() { + thread_local! { + static CONSTANT_THREAD_LOCAL: i32 = const { 1337 } + } + CONSTANT_THREAD_LOCAL.with(|ctx| println!("const tls: {ctx}")); + + let nop: Option = None; +} + pub fn main() { scalar_types(); compound_types(); @@ -550,4 +569,6 @@ pub fn main() { inner_static(); shadowing(); uuid(); + datetime(); + thread_local_const_init(); } diff --git a/src/debugger/debugee/dwarf/eval.rs b/src/debugger/debugee/dwarf/eval.rs index 234f45f..e73a5bb 100644 --- a/src/debugger/debugee/dwarf/eval.rs +++ b/src/debugger/debugee/dwarf/eval.rs @@ -11,6 +11,7 @@ use crate::debugger::error::Error::{ }; use crate::debugger::register::{DwarfRegisterMap, RegisterMap}; use crate::debugger::{debugee, ExplorationContext}; +use crate::version::Version; use bytes::{BufMut, Bytes, BytesMut}; use gimli::{ DebugAddr, Encoding, EndianSlice, EvaluationResult, Expression, Location, Piece, Register, @@ -24,8 +25,20 @@ use std::collections::hash_map::Entry; use std::collections::HashMap; use std::mem; +pub struct EvaluationContext<'a> { + pub evaluator: &'a ExpressionEvaluator<'a>, + pub expl_ctx: &'a ExplorationContext, +} + +impl<'a> EvaluationContext<'a> { + pub fn rustc_version(&self) -> Option { + self.evaluator.unit().rustc_version() + } +} + /// Resolve requirements that the `ExpressionEvaluator` may need. Relevant for the current breakpoint. /// Some options are lazy to avoid overhead on recalculation. +#[derive(Clone)] struct RequirementsResolver<'a> { debugee: &'a Debugee, cfa: RefCell>, @@ -164,6 +177,7 @@ impl ExternalRequirementsResolver { } } +#[derive(Clone)] pub struct ExpressionEvaluator<'a> { encoding: Encoding, unit: &'a Unit, diff --git a/src/debugger/debugee/dwarf/mod.rs b/src/debugger/debugee/dwarf/mod.rs index 3036e83..80d1921 100644 --- a/src/debugger/debugee/dwarf/mod.rs +++ b/src/debugger/debugee/dwarf/mod.rs @@ -11,9 +11,9 @@ pub use self::unwind::DwarfUnwinder; use crate::debugger::address::{GlobalAddress, RelocatedAddress}; use crate::debugger::debugee::dwarf::eval::AddressKind; +use crate::debugger::debugee::dwarf::eval::EvaluationContext; use crate::debugger::debugee::dwarf::location::Location as DwarfLocation; use crate::debugger::debugee::dwarf::r#type::ComplexType; -use crate::debugger::debugee::dwarf::r#type::EvaluationContext; use crate::debugger::debugee::dwarf::symbol::SymbolTab; use crate::debugger::debugee::dwarf::unit::{ DieRef, DieVariant, DwarfUnitParser, Entry, FunctionDie, Node, ParameterDie, @@ -26,7 +26,7 @@ use crate::debugger::error::Error::{ DebugIDFormat, FBANotAnExpression, FunctionNotFound, NoFBA, NoFunctionRanges, UnitNotFound, }; use crate::debugger::register::{DwarfRegisterMap, RegisterMap}; -use crate::debugger::variable::select::ObjectBinaryRepr; +use crate::debugger::variable::ObjectBinaryRepr; use crate::debugger::ExplorationContext; use crate::{muted_error, resolve_unit_call, version_switch, weak_error}; use fallible_iterator::FallibleIterator; @@ -512,7 +512,7 @@ impl DebugInformation { &self, location: Location, name: &str, - ) -> Result>, Error> { + ) -> Result>, Error> { let units = self.get_units()?; let mut found = vec![]; @@ -884,7 +884,7 @@ impl AsAllocatedData for ParameterDie { } } -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct NamespaceHierarchy(Vec); impl Deref for NamespaceHierarchy { @@ -944,11 +944,11 @@ impl NamespaceHierarchy { } } -pub struct ContextualDieRef<'a, T> { - pub debug_info: &'a DebugInformation, +pub struct ContextualDieRef<'node, 'dbg: 'node, T> { + pub debug_info: &'dbg DebugInformation, pub unit_idx: usize, - pub node: &'a Node, - pub die: &'a T, + pub node: &'node Node, + pub die: &'node T, } #[macro_export] @@ -958,26 +958,26 @@ macro_rules! ctx_resolve_unit_call { }}; } -impl<'a, T> Clone for ContextualDieRef<'a, T> { +impl<'node, 'dbg, T> Clone for ContextualDieRef<'node, 'dbg, T> { fn clone(&self) -> Self { *self } } -impl<'a, T> Copy for ContextualDieRef<'a, T> {} +impl<'node, 'dbg, T> Copy for ContextualDieRef<'node, 'dbg, T> {} -impl<'a, T> ContextualDieRef<'a, T> { +impl<'node, 'dbg, T> ContextualDieRef<'node, 'dbg, T> { pub fn namespaces(&self) -> NamespaceHierarchy { let entries = ctx_resolve_unit_call!(self, entries,); NamespaceHierarchy::for_node(self.node, entries) } - pub fn unit(&self) -> &'a Unit { + pub fn unit(&self) -> &'dbg Unit { self.debug_info.unit_ensure(self.unit_idx) } } -impl<'ctx> ContextualDieRef<'ctx, FunctionDie> { +impl<'ctx> ContextualDieRef<'ctx, 'ctx, FunctionDie> { pub fn full_name(&self) -> Option { self.die .base_attributes @@ -1007,7 +1007,7 @@ impl<'ctx> ContextualDieRef<'ctx, FunctionDie> { pub fn local_variables<'this>( &'this self, pc: GlobalAddress, - ) -> Vec> { + ) -> Vec> { let mut result = vec![]; let mut queue = VecDeque::from(self.node.children.clone()); while let Some(idx) = queue.pop_front() { @@ -1033,7 +1033,7 @@ impl<'ctx> ContextualDieRef<'ctx, FunctionDie> { &'this self, pc: GlobalAddress, needle: &str, - ) -> Option> { + ) -> Option> { let mut queue = VecDeque::from(self.node.children.clone()); while let Some(idx) = queue.pop_front() { let entry = ctx_resolve_unit_call!(self, entry, idx); @@ -1054,7 +1054,7 @@ impl<'ctx> ContextualDieRef<'ctx, FunctionDie> { None } - pub fn parameters(&self) -> Vec> { + pub fn parameters(&self) -> Vec> { let mut result = vec![]; for &idx in &self.node.children { let entry = ctx_resolve_unit_call!(self, entry, idx); @@ -1139,7 +1139,7 @@ impl<'ctx> ContextualDieRef<'ctx, FunctionDie> { } } -impl<'ctx> ContextualDieRef<'ctx, VariableDie> { +impl<'ctx> ContextualDieRef<'ctx, 'ctx, VariableDie> { pub fn ranges(&self) -> Option<&[Range]> { if let Some(lb_idx) = self.die.lexical_block_idx { let entry = ctx_resolve_unit_call!(self, entry, lb_idx); @@ -1160,7 +1160,7 @@ impl<'ctx> ContextualDieRef<'ctx, VariableDie> { .unwrap_or(true) } - pub fn assume_parent_function(&self) -> Option> { + pub fn assume_parent_function(&self) -> Option> { let mut mb_parent = self.node.parent; while let Some(p) = mb_parent { @@ -1181,7 +1181,7 @@ impl<'ctx> ContextualDieRef<'ctx, VariableDie> { } } -impl<'ctx> ContextualDieRef<'ctx, ParameterDie> { +impl<'ctx> ContextualDieRef<'ctx, 'ctx, ParameterDie> { /// Return max range (with max `end` address) of an underlying function. /// If it's possible, `end` address in range equals to function epilog begin. pub fn max_range(&self) -> Option { @@ -1205,7 +1205,7 @@ impl<'ctx> ContextualDieRef<'ctx, ParameterDie> { } } -impl<'ctx, D: AsAllocatedData> ContextualDieRef<'ctx, D> { +impl<'ctx, D: AsAllocatedData> ContextualDieRef<'ctx, 'ctx, D> { pub fn r#type(&self) -> Option { let parser = r#type::TypeParser::new(); Some(parser.parse(*self, self.die.type_ref()?)) @@ -1227,7 +1227,7 @@ impl<'ctx, D: AsAllocatedData> ContextualDieRef<'ctx, D> { evaluator: &evaluator, expl_ctx: ctx, }, - r#type.root, + r#type.root(), )? as usize; let (address, raw_data) = weak_error!(eval_result.into_raw_bytes(type_size, AddressKind::MemoryAddress))?; diff --git a/src/debugger/debugee/dwarf/type.rs b/src/debugger/debugee/dwarf/type.rs index d339409..497507c 100644 --- a/src/debugger/debugee/dwarf/type.rs +++ b/src/debugger/debugee/dwarf/type.rs @@ -1,4 +1,4 @@ -use crate::debugger::debugee::dwarf::eval::{AddressKind, ExpressionEvaluator}; +use crate::debugger::debugee::dwarf::eval::{AddressKind, EvaluationContext}; use crate::debugger::debugee::dwarf::unit::{ ArrayDie, AtomicDie, BaseTypeDie, ConstTypeDie, DieRef, DieVariant, EnumTypeDie, PointerType, RestrictDie, StructTypeDie, SubroutineDie, TypeDefDie, TypeMemberDie, UnionTypeDie, @@ -6,9 +6,7 @@ use crate::debugger::debugee::dwarf::unit::{ }; use crate::debugger::debugee::dwarf::{eval, ContextualDieRef, EndianArcSlice, NamespaceHierarchy}; use crate::debugger::error::Error; -use crate::debugger::variable::select::ObjectBinaryRepr; -use crate::debugger::ExplorationContext; -use crate::version::Version; +use crate::debugger::variable::ObjectBinaryRepr; use crate::{ctx_resolve_unit_call, weak_error}; use bytes::Bytes; use gimli::{AttributeValue, DwAte, Expression}; @@ -16,24 +14,87 @@ use log::warn; use std::cell::Cell; use std::collections::{HashMap, HashSet, VecDeque}; use std::mem; +use std::rc::Rc; use strum_macros::Display; use uuid::Uuid; /// Type identifier. -pub type TypeIdentity = DieRef; +pub type TypeId = DieRef; -pub struct EvaluationContext<'a> { - pub evaluator: &'a ExpressionEvaluator<'a>, - pub expl_ctx: &'a ExplorationContext, +/// Type name with namespace. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] +pub struct TypeIdentity { + namespace: NamespaceHierarchy, + name: Option, } -impl<'a> EvaluationContext<'a> { - pub fn rustc_version(&self) -> Option { - self.evaluator.unit().rustc_version() +impl TypeIdentity { + /// Create type identity with empty namespace. + #[inline(always)] + pub fn no_namespace(name: impl ToString) -> Self { + Self { + namespace: Default::default(), + name: Some(name.to_string()), + } + } + + /// Create type identity for an unknown type. + #[inline(always)] + pub fn unknown() -> Self { + Self { + namespace: Default::default(), + name: None, + } + } + + /// True whether a type is unknown. + #[inline(always)] + pub fn is_unknown(&self) -> bool { + self.name.is_none() + } + + #[inline(always)] + pub fn namespace(&self) -> &NamespaceHierarchy { + &self.namespace + } + + #[inline(always)] + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + /// Return formatted type name. + #[inline(always)] + pub fn name_fmt(&self) -> &str { + self.name().unwrap_or("unknown") + } + + /// Create address type name. + #[inline(always)] + pub fn as_address_type(&self) -> TypeIdentity { + TypeIdentity { + namespace: self.namespace.clone(), + name: Some(format!("&{}", self.name_fmt())), + } + } + + /// Create dereferenced type name. + #[inline(always)] + pub fn as_deref_type(&self) -> TypeIdentity { + TypeIdentity { + namespace: self.namespace.clone(), + name: Some(format!("*{}", self.name_fmt())), + } + } + + /// Create array type name from element type name. + #[inline(always)] + pub fn as_array_type(&self) -> TypeIdentity { + TypeIdentity::no_namespace(format!("[{}]", self.name_fmt())) } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct MemberLocationExpression { expr: Expression, } @@ -52,17 +113,17 @@ impl MemberLocationExpression { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum MemberLocation { Offset(i64), Expr(MemberLocationExpression), } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct StructureMember { pub in_struct_location: Option, pub name: Option, - pub type_ref: Option, + pub type_ref: Option, } impl StructureMember { @@ -106,7 +167,7 @@ impl StructureMember { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ArrayBoundValueExpression { expr: Expression, } @@ -120,7 +181,7 @@ impl ArrayBoundValueExpression { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum ArrayBoundValue { Const(i64), Expr(ArrayBoundValueExpression), @@ -135,17 +196,17 @@ impl ArrayBoundValue { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum UpperBound { UpperBound(ArrayBoundValue), Count(ArrayBoundValue), } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ArrayType { pub namespaces: NamespaceHierarchy, byte_size: Option, - pub element_type: Option, + pub element_type: Option, lower_bound: ArrayBoundValue, upper_bound: Option, byte_size_memo: Cell>, @@ -156,7 +217,7 @@ impl ArrayType { pub fn new( namespaces: NamespaceHierarchy, byte_size: Option, - element_type: Option, + element_type: Option, lower_bound: ArrayBoundValue, upper_bound: Option, ) -> Self { @@ -207,7 +268,7 @@ impl ArrayType { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ScalarType { pub namespaces: NamespaceHierarchy, pub name: Option, @@ -215,8 +276,17 @@ pub struct ScalarType { pub encoding: Option, } +impl ScalarType { + pub fn identity(&self) -> TypeIdentity { + TypeIdentity { + namespace: self.namespaces.clone(), + name: self.name.clone(), + } + } +} + /// List of type modifiers -#[derive(Display, Clone, Copy, PartialEq)] +#[derive(Display, Clone, Copy, PartialEq, Debug)] #[strum(serialize_all = "snake_case")] pub enum CModifier { TypeDef, @@ -226,7 +296,7 @@ pub enum CModifier { Restrict, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum TypeDeclaration { Scalar(ScalarType), Array(ArrayType), @@ -234,20 +304,20 @@ pub enum TypeDeclaration { namespaces: NamespaceHierarchy, name: Option, byte_size: Option, - discr_type: Option, + discr_type: Option, enumerators: HashMap, }, Pointer { namespaces: NamespaceHierarchy, name: Option, - target_type: Option, + target_type: Option, }, Structure { namespaces: NamespaceHierarchy, name: Option, byte_size: Option, members: Vec, - type_params: HashMap>, + type_params: HashMap>, }, Union { namespaces: NamespaceHierarchy, @@ -266,63 +336,119 @@ pub enum TypeDeclaration { Subroutine { namespaces: NamespaceHierarchy, name: Option, - return_type: Option, + return_type: Option, }, ModifiedType { modifier: CModifier, namespaces: NamespaceHierarchy, name: Option, - inner: Option, + inner: Option, }, } /// Type representation. This is a graph of types where vertexes is a type declaration and edges -/// is a dependencies between types. Type linking implemented by `TypeIdentity` references. -/// Root is an identity of a main type. -#[derive(Clone)] +/// is a dependencies between types. Type linking implemented by `TypeId` references. +/// Root is id of a main type. +#[derive(Clone, Debug)] pub struct ComplexType { - pub types: HashMap, - pub root: TypeIdentity, + pub types: HashMap, + root: TypeId, } impl ComplexType { - /// Returns name of some of type existed in a complex type. - pub fn type_name(&self, typ: TypeIdentity) -> Option { - match &self.types.get(&typ)? { - TypeDeclaration::Scalar(s) => s.name.clone(), - TypeDeclaration::Structure { name, .. } => name.clone(), - TypeDeclaration::Array(arr) => Some(format!( - "[{}]", - arr.element_type - .and_then(|t| self.type_name(t)) - .unwrap_or("unknown".to_string()) - )), - TypeDeclaration::CStyleEnum { name, .. } => name.clone(), - TypeDeclaration::RustEnum { name, .. } => name.clone(), - TypeDeclaration::Pointer { name, .. } => name.clone(), - TypeDeclaration::Union { name, .. } => name.clone(), - TypeDeclaration::Subroutine { name, .. } => name.clone(), + /// Return root type id. + #[inline(always)] + pub fn root(&self) -> TypeId { + self.root + } + + /// Return name of some of a type existed in a complex type. + pub fn identity(&self, typ: TypeId) -> TypeIdentity { + let Some(r#type) = self.types.get(&typ) else { + return TypeIdentity::unknown(); + }; + + match r#type { + TypeDeclaration::Scalar(s) => s.identity(), + TypeDeclaration::Structure { + name, namespaces, .. + } => TypeIdentity { + namespace: namespaces.clone(), + name: name.as_ref().cloned(), + }, + TypeDeclaration::Array(arr) => { + let el_ident = arr.element_type.map(|t| self.identity(t)); + let name = format!( + "[{}]", + el_ident + .as_ref() + .map(|ident| ident.name_fmt()) + .unwrap_or("unknown") + ); + + TypeIdentity { + namespace: arr.namespaces.clone(), + name: Some(name.to_string()), + } + } + TypeDeclaration::CStyleEnum { + name, namespaces, .. + } => TypeIdentity { + namespace: namespaces.clone(), + name: name.as_ref().cloned(), + }, + TypeDeclaration::RustEnum { + name, namespaces, .. + } => TypeIdentity { + namespace: namespaces.clone(), + name: name.as_ref().cloned(), + }, + TypeDeclaration::Pointer { + name, namespaces, .. + } => TypeIdentity { + namespace: namespaces.clone(), + name: name.as_ref().cloned(), + }, + TypeDeclaration::Union { + name, namespaces, .. + } => TypeIdentity { + namespace: namespaces.clone(), + name: name.as_ref().cloned(), + }, + TypeDeclaration::Subroutine { + name, namespaces, .. + } => TypeIdentity { + namespace: namespaces.clone(), + name: name.as_ref().cloned(), + }, TypeDeclaration::ModifiedType { modifier, + namespaces, name, inner, .. } => match name { - None => inner.and_then(|inner_id| { - self.type_name(inner_id) - .map(|name| format!("{modifier} {name}")) - }), - Some(n) => Some(n.clone()), + None => { + let name = inner.map(|inner_id| { + let ident = self.identity(inner_id); + format!("{modifier} {name}", name = ident.name_fmt()) + }); + + TypeIdentity { + namespace: namespaces.clone(), + name, + } + } + Some(n) => TypeIdentity { + namespace: namespaces.clone(), + name: Some(n.to_string()), + }, }, } } - /// Returns size of some of type existed in a complex type. - pub fn type_size_in_bytes( - &self, - eval_ctx: &EvaluationContext, - typ: TypeIdentity, - ) -> Option { + /// Return size of a type existed from a complex type. + pub fn type_size_in_bytes(&self, eval_ctx: &EvaluationContext, typ: TypeId) -> Option { match &self.types.get(&typ)? { TypeDeclaration::Scalar(s) => s.byte_size, TypeDeclaration::Structure { byte_size, .. } => *byte_size, @@ -339,7 +465,7 @@ impl ComplexType { } /// Visit type children in bfs order, `start_at` - identity of root type. - pub fn bfs_iterator(&self, start_at: TypeIdentity) -> BfsIterator { + pub fn bfs_iterator(&self, start_at: TypeId) -> BfsIterator { BfsIterator { complex_type: self, queue: VecDeque::from([start_at]), @@ -351,7 +477,7 @@ impl ComplexType { /// Note that this iterator may be infinite. pub struct BfsIterator<'a> { complex_type: &'a ComplexType, - queue: VecDeque, + queue: VecDeque, } impl<'a> Iterator for BfsIterator<'a> { @@ -428,8 +554,8 @@ impl<'a> Iterator for BfsIterator<'a> { /// Dwarf DIE parser. pub struct TypeParser { - known_type_ids: HashSet, - processed_types: HashMap, + known_type_ids: HashSet, + processed_types: HashMap, } impl TypeParser { @@ -442,7 +568,11 @@ impl TypeParser { } /// Parse a `ComplexType` from a DIEs. - pub fn parse(self, ctx_die: ContextualDieRef<'_, T>, root_id: TypeIdentity) -> ComplexType { + pub fn parse<'dbg, T>( + self, + ctx_die: ContextualDieRef<'dbg, 'dbg, T>, + root_id: TypeId, + ) -> ComplexType { let mut this = self; this.parse_inner(ctx_die, root_id); ComplexType { @@ -451,7 +581,7 @@ impl TypeParser { } } - fn parse_inner(&mut self, ctx_die: ContextualDieRef<'_, T>, type_ref: DieRef) { + fn parse_inner(&mut self, ctx_die: ContextualDieRef<'_, '_, T>, type_ref: DieRef) { // guard from recursion types parsing if self.known_type_ids.contains(&type_ref) { return; @@ -543,7 +673,10 @@ impl TypeParser { } } - fn parse_base_type(&mut self, ctx_die: ContextualDieRef<'_, BaseTypeDie>) -> TypeDeclaration { + fn parse_base_type( + &mut self, + ctx_die: ContextualDieRef<'_, '_, BaseTypeDie>, + ) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); TypeDeclaration::Scalar(ScalarType { namespaces: ctx_die.namespaces(), @@ -553,7 +686,10 @@ impl TypeParser { }) } - fn parse_array(&mut self, ctx_die: ContextualDieRef<'_, ArrayDie>) -> TypeDeclaration { + fn parse_array<'dbg>( + &mut self, + ctx_die: ContextualDieRef<'dbg, 'dbg, ArrayDie>, + ) -> TypeDeclaration { let mb_type_ref = ctx_die.die.type_ref; if let Some(reference) = mb_type_ref { self.parse_inner(ctx_die, reference); @@ -621,7 +757,10 @@ impl TypeParser { /// Convert DW_TAG_structure_type into TypeDeclaration. /// In rust DW_TAG_structure_type DIE can be interpreter as enum, see https://github.com/rust-lang/rust/issues/32920 - fn parse_struct(&mut self, ctx_die: ContextualDieRef<'_, StructTypeDie>) -> TypeDeclaration { + fn parse_struct<'dbg>( + &mut self, + ctx_die: ContextualDieRef<'dbg, 'dbg, StructTypeDie>, + ) -> TypeDeclaration { let is_enum = ctx_die.node.children.iter().any(|c_idx| { let entry = ctx_resolve_unit_call!(ctx_die, entry, *c_idx); matches!(entry.die, DieVariant::VariantPart(_)) @@ -636,7 +775,7 @@ impl TypeParser { fn parse_struct_struct( &mut self, - ctx_die: ContextualDieRef<'_, StructTypeDie>, + ctx_die: ContextualDieRef<'_, '_, StructTypeDie>, ) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); let members = ctx_die @@ -681,7 +820,10 @@ impl TypeParser { } } - fn parse_member(&mut self, ctx_die: ContextualDieRef<'_, TypeMemberDie>) -> StructureMember { + fn parse_member<'dbg>( + &mut self, + ctx_die: ContextualDieRef<'dbg, 'dbg, TypeMemberDie>, + ) -> StructureMember { let loc = ctx_die.die.location.as_ref().map(|attr| attr.value()); let in_struct_location = if let Some(offset) = loc.as_ref().and_then(|l| l.sdata_value()) { Some(MemberLocation::Offset(offset)) @@ -707,7 +849,7 @@ impl TypeParser { fn parse_struct_enum( &mut self, - ctx_die: ContextualDieRef<'_, StructTypeDie>, + ctx_die: ContextualDieRef<'_, '_, StructTypeDie>, ) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); @@ -785,7 +927,10 @@ impl TypeParser { } } - fn parse_enum(&mut self, ctx_die: ContextualDieRef<'_, EnumTypeDie>) -> TypeDeclaration { + fn parse_enum<'dbg>( + &mut self, + ctx_die: ContextualDieRef<'dbg, 'dbg, EnumTypeDie>, + ) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); let mb_discr_type = ctx_die.die.type_ref; @@ -819,7 +964,7 @@ impl TypeParser { } } - fn parse_union(&mut self, ctx_die: ContextualDieRef<'_, UnionTypeDie>) -> TypeDeclaration { + fn parse_union(&mut self, ctx_die: ContextualDieRef<'_, '_, UnionTypeDie>) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); let members = ctx_die .node @@ -847,7 +992,10 @@ impl TypeParser { } } - fn parse_pointer(&mut self, ctx_die: ContextualDieRef<'_, PointerType>) -> TypeDeclaration { + fn parse_pointer<'dbg>( + &mut self, + ctx_die: ContextualDieRef<'dbg, 'dbg, PointerType>, + ) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); let mb_type_ref = ctx_die.die.type_ref; @@ -862,9 +1010,9 @@ impl TypeParser { } } - fn parse_subroutine( + fn parse_subroutine<'dbg>( &mut self, - ctx_die: ContextualDieRef<'_, SubroutineDie>, + ctx_die: ContextualDieRef<'dbg, 'dbg, SubroutineDie>, ) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); let mb_ret_type_ref = ctx_die.die.return_type_ref; @@ -882,7 +1030,10 @@ impl TypeParser { macro_rules! parse_modifier_fn { ($fn_name: ident, $die: ty, $modifier: expr) => { - fn $fn_name(&mut self, ctx_die: ContextualDieRef<'_, $die>) -> TypeDeclaration { + fn $fn_name<'dbg>( + &mut self, + ctx_die: ContextualDieRef<'dbg, 'dbg, $die>, + ) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); let mb_type_ref = ctx_die.die.type_ref; if let Some(inner_type) = mb_type_ref { @@ -909,5 +1060,5 @@ impl TypeParser { } /// A cache structure for types. -/// Every type identifies by its `TypeIdentity` and dwarf unit uuid. -pub type TypeCache = HashMap<(Uuid, TypeIdentity), ComplexType>; +/// Every type identified by its `TypeId` and DWARF unit uuid. +pub type TypeCache = HashMap<(Uuid, TypeId), Rc>; diff --git a/src/debugger/debugee/dwarf/unit/mod.rs b/src/debugger/debugee/dwarf/unit/mod.rs index 01ede44..5d9c605 100644 --- a/src/debugger/debugee/dwarf/unit/mod.rs +++ b/src/debugger/debugee/dwarf/unit/mod.rs @@ -405,7 +405,7 @@ pub struct Node { } impl Node { - pub fn new_leaf(parent: Option) -> Node { + pub const fn new_leaf(parent: Option) -> Node { Self { parent, children: vec![], @@ -460,13 +460,13 @@ struct UnitProperties { struct UnitLazyPart { entries: Vec, die_ranges: Vec, - // index for variable die position: { variable name -> [namespaces : die position in unit] } + /// index for variable die position: { variable name -> [namespaces: die position in unit] } variable_index: HashMap>, - // index for type die position: { type name -> offset in unit } + /// index for type die position: { type name -> offset in unit } type_index: HashMap, - // index for variables: offset in unit -> position in unit `entries` + /// index for variables: offset in unit -> position in unit `entries` die_offsets_index: HashMap, - // index for function entries: function -> die position in unit `entries` + /// index for function entries: function -> die position in unit `entries` function_index: PathSearchIndex, } @@ -786,13 +786,12 @@ impl Unit { /// # Arguments /// /// * `name`: needle variable name - pub fn locate_var_die( - &self, - name: &str, - ) -> UnitResult>> { + pub fn locate_var_die(&self, name: &str) -> UnitResult> { match self.lazy_part.get() { None => UnitResult::Reload, - Some(additional) => UnitResult::Ok(additional.variable_index.get(name)), + Some(additional) => { + UnitResult::Ok(additional.variable_index.get(name).map(|v| v.as_slice())) + } } } diff --git a/src/debugger/debugee/mod.rs b/src/debugger/debugee/mod.rs index 0181177..286cc83 100644 --- a/src/debugger/debugee/mod.rs +++ b/src/debugger/debugee/mod.rs @@ -411,12 +411,12 @@ impl Debugee { /// /// # Panics /// - /// This method panics if thread with pid `pid` not run + /// This method panics if thread with pid `pid` not runs. pub fn get_tracee_ensure(&self, pid: Pid) -> &Tracee { self.tracee_ctl().tracee_ensure(pid) } - /// Return tracee by it's number. + /// Return tracee by its number. /// /// # Arguments /// @@ -507,7 +507,7 @@ impl Debugee { unwind::restore_registers_at_frame(self, pid, registers, frame_num) } - /// Return return address for thread current program counter. + /// Return a current frame return address for current thread. /// /// # Arguments /// diff --git a/src/debugger/error.rs b/src/debugger/error.rs index 1e4f2db..29036d8 100644 --- a/src/debugger/error.rs +++ b/src/debugger/error.rs @@ -1,7 +1,7 @@ use crate::debugger::address::GlobalAddress; use crate::debugger::debugee::dwarf::unit::DieRef; use crate::debugger::debugee::RendezvousError; -use crate::debugger::variable::ParsingError; +use crate::debugger::variable::value::ParsingError; use gimli::UnitOffset; use nix::unistd::Pid; use std::str::Utf8Error; diff --git a/src/debugger/mod.rs b/src/debugger/mod.rs index 62a03eb..41a43cf 100644 --- a/src/debugger/mod.rs +++ b/src/debugger/mod.rs @@ -44,8 +44,9 @@ use crate::debugger::process::{Child, Installed}; use crate::debugger::register::debug::BreakCondition; use crate::debugger::register::{DwarfRegisterMap, Register, RegisterMap}; use crate::debugger::step::StepResult; -use crate::debugger::variable::select::{VariableSelector, DQE}; -use crate::debugger::variable::VariableIR; +use crate::debugger::variable::dqe::{Dqe, Selector}; +use crate::debugger::variable::execute::QueryResult; +use crate::debugger::variable::value::Value; use crate::debugger::watchpoint::WatchpointRegistry; use crate::debugger::Error::Syscall; use crate::oracle::Oracle; @@ -93,6 +94,7 @@ pub trait EventHook { /// * `num`: breakpoint number /// * `place`: breakpoint number /// * `condition`: reason of a watchpoint activation + /// * `dqe_string`: stringifed data query expression (if exist) /// * `old_value`: previous expression or mem location value /// * `new_value`: current expression or mem location value /// * `end_of_scope`: true if watchpoint activated cause end of scope is reached @@ -103,8 +105,9 @@ pub trait EventHook { num: u32, place: Option, condition: BreakCondition, - old_value: Option<&VariableIR>, - new_value: Option<&VariableIR>, + dqe_string: Option<&str>, + old_value: Option<&Value>, + new_value: Option<&Value>, end_of_scope: bool, ) -> anyhow::Result<()>; @@ -163,8 +166,9 @@ impl EventHook for NopHook { _: u32, _: Option, _: BreakCondition, - _: Option<&VariableIR>, - _: Option<&VariableIR>, + _: Option<&str>, + _: Option<&Value>, + _: Option<&Value>, _: bool, ) -> anyhow::Result<()> { Ok(()) @@ -517,8 +521,8 @@ impl Debugger { let oracles = self.oracles.clone(); let ready_oracles = oracles.into_values().filter(|(_, a)| *a); for (oracle, _) in ready_oracles { - let watch_points = oracle.watch_points(); - for request in watch_points { + let spy_points = oracle.spy_points(); + for request in spy_points { weak_error!(self.set_transparent_breakpoint(request)); } } @@ -872,15 +876,12 @@ impl Debugger { } /// Reads all local variables from current function in current thread. - pub fn read_local_variables(&self) -> Result, Error> { + pub fn read_local_variables(&self) -> Result, Error> { disable_when_not_stared!(self); - let evaluator = variable::select::SelectExpressionEvaluator::new( - self, - DQE::Variable(VariableSelector::Any), - ); - let eval_result = evaluator.evaluate()?; - Ok(eval_result.into_iter().map(|res| res.variable).collect()) + let executor = variable::execute::DqeExecutor::new(self); + let eval_result = executor.query(&Dqe::Variable(Selector::Any))?; + Ok(eval_result) } /// Reads any variable from the current thread, uses a select expression to filter variables @@ -889,11 +890,11 @@ impl Debugger { /// # Arguments /// /// * `select_expr`: data query expression - pub fn read_variable(&self, select_expr: DQE) -> Result, Error> { + pub fn read_variable(&self, select_expr: Dqe) -> Result, Error> { disable_when_not_stared!(self); - let evaluator = variable::select::SelectExpressionEvaluator::new(self, select_expr); - let eval_result = evaluator.evaluate()?; - Ok(eval_result.into_iter().map(|res| res.variable).collect()) + let executor = variable::execute::DqeExecutor::new(self); + let eval_result = executor.query(&select_expr)?; + Ok(eval_result) } /// Reads any variable from the current thread, uses a select expression to filter variables @@ -902,10 +903,10 @@ impl Debugger { /// # Arguments /// /// * `select_expr`: data query expression - pub fn read_variable_names(&self, select_expr: DQE) -> Result, Error> { + pub fn read_variable_names(&self, select_expr: Dqe) -> Result, Error> { disable_when_not_stared!(self); - let evaluator = variable::select::SelectExpressionEvaluator::new(self, select_expr); - evaluator.evaluate_names() + let executor = variable::execute::DqeExecutor::new(self); + executor.query_names(&select_expr) } /// Reads any argument from the current function, uses a select expression to filter variables @@ -914,11 +915,11 @@ impl Debugger { /// # Arguments /// /// * `select_expr`: data query expression - pub fn read_argument(&self, select_expr: DQE) -> Result, Error> { + pub fn read_argument(&self, select_expr: Dqe) -> Result, Error> { disable_when_not_stared!(self); - let evaluator = variable::select::SelectExpressionEvaluator::new(self, select_expr); - let eval_result = evaluator.evaluate_on_arguments()?; - Ok(eval_result.into_iter().map(|res| res.variable).collect()) + let executor = variable::execute::DqeExecutor::new(self); + let eval_result = executor.query_arguments(&select_expr)?; + Ok(eval_result) } /// Reads any argument from the current function, uses a select expression to filter arguments @@ -927,10 +928,10 @@ impl Debugger { /// # Arguments /// /// * `select_expr`: data query expression - pub fn read_argument_names(&self, select_expr: DQE) -> Result, Error> { + pub fn read_argument_names(&self, select_expr: Dqe) -> Result, Error> { disable_when_not_stared!(self); - let evaluator = variable::select::SelectExpressionEvaluator::new(self, select_expr); - evaluator.evaluate_on_arguments_names() + let executor = variable::execute::DqeExecutor::new(self); + executor.query_arguments_names(&select_expr) } /// Return following register value. diff --git a/src/debugger/variable/dqe.rs b/src/debugger/variable/dqe.rs new file mode 100644 index 0000000..d5563cc --- /dev/null +++ b/src/debugger/variable/dqe.rs @@ -0,0 +1,226 @@ +use itertools::Itertools; +use std::collections::HashMap; +use std::fmt::{Debug, Display, Formatter}; + +/// Literal object. +/// Using it for a searching element by key in key-value containers. +#[derive(PartialEq, Clone)] +pub enum Literal { + String(String), + Int(i64), + Float(f64), + Address(usize), + Bool(bool), + EnumVariant(String, Option>), + Array(Box<[LiteralOrWildcard]>), + AssocArray(HashMap), +} + +impl Display for Literal { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Literal::String(str) => f.write_fmt(format_args!("\"{str}\"")), + Literal::Int(i) => f.write_str(&i.to_string()), + Literal::Float(float) => f.write_str(&float.to_string()), + Literal::Address(addr) => f.write_fmt(format_args!("{addr:#016X}")), + Literal::Bool(b) => f.write_fmt(format_args!("{b}")), + Literal::EnumVariant(variant, data) => { + if let Some(data) = data { + f.write_fmt(format_args!("{variant}({data})")) + } else { + f.write_fmt(format_args!("{variant}")) + } + } + Literal::Array(array) => { + let body = array + .iter() + .map(|item| match item { + LiteralOrWildcard::Literal(lit) => lit.to_string(), + LiteralOrWildcard::Wildcard => "*".to_string(), + }) + .join(", "); + f.write_fmt(format_args!("{{ {body} }}")) + } + Literal::AssocArray(assoc_array) => { + let body = assoc_array + .iter() + .map(|(key, value)| { + let value_string = match value { + LiteralOrWildcard::Literal(lit) => lit.to_string(), + LiteralOrWildcard::Wildcard => "*".to_string(), + }; + format!("\"{key}\": {value_string}") + }) + .join(", "); + + f.write_fmt(format_args!("{{ {body} }}")) + } + } + } +} + +impl Debug for Literal { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.to_string()) + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum LiteralOrWildcard { + Literal(Literal), + Wildcard, +} + +macro_rules! impl_equal { + ($lhs: expr, $rhs: expr, $lit: path) => { + if let $lit(lhs) = $lhs { + lhs == &$rhs + } else { + false + } + }; +} + +impl Literal { + pub fn equal_with_string(&self, rhs: &str) -> bool { + impl_equal!(self, rhs, Literal::String) + } + + pub fn equal_with_address(&self, rhs: usize) -> bool { + impl_equal!(self, rhs, Literal::Address) + } + + pub fn equal_with_bool(&self, rhs: bool) -> bool { + impl_equal!(self, rhs, Literal::Bool) + } + + pub fn equal_with_int(&self, rhs: i64) -> bool { + impl_equal!(self, rhs, Literal::Int) + } + + pub fn equal_with_float(&self, rhs: f64) -> bool { + const EPS: f64 = 0.0000001f64; + if let Literal::Float(float) = self { + let diff = (*float - rhs).abs(); + diff < EPS + } else { + false + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum Selector { + Name { var_name: String, local_only: bool }, + Any, +} + +impl Selector { + pub fn by_name(name: impl ToString, local_only: bool) -> Self { + Self::Name { + var_name: name.to_string(), + local_only, + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub struct PointerCast { + pub ptr: usize, + pub ty: String, +} + +impl PointerCast { + pub fn new(ptr: usize, ty: impl ToString) -> Self { + Self { + ptr, + ty: ty.to_string(), + } + } +} + +/// Data query expression. +/// List of operations for select variables and their properties. +/// +/// Expression can be parsed from an input string like `*(*variable1.field2)[1]` +/// (see [`crate::ui::command`] module) +/// +/// Supported operations are: dereference, get an element by index, get field by name, make slice from a pointer. +#[derive(Debug, PartialEq, Clone)] +pub enum Dqe { + /// Select variables or arguments from debugee state. + Variable(Selector), + /// Cast raw memory address to a typed pointer. + PtrCast(PointerCast), + /// Get structure field (or similar, for example, values from hashmap with string keys). + Field(Box, String), + /// Get an element from array (or vector, vecdeq, etc.) by its index. + Index(Box, Literal), + /// Get array (or vector, vecdeq, etc.) slice. + Slice(Box, Option, Option), + /// Dereference pointer value. + Deref(Box), + /// Get address of value. + Address(Box), + /// Get canonic value (actual for specialized value, typically return underlying structure). + Canonic(Box), +} + +impl Dqe { + /// Return boxed expression. + pub fn boxed(self) -> Box { + Box::new(self) + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::collections::HashMap; + + #[test] + fn test_literal_display() { + struct TestCase { + literal: Literal, + expect: &'static str, + } + let test_cases = &[ + TestCase { + literal: Literal::String("abc".to_string()), + expect: "\"abc\"", + }, + TestCase { + literal: Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Wildcard, + ])), + expect: "{ 1, 1, * }", + }, + TestCase { + literal: Literal::Address(101), + expect: "0x00000000000065", + }, + TestCase { + literal: Literal::EnumVariant( + "Some".to_string(), + Some(Box::new(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Bool(true)), + ])))), + ), + expect: "Some({ true })", + }, + TestCase { + literal: Literal::AssocArray(HashMap::from([( + "__1".to_string(), + LiteralOrWildcard::Literal(Literal::String("abc".to_string())), + )])), + expect: "{ \"__1\": \"abc\" }", + }, + ]; + + for tc in test_cases { + assert_eq!(tc.literal.to_string(), tc.expect); + } + } +} diff --git a/src/debugger/variable/execute.rs b/src/debugger/variable/execute.rs new file mode 100644 index 0000000..55f390b --- /dev/null +++ b/src/debugger/variable/execute.rs @@ -0,0 +1,453 @@ +use crate::debugger::debugee::dwarf::eval::{EvaluationContext, ExpressionEvaluator}; +use crate::debugger::debugee::dwarf::r#type::ComplexType; +use crate::debugger::debugee::dwarf::unit::{ParameterDie, VariableDie}; +use crate::debugger::debugee::dwarf::{AsAllocatedData, ContextualDieRef, DebugInformation}; +use crate::debugger::error::Error; +use crate::debugger::error::Error::FunctionNotFound; +use crate::debugger::variable::dqe::{Dqe, PointerCast, Selector}; +use crate::debugger::variable::r#virtual::VirtualVariableDie; +use crate::debugger::variable::value::parser::{ParseContext, ValueModifiers, ValueParser}; +use crate::debugger::variable::value::Value; +use crate::debugger::variable::{Identity, ObjectBinaryRepr}; +use crate::debugger::Debugger; +use crate::{ctx_resolve_unit_call, weak_error}; +use bytes::Bytes; +use gimli::Range; +use std::collections::hash_map::Entry; +use std::fmt::Debug; +use std::rc::Rc; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum QueryResultKind { + /// Result value is an argument or variable + Root, + /// Result value calculated using DQE + Expression, +} + +/// Result of DQE evaluation. +#[derive(Clone)] +pub struct QueryResult<'a> { + value: Option, + scope: Option>, + kind: QueryResultKind, + base_type: Rc, + identity: Identity, + eval_ctx_builder: EvaluationContextBuilder<'a>, +} + +impl<'a> QueryResult<'a> { + /// Return underlying typed value representation. + #[inline(always)] + pub fn value(&self) -> &Value { + self.value.as_ref().expect("should be `Some`") + } + + /// Return underlying typed value representation. + #[inline(always)] + pub fn into_value(mut self) -> Value { + self.value.take().expect("should be `Some`") + } + + /// Return underlying value and result identity (variable or argument identity). + #[inline(always)] + pub fn into_identified_value(mut self) -> (Identity, Value) { + (self.identity, self.value.take().expect("should be `Some`")) + } + + /// Return result kind: + /// - `Root` kind means that value is an argument or variable + /// - `Expression` kind means that value calculated using DQE + #[inline(always)] + pub fn kind(&self) -> QueryResultKind { + self.kind + } + + /// Return type graph using for parse a result. + #[inline(always)] + pub fn type_graph(&self) -> &ComplexType { + self.base_type.as_ref() + } + + /// Return result identity. + #[inline(always)] + pub fn identity(&self) -> &Identity { + &self.identity + } + + /// Return variable or argument scope. Scope is a PC ranges where value is valid, + /// `None` for global or virtual variables. + #[inline(always)] + pub fn scope(&self) -> &Option> { + &self.scope + } + + /// Evaluate any function with evaluation context. + pub fn with_eval_ctx T>(&self, cb: F) -> T { + self.eval_ctx_builder.with_eval_ctx(cb) + } + + /// Modify the underlying value and return a new result extended from the current one. + pub fn modify_value Option>( + mut self, + cb: F, + ) -> Option { + let value = self.value.take().expect("should be `Some`"); + let type_graph = self.type_graph(); + let eval_cb = |ctx: &EvaluationContext| { + let parse_ctx = &ParseContext { + evaluation_context: ctx, + type_graph, + }; + cb(parse_ctx, value) + }; + let new_value = self.eval_ctx_builder.with_eval_ctx(eval_cb)?; + self.value = Some(new_value); + Some(self) + } +} + +impl<'a> PartialEq for QueryResult<'a> { + fn eq(&self, other: &Self) -> bool { + self.value == other.value && self.identity == other.identity + } +} + +#[derive(Clone)] +enum EvaluationContextBuilder<'a> { + Ready(&'a Debugger, ExpressionEvaluator<'a>), + Virtual { + debugger: &'a Debugger, + debug_info: &'a DebugInformation, + unit_idx: usize, + die: VirtualVariableDie, + }, +} + +impl<'a> EvaluationContextBuilder<'a> { + fn with_eval_ctx T>(&self, cb: F) -> T { + let evaluator; + let ctx = match self { + EvaluationContextBuilder::Ready(debugger, evaluator) => EvaluationContext { + evaluator, + expl_ctx: debugger.exploration_ctx(), + }, + EvaluationContextBuilder::Virtual { + debugger, + debug_info, + unit_idx, + die, + } => { + let var = ContextualDieRef { + debug_info, + unit_idx: *unit_idx, + node: VirtualVariableDie::ANY_NODE, + die, + }; + evaluator = ctx_resolve_unit_call!(var, evaluator, &debugger.debugee); + EvaluationContext { + evaluator: &evaluator, + expl_ctx: debugger.exploration_ctx(), + } + } + }; + cb(&ctx) + } +} + +macro_rules! type_from_cache { + ($variable: expr, $cache: expr) => { + $variable + .die + .type_ref() + .and_then( + |type_ref| match $cache.entry(($variable.unit().id, type_ref)) { + Entry::Occupied(o) => Some(Rc::clone(o.get())), + Entry::Vacant(v) => $variable.r#type().map(|t| { + let t = Rc::new(t); + v.insert(t.clone()); + t + }), + }, + ) + .ok_or_else(|| { + crate::debugger::variable::value::ParsingError::Assume( + crate::debugger::variable::value::AssumeError::NoType("variable"), + ) + }) + }; +} + +/// Evaluate DQE at current location. +pub struct DqeExecutor<'a> { + debugger: &'a Debugger, +} + +impl<'dbg> DqeExecutor<'dbg> { + pub fn new(debugger: &'dbg Debugger) -> Self { + Self { debugger } + } + + fn variable_die_by_selector( + &self, + selector: &Selector, + ) -> Result>, Error> { + let ctx = self.debugger.exploration_ctx(); + + let debugee = &self.debugger.debugee; + let current_func = debugee + .debug_info(ctx.location().pc)? + .find_function_by_pc(ctx.location().global_pc)? + .ok_or(FunctionNotFound(ctx.location().global_pc))?; + + let vars = match selector { + Selector::Name { + var_name, + local_only: local, + } => { + let local_variants = current_func + .local_variable(ctx.location().global_pc, var_name) + .map(|v| vec![v]) + .unwrap_or_default(); + + let local = *local; + + // local variables is in priority anyway, if there are no local variables and + // selector allow non-locals then try to search in a whole object + if !local && local_variants.is_empty() { + debugee + .debug_info(ctx.location().pc)? + .find_variables(ctx.location(), var_name)? + } else { + local_variants + } + } + Selector::Any => current_func.local_variables(ctx.location().global_pc), + }; + + Ok(vars) + } + + fn param_die_by_selector( + &self, + selector: &Selector, + ) -> Result>, Error> { + let expl_ctx_loc = self.debugger.exploration_ctx().location(); + let debugee = &self.debugger.debugee; + let current_function = debugee + .debug_info(expl_ctx_loc.pc)? + .find_function_by_pc(expl_ctx_loc.global_pc)? + .ok_or(FunctionNotFound(expl_ctx_loc.global_pc))?; + let params = current_function.parameters(); + let params = match selector { + Selector::Name { var_name, .. } => params + .into_iter() + .filter(|param| param.die.base_attributes.name.as_ref() == Some(var_name)) + .collect::>(), + Selector::Any => params, + }; + Ok(params) + } + + /// Select variables or arguments from debugee state. + fn apply_select_die( + &self, + selector: &Selector, + on_args: bool, + ) -> Result>, Error> { + fn root_from_die<'dbg, T: AsAllocatedData>( + debugger: &'dbg Debugger, + die: &ContextualDieRef<'_, 'dbg, T>, + ranges: Option>, + ) -> Option> { + let mut type_cache = debugger.type_cache.borrow_mut(); + let r#type = weak_error!(type_from_cache!(die, type_cache))?; + + let evaluator = ctx_resolve_unit_call!(die, evaluator, &debugger.debugee); + let context_builder = EvaluationContextBuilder::Ready(debugger, evaluator); + + let value = context_builder.with_eval_ctx(|eval_ctx| { + let data = die.read_value(debugger.exploration_ctx(), &debugger.debugee, &r#type); + + let parser = ValueParser::new(); + let parse_ctx = &ParseContext { + evaluation_context: eval_ctx, + type_graph: &r#type, + }; + let modifiers = &ValueModifiers::from_identity(parse_ctx, Identity::from_die(die)); + parser.parse(parse_ctx, data, modifiers) + })?; + + Some(QueryResult { + value: Some(value), + scope: ranges, + kind: QueryResultKind::Root, + base_type: r#type, + identity: Identity::from_die(die), + eval_ctx_builder: context_builder, + }) + } + + match on_args { + true => { + let params = self.param_die_by_selector(selector)?; + Ok(params + .iter() + .filter_map(|arg_die| { + root_from_die( + self.debugger, + arg_die, + arg_die.max_range().map(|r| { + let scope: Box<[Range]> = Box::new([r]); + scope + }), + ) + }) + .collect()) + } + false => { + let vars = self.variable_die_by_selector(selector)?; + Ok(vars + .iter() + .filter_map(|var_die| { + root_from_die(self.debugger, var_die, var_die.ranges().map(Box::from)) + }) + .collect()) + } + } + } + + /// Create virtual DIE from an existing type, + /// then return a query result with a value from this DIE and address in debugee memory. + fn apply_ptr_cast_op(&self, ptr_cast: &PointerCast) -> Result, Error> { + let mut var_die = VirtualVariableDie::workpiece(); + let var_die_ref = var_die.init_with_type(&self.debugger.debugee, &ptr_cast.ty)?; + + let mut type_cache = self.debugger.type_cache.borrow_mut(); + let r#type = type_from_cache!(var_die_ref, type_cache)?; + + let context_builder = EvaluationContextBuilder::Virtual { + debugger: self.debugger, + debug_info: var_die_ref.debug_info, + unit_idx: var_die_ref.unit_idx, + die: VirtualVariableDie::workpiece(), + }; + + let value = context_builder.with_eval_ctx(|eval_ctx| { + let data = ObjectBinaryRepr { + raw_data: Bytes::copy_from_slice(&ptr_cast.ptr.to_le_bytes()), + address: None, + size: std::mem::size_of::(), + }; + + let parser = ValueParser::new(); + let ctx = &ParseContext { + evaluation_context: eval_ctx, + type_graph: &r#type, + }; + parser.parse(ctx, Some(data), &ValueModifiers::default()) + }); + + Ok(QueryResult { + value, + scope: None, + kind: QueryResultKind::Expression, + base_type: r#type, + identity: Identity::default(), + eval_ctx_builder: context_builder, + }) + } + + fn apply_dqe(&self, dqe: &Dqe, on_args: bool) -> Result>, Error> { + match dqe { + Dqe::Variable(selector) => self.apply_select_die(selector, on_args), + Dqe::PtrCast(ptr_cast) => self.apply_ptr_cast_op(ptr_cast).map(|q| vec![q]), + Dqe::Field(next, field) => { + let results = self.apply_dqe(next, on_args)?; + Ok(results + .into_iter() + .filter_map(|q| q.modify_value(|_, val| val.field(field))) + .collect()) + } + Dqe::Index(next, idx) => { + let results = self.apply_dqe(next, on_args)?; + Ok(results + .into_iter() + .filter_map(|q| q.modify_value(|_, val| val.index(idx))) + .collect()) + } + Dqe::Slice(next, left, right) => { + let results = self.apply_dqe(next, on_args)?; + Ok(results + .into_iter() + .filter_map(|q| q.modify_value(|ctx, val| val.slice(ctx, *left, *right))) + .collect()) + } + Dqe::Deref(next) => { + let results = self.apply_dqe(next, on_args)?; + Ok(results + .into_iter() + .filter_map(|q| q.modify_value(|ctx, val| val.deref(ctx))) + .collect()) + } + Dqe::Address(next) => { + let results = self.apply_dqe(next, on_args)?; + Ok(results + .into_iter() + .filter_map(|q| q.modify_value(|ctx, val| val.address(ctx))) + .collect()) + } + Dqe::Canonic(next) => { + let results = self.apply_dqe(next, on_args)?; + Ok(results + .into_iter() + .filter_map(|q| q.modify_value(|_, val| Some(val.canonic()))) + .collect()) + } + } + } + + /// Query variables and returns matched list. + pub fn query(&self, dqe: &Dqe) -> Result>, Error> { + self.apply_dqe(dqe, false) + } + + /// Query only variable names. + /// Only filter expression supported. + /// + /// # Panics + /// + /// This method will panic if select expression + /// contains any operators excluding a variable selector. + pub fn query_names(&self, dqe: &Dqe) -> Result, Error> { + match dqe { + Dqe::Variable(selector) => { + let vars = self.variable_die_by_selector(selector)?; + Ok(vars + .into_iter() + .filter_map(|die| die.die.name().map(ToOwned::to_owned)) + .collect()) + } + _ => unreachable!("unexpected expression variant"), + } + } + + /// Same as [`DqeExecutor::query`] but for function arguments. + pub fn query_arguments(&self, dqe: &Dqe) -> Result>, Error> { + self.apply_dqe(dqe, true) + } + + /// Same as [`DqeExecutor::query_names`] but for function arguments. + pub fn query_arguments_names(&self, dqe: &Dqe) -> Result, Error> { + match dqe { + Dqe::Variable(selector) => { + let params = self.param_die_by_selector(selector)?; + Ok(params + .into_iter() + .filter_map(|die| die.die.name().map(ToOwned::to_owned)) + .collect()) + } + _ => unreachable!("unexpected expression variant"), + } + } +} diff --git a/src/debugger/variable/mod.rs b/src/debugger/variable/mod.rs index 7d290b1..a0d1087 100644 --- a/src/debugger/variable/mod.rs +++ b/src/debugger/variable/mod.rs @@ -1,80 +1,27 @@ -use crate::debugger::debugee::dwarf::r#type::{ - ArrayType, CModifier, EvaluationContext, ScalarType, StructureMember, TypeIdentity, -}; -use crate::debugger::debugee::dwarf::r#type::{ComplexType, TypeDeclaration}; use crate::debugger::debugee::dwarf::{AsAllocatedData, ContextualDieRef, NamespaceHierarchy}; -use crate::debugger::variable::render::RenderRepr; -use crate::debugger::variable::select::{Literal, LiteralOrWildcard, ObjectBinaryRepr}; -use crate::debugger::variable::specialization::{ - HashSetVariable, StrVariable, StringVariable, VariableParserExtension, -}; -use crate::{debugger, version_switch, weak_error}; use bytes::Bytes; -use gimli::{ - DW_ATE_address, DW_ATE_boolean, DW_ATE_float, DW_ATE_signed, DW_ATE_signed_char, - DW_ATE_unsigned, DW_ATE_unsigned_char, DW_ATE_ASCII, DW_ATE_UTF, -}; -use log::warn; -pub use specialization::SpecializedVariableIR; -use std::collections::{HashMap, VecDeque}; use std::fmt::{Debug, Display, Formatter}; -use std::string::FromUtf8Error; -use uuid::Uuid; +pub mod dqe; +pub mod execute; pub mod render; -pub mod select; -mod specialization; +pub mod value; +mod r#virtual; -#[derive(Debug, thiserror::Error, PartialEq)] -pub enum AssumeError { - #[error("field `{0}` not found")] - FieldNotFound(&'static str), - #[error("field `{0}` not a number")] - FieldNotANumber(&'static str), - #[error("incomplete interpretation of `{0}`")] - IncompleteInterp(&'static str), - #[error("not data for {0}")] - NoData(&'static str), - #[error("not type for {0}")] - NoType(&'static str), - #[error("underline data not a string")] - DataNotAString(#[from] FromUtf8Error), - #[error("undefined size of type `{0}`")] - UnknownSize(String), - #[error("type parameter `{0}` not found")] - TypeParameterNotFound(&'static str), - #[error("unknown type for type parameter `{0}`")] - TypeParameterTypeNotFound(&'static str), - #[error("unexpected type for {0}")] - UnexpectedType(&'static str), - #[error("unexpected binary representation of {0}, expect {1} got {2} bytes")] - UnexpectedBinaryRepr(&'static str, usize, usize), -} - -#[derive(Debug, thiserror::Error, PartialEq)] -pub enum ParsingError { - #[error(transparent)] - Assume(#[from] AssumeError), - #[error("unsupported language version")] - UnsupportedVersion, - #[error("error while reading from debugee memory: {0}")] - ReadDebugeeMemory(#[from] nix::Error), -} - -/// Identifier of debugee variables. -/// Consists of the name and namespace of the variable. -#[derive(Clone, Default, PartialEq)] -pub struct VariableIdentity { +/// Identifier of a query result. +/// Consists name and namespace of the variable or argument. +#[derive(Clone, Default, PartialEq, Debug)] +pub struct Identity { namespace: NamespaceHierarchy, pub name: Option, } -impl VariableIdentity { +impl Identity { pub fn new(namespace: NamespaceHierarchy, name: Option) -> Self { Self { namespace, name } } - pub fn from_variable_die(var: &ContextualDieRef) -> Self { + pub fn from_die(var: &ContextualDieRef) -> Self { Self::new(var.namespaces(), var.die.name().map(String::from)) } @@ -86,7 +33,7 @@ impl VariableIdentity { } } -impl Display for VariableIdentity { +impl Display for Identity { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let namespaces = if self.namespace.is_empty() { String::default() @@ -95,2497 +42,19 @@ impl Display for VariableIdentity { }; match self.name.as_deref() { - None => f.write_fmt(format_args!("{namespaces}{{unknown}}")), - Some(mut name) => { - if name.starts_with("__") { - let mb_num = name.trim_start_matches('_'); - if mb_num.parse::().is_ok() { - name = mb_num - } - } - - f.write_fmt(format_args!("{namespaces}{name}")) - } - } - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum SupportedScalar { - I8(i8), - I16(i16), - I32(i32), - I64(i64), - I128(i128), - Isize(isize), - U8(u8), - U16(u16), - U32(u32), - U64(u64), - U128(u128), - Usize(usize), - F32(f32), - F64(f64), - Bool(bool), - Char(char), - Empty(), -} - -impl Display for SupportedScalar { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - SupportedScalar::I8(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::I16(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::I32(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::I64(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::I128(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::Isize(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::U8(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::U16(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::U32(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::U64(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::U128(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::Usize(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::F32(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::F64(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::Bool(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::Char(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::Empty() => f.write_str("()"), - } - } -} - -impl SupportedScalar { - fn equal_with_literal(&self, lhs: &Literal) -> bool { - match self { - SupportedScalar::I8(i) => lhs.equal_with_int(*i as i64), - SupportedScalar::I16(i) => lhs.equal_with_int(*i as i64), - SupportedScalar::I32(i) => lhs.equal_with_int(*i as i64), - SupportedScalar::I64(i) => lhs.equal_with_int(*i), - SupportedScalar::I128(i) => lhs.equal_with_int(*i as i64), - SupportedScalar::Isize(i) => lhs.equal_with_int(*i as i64), - SupportedScalar::U8(u) => lhs.equal_with_int(*u as i64), - SupportedScalar::U16(u) => lhs.equal_with_int(*u as i64), - SupportedScalar::U32(u) => lhs.equal_with_int(*u as i64), - SupportedScalar::U64(u) => lhs.equal_with_int(*u as i64), - SupportedScalar::U128(u) => lhs.equal_with_int(*u as i64), - SupportedScalar::Usize(u) => lhs.equal_with_int(*u as i64), - SupportedScalar::F32(f) => lhs.equal_with_float(*f as f64), - SupportedScalar::F64(f) => lhs.equal_with_float(*f), - SupportedScalar::Bool(b) => lhs.equal_with_bool(*b), - SupportedScalar::Char(c) => lhs.equal_with_string(&c.to_string()), - SupportedScalar::Empty() => false, - } - } -} - -/// Represents scalars: integer's, float's, bool, char and () types. -#[derive(Clone, PartialEq)] -pub struct ScalarVariable { - pub identity: VariableIdentity, - pub value: Option, - pub raw_address: Option, - pub type_name: Option, - pub type_id: Option, -} - -impl ScalarVariable { - fn try_as_number(&self) -> Option { - match self.value { - Some(SupportedScalar::I8(num)) => Some(num as i64), - Some(SupportedScalar::I16(num)) => Some(num as i64), - Some(SupportedScalar::I32(num)) => Some(num as i64), - Some(SupportedScalar::I64(num)) => Some(num), - Some(SupportedScalar::Isize(num)) => Some(num as i64), - Some(SupportedScalar::U8(num)) => Some(num as i64), - Some(SupportedScalar::U16(num)) => Some(num as i64), - Some(SupportedScalar::U32(num)) => Some(num as i64), - Some(SupportedScalar::U64(num)) => Some(num as i64), - Some(SupportedScalar::Usize(num)) => Some(num as i64), - _ => None, - } - } -} - -/// Represents structures. -#[derive(Clone, Default, PartialEq)] -pub struct StructVariable { - pub identity: VariableIdentity, - pub type_name: Option, - pub type_id: Option, - /// Structure members. Each represents by variable IR. - pub members: Vec, - /// Map of type parameters of a structure type. - pub type_params: HashMap>, - pub raw_address: Option, -} - -/// Represents arrays. -#[derive(Clone, PartialEq)] -pub struct ArrayVariable { - pub identity: VariableIdentity, - pub type_name: Option, - pub type_id: Option, - /// Array items. Each represents by variable IR. - pub items: Option>, - pub raw_address: Option, -} - -impl ArrayVariable { - fn slice(&mut self, left: Option, right: Option) { - if let Some(items) = self.items.as_mut() { - if let Some(left) = left { - items.drain(..left); - } - - if let Some(right) = right { - let remove_range = right - left.unwrap_or_default()..; - if remove_range.start < items.len() { - items.drain(remove_range); - }; - } + None => Ok(()), + Some(name) => f.write_fmt(format_args!("{namespaces}{name}")), } } } -/// Simple c-style enums (each option in which does not contain the underlying values). -#[derive(Clone, PartialEq)] -pub struct CEnumVariable { - pub identity: VariableIdentity, - pub type_name: Option, - pub type_id: Option, - /// String representation of selected variant. - pub value: Option, - pub raw_address: Option, -} - -/// Represents all enum's that more complex then c-style enums. -#[derive(Clone, PartialEq)] -pub struct RustEnumVariable { - pub identity: VariableIdentity, - pub type_name: Option, - pub type_id: Option, - /// Variable IR representation of selected variant. - pub value: Option>, - pub raw_address: Option, -} - -/// Raw pointers, references, Box. -#[derive(Clone, PartialEq)] -pub struct PointerVariable { - pub identity: VariableIdentity, - pub type_name: Option, - pub type_id: Option, - /// Raw pointer to underline value. - pub value: Option<*const ()>, - /// Underline type identity. - pub target_type: Option, - pub target_type_size: Option, - pub raw_address: Option, -} - -impl PointerVariable { - /// Dereference pointer and return variable IR that represents underline value. - pub fn deref( - &self, - eval_ctx: &EvaluationContext, - parser: &VariableParser, - ) -> Option { - let target_type = self.target_type?; - let deref_size = self - .target_type_size - .or_else(|| parser.r#type.type_size_in_bytes(eval_ctx, target_type)); - - let target_type_decl = parser.r#type.types.get(&target_type); - if matches!(target_type_decl, Some(TypeDeclaration::Subroutine { .. })) { - // this variable is a fn pointer - don't deref it - return None; - } - - self.value.and_then(|ptr| { - let data = deref_size.and_then(|sz| { - let raw_data = debugger::read_memory_by_pid( - eval_ctx.expl_ctx.pid_on_focus(), - ptr as usize, - sz as usize, - ) - .ok()?; - - Some(ObjectBinaryRepr { - raw_data: Bytes::from(raw_data), - address: Some(ptr as usize), - size: sz as usize, - }) - }); - let mut identity = self.identity.clone(); - identity.name = identity.name.map(|n| format!("*{n}")); - parser.parse_inner(eval_ctx, identity, data, target_type) - }) - } - - /// Interpret pointer as a pointer on first array element. Returns variable IR that represents - /// an array. - pub fn slice( - &self, - eval_ctx: &EvaluationContext, - parser: &VariableParser, - left: Option, - right: usize, - ) -> Option { - let target_type = self.target_type?; - let deref_size = parser.r#type.type_size_in_bytes(eval_ctx, target_type)? as usize; - - self.value.and_then(|ptr| { - let left = left.unwrap_or_default(); - let base_addr = ptr as usize + deref_size * left; - let raw_data = weak_error!(debugger::read_memory_by_pid( - eval_ctx.expl_ctx.pid_on_focus(), - base_addr, - deref_size * (right - left) - ))?; - let raw_data = bytes::Bytes::from(raw_data); - let mut identity = self.identity.clone(); - identity.name = identity.name.map(|n| format!("[*{n}]")); - - let items = raw_data - .chunks(deref_size) - .enumerate() - .filter_map(|(i, chunk)| { - let data = ObjectBinaryRepr { - raw_data: raw_data.slice_ref(chunk), - address: Some(base_addr + (i * deref_size)), - size: deref_size, - }; - parser.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(Some(format!("{}", i as i64))), - Some(data), - target_type, - ) - }) - .collect::>(); - - Some(VariableIR::Array(ArrayVariable { - identity, - items: Some(items), - type_id: None, - type_name: parser - .r#type - .type_name(target_type) - .map(|t| format!("[{t}]")), - raw_address: Some(base_addr), - })) - }) - } -} - -/// Represents subroutine. -#[derive(Clone, PartialEq)] -pub struct SubroutineVariable { - pub identity: VariableIdentity, - pub type_id: Option, - pub return_type_name: Option, - pub address: Option, -} - -/// Represent a variable with C modifiers (volatile, const, typedef, etc) -#[derive(Clone, PartialEq)] -pub struct CModifiedVariable { - pub identity: VariableIdentity, - pub type_name: Option, - pub type_id: Option, - pub modifier: CModifier, - pub value: Option>, +/// Object binary representation in debugee memory. +pub struct ObjectBinaryRepr { + /// Binary representation. + pub raw_data: Bytes, + /// Possible address of object data in debugee memory. + /// It may not exist if there is no debug information, or if an object is allocated in registers. pub address: Option, -} - -/// Variable intermediate representation. -#[derive(Clone, PartialEq)] -pub enum VariableIR { - Scalar(ScalarVariable), - Struct(StructVariable), - Array(ArrayVariable), - CEnum(CEnumVariable), - RustEnum(RustEnumVariable), - Pointer(PointerVariable), - Subroutine(SubroutineVariable), - Specialized(SpecializedVariableIR), - CModifiedVariable(CModifiedVariable), -} - -// SAFETY: this enum may contain a raw pointers on memory in a debugee process, -// it is safe to dereference it using public API of *Variable structures. -unsafe impl Send for VariableIR {} - -impl Debug for VariableIR { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.name()) - } -} - -impl VariableIR { - /// Return address in debugee memory for variable data. - pub fn in_memory_location(&self) -> Option { - match self { - VariableIR::Scalar(s) => s.raw_address, - VariableIR::Struct(s) => s.raw_address, - VariableIR::Array(a) => a.raw_address, - VariableIR::CEnum(ce) => ce.raw_address, - VariableIR::RustEnum(re) => re.raw_address, - VariableIR::Pointer(p) => p.raw_address, - VariableIR::Subroutine(s) => s.address, - VariableIR::Specialized(s) => s.in_memory_location(), - VariableIR::CModifiedVariable(cmv) => cmv.address, - } - } - - pub fn type_identity(&self) -> Option { - match self { - VariableIR::Scalar(s) => s.type_id, - VariableIR::Struct(s) => s.type_id, - VariableIR::Array(a) => a.type_id, - VariableIR::CEnum(ce) => ce.type_id, - VariableIR::RustEnum(re) => re.type_id, - VariableIR::Pointer(p) => p.type_id, - VariableIR::Subroutine(s) => s.type_id, - VariableIR::Specialized(s) => s.type_id(), - VariableIR::CModifiedVariable(cmv) => cmv.type_id, - } - } - - /// Visit variable children in BFS order. - fn bfs_iterator(&self) -> BfsIterator { - BfsIterator { - queue: VecDeque::from([self]), - } - } - - /// Returns i64 value representation or error if cast fail. - fn assume_field_as_scalar_number(&self, field_name: &'static str) -> Result { - let ir = self - .bfs_iterator() - .find(|child| child.name() == field_name) - .ok_or(AssumeError::FieldNotFound(field_name))?; - if let VariableIR::Scalar(s) = ir { - Ok(s.try_as_number() - .ok_or(AssumeError::FieldNotANumber(field_name))?) - } else { - Err(AssumeError::FieldNotANumber(field_name)) - } - } - - /// Returns value as raw pointer or error if cast fail. - fn assume_field_as_pointer(&self, field_name: &'static str) -> Result<*const (), AssumeError> { - self.bfs_iterator() - .find_map(|child| { - if let VariableIR::Pointer(pointer) = child { - if pointer.identity.name.as_deref()? == field_name { - return pointer.value; - } - } - None - }) - .ok_or(AssumeError::IncompleteInterp("pointer")) - } - - /// Returns value as enum or error if cast fail. - fn assume_field_as_rust_enum( - &self, - field_name: &'static str, - ) -> Result { - self.bfs_iterator() - .find_map(|child| { - if let VariableIR::RustEnum(r_enum) = child { - if r_enum.identity.name.as_deref()? == field_name { - return Some(r_enum.clone()); - } - } - None - }) - .ok_or(AssumeError::IncompleteInterp("pointer")) - } - - /// Returns value as structure or error if cast fail. - fn assume_field_as_struct( - &self, - field_name: &'static str, - ) -> Result { - self.bfs_iterator() - .find_map(|child| { - if let VariableIR::Struct(structure) = child { - if structure.identity.name.as_deref()? == field_name { - return Some(structure.clone()); - } - } - None - }) - .ok_or(AssumeError::IncompleteInterp("structure")) - } - - /// Returns variable identity. - fn identity(&self) -> &VariableIdentity { - match self { - VariableIR::Scalar(s) => &s.identity, - VariableIR::Struct(s) => &s.identity, - VariableIR::Array(a) => &a.identity, - VariableIR::CEnum(e) => &e.identity, - VariableIR::RustEnum(e) => &e.identity, - VariableIR::Pointer(p) => &p.identity, - VariableIR::Specialized(s) => match s { - SpecializedVariableIR::Vector { original, .. } => &original.identity, - SpecializedVariableIR::VecDeque { original, .. } => &original.identity, - SpecializedVariableIR::String { original, .. } => &original.identity, - SpecializedVariableIR::Str { original, .. } => &original.identity, - SpecializedVariableIR::Tls { original, .. } => &original.identity, - SpecializedVariableIR::HashMap { original, .. } => &original.identity, - SpecializedVariableIR::HashSet { original, .. } => &original.identity, - SpecializedVariableIR::BTreeMap { original, .. } => &original.identity, - SpecializedVariableIR::BTreeSet { original, .. } => &original.identity, - SpecializedVariableIR::Cell { original, .. } => &original.identity, - SpecializedVariableIR::RefCell { original, .. } => &original.identity, - SpecializedVariableIR::Rc { original, .. } => &original.identity, - SpecializedVariableIR::Arc { original, .. } => &original.identity, - SpecializedVariableIR::Uuid { original, .. } => &original.identity, - }, - VariableIR::Subroutine(s) => &s.identity, - VariableIR::CModifiedVariable(v) => &v.identity, - } - } - - pub fn identity_mut(&mut self) -> &mut VariableIdentity { - match self { - VariableIR::Scalar(s) => &mut s.identity, - VariableIR::Struct(s) => &mut s.identity, - VariableIR::Array(a) => &mut a.identity, - VariableIR::CEnum(e) => &mut e.identity, - VariableIR::RustEnum(e) => &mut e.identity, - VariableIR::Pointer(p) => &mut p.identity, - VariableIR::Specialized(s) => match s { - SpecializedVariableIR::Vector { original, .. } => &mut original.identity, - SpecializedVariableIR::VecDeque { original, .. } => &mut original.identity, - SpecializedVariableIR::String { original, .. } => &mut original.identity, - SpecializedVariableIR::Str { original, .. } => &mut original.identity, - SpecializedVariableIR::Tls { original, .. } => &mut original.identity, - SpecializedVariableIR::HashMap { original, .. } => &mut original.identity, - SpecializedVariableIR::HashSet { original, .. } => &mut original.identity, - SpecializedVariableIR::BTreeMap { original, .. } => &mut original.identity, - SpecializedVariableIR::BTreeSet { original, .. } => &mut original.identity, - SpecializedVariableIR::Cell { original, .. } => &mut original.identity, - SpecializedVariableIR::RefCell { original, .. } => &mut original.identity, - SpecializedVariableIR::Rc { original, .. } => &mut original.identity, - SpecializedVariableIR::Arc { original, .. } => &mut original.identity, - SpecializedVariableIR::Uuid { original, .. } => &mut original.identity, - }, - VariableIR::Subroutine(s) => &mut s.identity, - VariableIR::CModifiedVariable(v) => &mut v.identity, - } - } - - /// If a variable has a specialized ir (vectors, strings, etc.) then return - /// an underlying structure - fn canonic(self) -> Self { - let VariableIR::Specialized(spec) = self else { - return self; - }; - - match spec { - SpecializedVariableIR::Vector { original, .. } - | SpecializedVariableIR::VecDeque { original, .. } - | SpecializedVariableIR::HashMap { original, .. } - | SpecializedVariableIR::HashSet { original, .. } - | SpecializedVariableIR::BTreeMap { original, .. } - | SpecializedVariableIR::BTreeSet { original, .. } - | SpecializedVariableIR::String { original, .. } - | SpecializedVariableIR::Str { original, .. } - | SpecializedVariableIR::Tls { original, .. } - | SpecializedVariableIR::Cell { original, .. } - | SpecializedVariableIR::RefCell { original, .. } - | SpecializedVariableIR::Rc { original, .. } - | SpecializedVariableIR::Arc { original, .. } - | SpecializedVariableIR::Uuid { original, .. } => VariableIR::Struct(original), - } - } - - /// Try to dereference variable and returns underline variable IR. - /// Return `None` if dereference not allowed. - fn deref(self, eval_ctx: &EvaluationContext, variable_parser: &VariableParser) -> Option { - match self { - VariableIR::Pointer(ptr) => ptr.deref(eval_ctx, variable_parser), - VariableIR::RustEnum(r_enum) => r_enum - .value - .and_then(|v| v.deref(eval_ctx, variable_parser)), - VariableIR::Specialized(SpecializedVariableIR::Rc { value, .. }) - | VariableIR::Specialized(SpecializedVariableIR::Arc { value, .. }) => { - value.and_then(|var| var.deref(eval_ctx, variable_parser)) - } - VariableIR::Specialized(SpecializedVariableIR::Tls { tls_var, .. }) => tls_var - .and_then(|var| { - var.inner_value - .and_then(|inner| inner.deref(eval_ctx, variable_parser)) - }), - _ => None, - } - } - - /// Return address (as pointer variable) of raw data in debugee memory. - fn address( - self, - eval_ctx: &EvaluationContext, - variable_parser: &VariableParser, - ) -> Option { - let addr = self.in_memory_location()?; - Some(VariableIR::Pointer(PointerVariable { - identity: VariableIdentity::no_namespace(None), - type_name: Some(format!("&{}", self.r#type())), - value: Some(addr as *const ()), - target_type: self.type_identity(), - target_type_size: self - .type_identity() - .and_then(|t| variable_parser.r#type.type_size_in_bytes(eval_ctx, t)), - raw_address: None, - type_id: None, - })) - } - - /// Return variable field, `None` if get field is not allowed for variable type. - /// Supported: structures, rust-style enums, hashmaps, btree-maps. - fn field(self, field_name: &str) -> Option { - match self { - VariableIR::Struct(structure) => structure - .members - .into_iter() - .find(|member| field_name == member.name()), - VariableIR::RustEnum(r_enum) => r_enum.value.and_then(|v| v.field(field_name)), - VariableIR::Specialized(spec) => match spec { - SpecializedVariableIR::HashMap { map, .. } => map.and_then(|map| { - map.kv_items.into_iter().find_map(|(key, value)| match key { - VariableIR::Specialized(spec) => match spec { - SpecializedVariableIR::String { - string: string_key, .. - } => string_key.and_then(|string| { - (string.value == field_name) - .then(|| value.clone_and_rename(&string.value)) - }), - SpecializedVariableIR::Str { - string: string_key, .. - } => string_key.and_then(|str| { - (str.value == field_name) - .then(|| value.clone_and_rename(&str.value)) - }), - _ => None, - }, - _ => None, - }) - }), - SpecializedVariableIR::Tls { tls_var, .. } => tls_var - .and_then(|var| var.inner_value.and_then(|inner| inner.field(field_name))), - SpecializedVariableIR::Cell { value, .. } - | SpecializedVariableIR::RefCell { value, .. } => { - value.and_then(|var| var.field(field_name)) - } - _ => None, - }, - _ => None, - } - } - - /// Return variable element by its index, `None` if indexing is not allowed for a variable type. - /// Supported: array, rust-style enums, vector, hashmap, hashset, btreemap, btreeset. - fn index(self, idx: &Literal) -> Option { - match self { - VariableIR::Array(array) => array.items.and_then(|mut items| { - if let Literal::Int(idx) = idx { - let idx = *idx as usize; - if idx < items.len() { - return Some(items.swap_remove(idx)); - } - } - None - }), - VariableIR::RustEnum(r_enum) => r_enum.value.and_then(|v| v.index(idx)), - VariableIR::Specialized(spec) => match spec { - SpecializedVariableIR::Vector { vec, .. } - | SpecializedVariableIR::VecDeque { vec, .. } => vec.and_then(|mut v| { - let inner_array = v.structure.members.swap_remove(0); - inner_array.index(idx) - }), - SpecializedVariableIR::Tls { tls_var, .. } => { - tls_var.and_then(|var| var.inner_value.and_then(|inner| inner.index(idx))) - } - SpecializedVariableIR::Cell { value, .. } - | SpecializedVariableIR::RefCell { value, .. } => { - value.and_then(|var| var.index(idx)) - } - SpecializedVariableIR::BTreeMap { map: Some(map), .. } - | SpecializedVariableIR::HashMap { map: Some(map), .. } => { - for (k, mut v) in map.kv_items { - if k.match_literal(idx) { - let identity = v.identity_mut(); - identity.name = Some("value".to_string()); - return Some(v); - } - } - - None - } - SpecializedVariableIR::BTreeSet { set: Some(set), .. } - | SpecializedVariableIR::HashSet { set: Some(set), .. } => { - let found = set.items.into_iter().any(|it| it.match_literal(idx)); - - Some(VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some("contains".to_string())), - type_id: None, - type_name: Some("bool".to_string()), - value: Some(SupportedScalar::Bool(found)), - raw_address: None, - })) - } - _ => None, - }, - _ => None, - } - } - - fn slice( - mut self, - eval_ctx: &EvaluationContext, - variable_parser: &VariableParser, - left: Option, - right: Option, - ) -> Option { - match &mut self { - VariableIR::Array(ref mut array) => { - array.slice(left, right); - Some(self) - } - VariableIR::Pointer(ptr) => { - // for pointer the right bound must always be specified - let right = right?; - ptr.slice(eval_ctx, variable_parser, left, right) - } - VariableIR::Specialized(spec) => match spec { - SpecializedVariableIR::Rc { value, .. } - | SpecializedVariableIR::Arc { value, .. } => { - let ptr = value.as_ref()?; - // for pointer the right bound must always be specified - let right = right?; - ptr.slice(eval_ctx, variable_parser, left, right) - } - SpecializedVariableIR::Vector { vec, .. } - | SpecializedVariableIR::VecDeque { vec, .. } => { - let vec = vec.as_mut()?; - vec.slice(left, right); - Some(self) - } - SpecializedVariableIR::Tls { tls_var, .. } => { - let tls_var = tls_var.take()?; - let inner = tls_var.inner_value?; - inner.slice(eval_ctx, variable_parser, left, right) - } - SpecializedVariableIR::Cell { value, .. } - | SpecializedVariableIR::RefCell { value, .. } => { - let inner = value.take()?; - inner.slice(eval_ctx, variable_parser, left, right) - } - _ => None, - }, - _ => None, - } - } - - fn clone_and_rename(&self, new_name: &str) -> Self { - let mut clone = self.clone(); - let identity = clone.identity_mut(); - identity.name = Some(new_name.to_string()); - clone - } - - /// Match variable with a literal object. - /// Return true if variable matched to literal. - fn match_literal(self, literal: &Literal) -> bool { - match self { - VariableIR::Scalar(ScalarVariable { - value: Some(scalar), - .. - }) => scalar.equal_with_literal(literal), - VariableIR::Pointer(PointerVariable { - value: Some(ptr), .. - }) => literal.equal_with_address(ptr as usize), - VariableIR::Array(ArrayVariable { - items: Some(items), .. - }) => { - let Literal::Array(arr_literal) = literal else { - return false; - }; - if arr_literal.len() != items.len() { - return false; - } - - for (i, item) in items.into_iter().enumerate() { - match &arr_literal[i] { - LiteralOrWildcard::Literal(lit) => { - if !item.match_literal(lit) { - return false; - } - } - LiteralOrWildcard::Wildcard => continue, - } - } - true - } - VariableIR::Struct(StructVariable { members, .. }) => { - match literal { - Literal::Array(array_literal) => { - // structure must be a tuple - if array_literal.len() != members.len() { - return false; - } - - for (i, member) in members.into_iter().enumerate() { - let field_literal = &array_literal[i]; - match field_literal { - LiteralOrWildcard::Literal(lit) => { - if !member.match_literal(lit) { - return false; - } - } - LiteralOrWildcard::Wildcard => continue, - } - } - - true - } - Literal::AssocArray(struct_literal) => { - // default structure - if struct_literal.len() != members.len() { - return false; - } - - for member in members { - let Some(member_name) = member.identity().name.as_ref() else { - return false; - }; - - let Some(field_literal) = struct_literal.get(member_name) else { - return false; - }; - - match field_literal { - LiteralOrWildcard::Literal(lit) => { - if !member.match_literal(lit) { - return false; - } - } - LiteralOrWildcard::Wildcard => continue, - } - } - true - } - _ => false, - } - } - VariableIR::Specialized(spec) => match spec { - SpecializedVariableIR::String { - string: Some(StringVariable { value, .. }), - .. - } => literal.equal_with_string(&value), - SpecializedVariableIR::Str { - string: Some(StrVariable { value, .. }), - .. - } => literal.equal_with_string(&value), - SpecializedVariableIR::Uuid { - value: Some(bytes), .. - } => { - let uuid = Uuid::from_bytes(bytes); - literal.equal_with_string(&uuid.to_string()) - } - SpecializedVariableIR::Cell { mut value, .. } - | SpecializedVariableIR::RefCell { mut value, .. } => { - let Some(inner) = value.take() else { - return false; - }; - inner.match_literal(literal) - } - SpecializedVariableIR::Rc { - value: - Some(PointerVariable { - value: Some(ptr), .. - }), - .. - } - | SpecializedVariableIR::Arc { - value: - Some(PointerVariable { - value: Some(ptr), .. - }), - .. - } => literal.equal_with_address(ptr as usize), - SpecializedVariableIR::Vector { - vec: Some(mut v), .. - } - | SpecializedVariableIR::VecDeque { - vec: Some(mut v), .. - } => { - let inner_array = v.structure.members.swap_remove(0); - debug_assert!(matches!(inner_array, VariableIR::Array(_))); - inner_array.match_literal(literal) - } - SpecializedVariableIR::HashSet { - set: Some(HashSetVariable { items, .. }), - .. - } - | SpecializedVariableIR::BTreeSet { - set: Some(HashSetVariable { items, .. }), - .. - } => { - let Literal::Array(arr_literal) = literal else { - return false; - }; - if arr_literal.len() != items.len() { - return false; - } - let mut arr_literal = arr_literal.to_vec(); - - for item in items { - let mut item_found = false; - - // try to find equals item - let mb_literal_idx = arr_literal.iter().position(|lit| { - if let LiteralOrWildcard::Literal(lit) = lit { - item.clone().match_literal(lit) - } else { - false - } - }); - if let Some(literal_idx) = mb_literal_idx { - arr_literal.swap_remove(literal_idx); - item_found = true; - } - - // try to find wildcard - if !item_found { - let mb_wildcard_idx = arr_literal - .iter() - .position(|lit| matches!(lit, LiteralOrWildcard::Wildcard)); - if let Some(wildcard_idx) = mb_wildcard_idx { - arr_literal.swap_remove(wildcard_idx); - item_found = true; - } - } - - // still not found - set aren't equal - if !item_found { - return false; - } - } - true - } - _ => false, - }, - VariableIR::CEnum(CEnumVariable { - value: Some(ref value), - .. - }) => { - let Literal::EnumVariant(variant, None) = literal else { - return false; - }; - value == variant - } - VariableIR::RustEnum(RustEnumVariable { - value: Some(value), .. - }) => { - let Literal::EnumVariant(variant, variant_value) = literal else { - return false; - }; - - if value.identity().name.as_ref() != Some(variant) { - return false; - } - - match variant_value { - None => true, - Some(lit) => value.match_literal(lit), - } - } - _ => false, - } - } -} - -pub struct VariableParser<'a> { - r#type: &'a ComplexType, -} - -impl<'a> VariableParser<'a> { - pub fn new(r#type: &'a ComplexType) -> Self { - Self { r#type } - } - - fn parse_scalar( - &self, - identity: VariableIdentity, - data: Option, - type_id: TypeIdentity, - r#type: &ScalarType, - ) -> ScalarVariable { - fn render_scalar(data: Option) -> Option { - data.as_ref().map(|v| scalar_from_bytes::(&v.raw_data)) - } - let in_debugee_loc = data.as_ref().and_then(|d| d.address); - #[allow(non_upper_case_globals)] - let value_view = r#type.encoding.and_then(|encoding| match encoding { - DW_ATE_address => render_scalar::(data).map(SupportedScalar::Usize), - DW_ATE_signed_char => render_scalar::(data).map(SupportedScalar::I8), - DW_ATE_unsigned_char => render_scalar::(data).map(SupportedScalar::U8), - DW_ATE_signed => match r#type.byte_size.unwrap_or(0) { - 0 => Some(SupportedScalar::Empty()), - 1 => render_scalar::(data).map(SupportedScalar::I8), - 2 => render_scalar::(data).map(SupportedScalar::I16), - 4 => render_scalar::(data).map(SupportedScalar::I32), - 8 => { - if r#type.name.as_deref() == Some("isize") { - render_scalar::(data).map(SupportedScalar::Isize) - } else { - render_scalar::(data).map(SupportedScalar::I64) - } - } - 16 => render_scalar::(data).map(SupportedScalar::I128), - _ => { - warn!( - "parse scalar: unexpected signed size: {size:?}", - size = r#type.byte_size - ); - None - } - }, - DW_ATE_unsigned => match r#type.byte_size.unwrap_or(0) { - 0 => Some(SupportedScalar::Empty()), - 1 => render_scalar::(data).map(SupportedScalar::U8), - 2 => render_scalar::(data).map(SupportedScalar::U16), - 4 => render_scalar::(data).map(SupportedScalar::U32), - 8 => { - if r#type.name.as_deref() == Some("usize") { - render_scalar::(data).map(SupportedScalar::Usize) - } else { - render_scalar::(data).map(SupportedScalar::U64) - } - } - 16 => render_scalar::(data).map(SupportedScalar::U128), - _ => { - warn!( - "parse scalar: unexpected unsigned size: {size:?}", - size = r#type.byte_size - ); - None - } - }, - DW_ATE_float => match r#type.byte_size.unwrap_or(0) { - 4 => render_scalar::(data).map(SupportedScalar::F32), - 8 => render_scalar::(data).map(SupportedScalar::F64), - _ => { - warn!( - "parse scalar: unexpected float size: {size:?}", - size = r#type.byte_size - ); - None - } - }, - DW_ATE_boolean => render_scalar::(data).map(SupportedScalar::Bool), - DW_ATE_UTF => render_scalar::(data).map(|char| { - // WAITFORFIX: https://github.com/rust-lang/rust/issues/113819 - // this check is meaningfully here cause in case above there is a random bytes here, - // and it may lead to panic in other places - // (specially when someone tries to render this char) - if String::from_utf8(char.to_string().into_bytes()).is_err() { - SupportedScalar::Char('?') - } else { - SupportedScalar::Char(char) - } - }), - DW_ATE_ASCII => render_scalar::(data).map(SupportedScalar::Char), - _ => { - warn!("parse scalar: unexpected base type encoding: {encoding}"); - None - } - }); - - ScalarVariable { - identity, - type_name: r#type.name.clone(), - type_id: Some(type_id), - value: value_view, - raw_address: in_debugee_loc, - } - } - - fn parse_struct_variable( - &self, - eval_ctx: &EvaluationContext, - identity: VariableIdentity, - data: Option, - type_id: TypeIdentity, - type_params: HashMap>, - members: &[StructureMember], - ) -> StructVariable { - let children = members - .iter() - .filter_map(|member| self.parse_struct_member(eval_ctx, member, data.as_ref())) - .collect(); - - StructVariable { - identity, - type_id: Some(type_id), - type_name: self.r#type.type_name(type_id), - members: children, - type_params, - raw_address: data.and_then(|d| d.address), - } - } - - fn parse_struct_member( - &self, - eval_ctx: &EvaluationContext, - member: &StructureMember, - parent_data: Option<&ObjectBinaryRepr>, - ) -> Option { - let name = member.name.clone(); - let Some(type_ref) = member.type_ref else { - warn!( - "parse structure: unknown type for member {}", - name.as_deref().unwrap_or_default() - ); - return None; - }; - let member_val = parent_data.and_then(|data| member.value(eval_ctx, self.r#type, data)); - - self.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(member.name.clone()), - member_val, - type_ref, - ) - } - - fn parse_array( - &self, - eval_ctx: &EvaluationContext, - identity: VariableIdentity, - data: Option, - type_id: TypeIdentity, - array_decl: &ArrayType, - ) -> ArrayVariable { - let items = array_decl.bounds(eval_ctx).and_then(|bounds| { - let len = bounds.1 - bounds.0; - let data = data.as_ref()?; - let el_size = (array_decl.size_in_bytes(eval_ctx, self.r#type)? / len as u64) as usize; - let bytes = &data.raw_data; - let el_type_id = array_decl.element_type?; - - let (mut bytes_chunks, mut empty_chunks); - let raw_items_iter: &mut dyn Iterator = if el_size != 0 { - bytes_chunks = bytes.chunks(el_size).enumerate(); - &mut bytes_chunks - } else { - // if an item type is zst - let v: Vec<&[u8]> = vec![&[]; len as usize]; - empty_chunks = v.into_iter().enumerate(); - &mut empty_chunks - }; - - Some( - raw_items_iter - .filter_map(|(i, chunk)| { - let offset = i * el_size; - let data = ObjectBinaryRepr { - raw_data: bytes.slice_ref(chunk), - address: data.address.map(|addr| addr + offset), - size: el_size, - }; - - self.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(Some(format!( - "{index}", - index = bounds.0 + i as i64 - ))), - Some(data), - el_type_id, - ) - }) - .collect::>(), - ) - }); - - ArrayVariable { - identity, - items, - type_id: Some(type_id), - type_name: self.r#type.type_name(type_id), - raw_address: data.and_then(|d| d.address), - } - } - - fn parse_c_enum( - &self, - eval_ctx: &EvaluationContext, - identity: VariableIdentity, - data: Option, - type_id: TypeIdentity, - discr_type: Option, - enumerators: &HashMap, - ) -> CEnumVariable { - let in_debugee_loc = data.as_ref().and_then(|d| d.address); - let mb_discr = discr_type.and_then(|type_id| { - self.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(None), - data, - type_id, - ) - }); - - let value = mb_discr.and_then(|discr| { - if let VariableIR::Scalar(scalar) = discr { - scalar.try_as_number() - } else { - None - } - }); - - CEnumVariable { - identity, - type_name: self.r#type.type_name(type_id), - type_id: Some(type_id), - value: value.and_then(|val| enumerators.get(&val).cloned()), - raw_address: in_debugee_loc, - } - } - - fn parse_rust_enum( - &self, - eval_ctx: &EvaluationContext, - identity: VariableIdentity, - data: Option, - type_id: TypeIdentity, - discr_member: Option<&StructureMember>, - enumerators: &HashMap, StructureMember>, - ) -> RustEnumVariable { - let discr_value = discr_member.and_then(|member| { - let discr = self.parse_struct_member(eval_ctx, member, data.as_ref())?; - if let VariableIR::Scalar(scalar) = discr { - return scalar.try_as_number(); - } - None - }); - - let enumerator = - discr_value.and_then(|v| enumerators.get(&Some(v)).or_else(|| enumerators.get(&None))); - - let enumerator = enumerator.and_then(|member| { - Some(Box::new(self.parse_struct_member( - eval_ctx, - member, - data.as_ref(), - )?)) - }); - - RustEnumVariable { - identity, - type_id: Some(type_id), - type_name: self.r#type.type_name(type_id), - value: enumerator, - raw_address: data.and_then(|d| d.address), - } - } - - fn parse_pointer( - &self, - identity: VariableIdentity, - data: Option, - type_id: TypeIdentity, - target_type: Option, - ) -> PointerVariable { - let mb_ptr = data - .as_ref() - .map(|v| scalar_from_bytes::<*const ()>(&v.raw_data)); - - PointerVariable { - identity, - type_id: Some(type_id), - type_name: self.r#type.type_name(type_id).or_else(|| { - Some(format!( - "*{deref_type}", - deref_type = self.r#type.type_name(target_type?)? - )) - }), - value: mb_ptr, - target_type, - target_type_size: None, - raw_address: data.and_then(|d| d.address), - } - } - - fn parse_inner( - &self, - eval_ctx: &EvaluationContext, - identity: VariableIdentity, - data: Option, - type_id: TypeIdentity, - ) -> Option { - match &self.r#type.types[&type_id] { - TypeDeclaration::Scalar(scalar_type) => Some(VariableIR::Scalar(self.parse_scalar( - identity, - data, - type_id, - scalar_type, - ))), - TypeDeclaration::Structure { - namespaces: type_ns_h, - members, - type_params, - name: struct_name, - .. - } => { - let struct_var = self.parse_struct_variable( - eval_ctx, - identity, - data, - type_id, - type_params.clone(), - members, - ); - - let parser_ext = VariableParserExtension::new(self); - // Reinterpret structure if underline data type is: - // - Vector - // - String - // - &str - // - tls variable - // - hashmaps - // - hashset - // - btree map - // - btree set - // - vecdeque - // - cell/refcell - // - rc/arc - if struct_name.as_deref() == Some("&str") { - return Some(VariableIR::Specialized( - parser_ext.parse_str(eval_ctx, struct_var), - )); - }; - - if struct_name.as_deref() == Some("String") { - return Some(VariableIR::Specialized( - parser_ext.parse_string(eval_ctx, struct_var), - )); - }; - - if struct_name.as_ref().map(|name| name.starts_with("Vec")) == Some(true) - && type_ns_h.contains(&["vec"]) - { - return Some(VariableIR::Specialized(parser_ext.parse_vector( - eval_ctx, - struct_var, - type_params, - ))); - }; - - let rust_version = eval_ctx.rustc_version().unwrap_or_default(); - let is_tls_type = version_switch!( - rust_version, - (1, 0, 0) ..= (1, 76, u32::MAX) => type_ns_h.contains(&["std", "sys", "common", "thread_local", "fast_local"]), - (1, 77, 0) ..= (1, 77, u32::MAX) => type_ns_h.contains(&["std", "sys", "pal", "common", "thread_local", "fast_local"]), - (1, 78, 0) ..= (1, 80, u32::MAX) => type_ns_h.contains(&["std", "sys", "thread_local", "fast_local"]), - (1, 81, 0) ..= (1, u32::MAX, u32::MAX) => type_ns_h.contains(&["std", "sys", "thread_local"]), - ); - if is_tls_type == Some(true) { - return version_switch!( - rust_version, - (1, 0, 0) ..= (1, 79, u32::MAX) => Some(VariableIR::Specialized(parser_ext.parse_tls_old(struct_var, type_params))), - (1, 80, 0) ..= (1, u32::MAX, u32::MAX) => Some(VariableIR::Specialized(parser_ext.parse_tls(struct_var, type_params)?)), - ) - .expect("infallible: all rustc versions are covered"); - } - - if struct_name.as_ref().map(|name| name.starts_with("HashMap")) == Some(true) - && type_ns_h.contains(&["collections", "hash", "map"]) - { - return Some(VariableIR::Specialized( - parser_ext.parse_hashmap(eval_ctx, struct_var), - )); - }; - - if struct_name.as_ref().map(|name| name.starts_with("HashSet")) == Some(true) - && type_ns_h.contains(&["collections", "hash", "set"]) - { - return Some(VariableIR::Specialized( - parser_ext.parse_hashset(eval_ctx, struct_var), - )); - }; - - if struct_name - .as_ref() - .map(|name| name.starts_with("BTreeMap")) - == Some(true) - && type_ns_h.contains(&["collections", "btree", "map"]) - { - return Some(VariableIR::Specialized(parser_ext.parse_btree_map( - eval_ctx, - struct_var, - type_id, - type_params, - ))); - }; - - if struct_name - .as_ref() - .map(|name| name.starts_with("BTreeSet")) - == Some(true) - && type_ns_h.contains(&["collections", "btree", "set"]) - { - return Some(VariableIR::Specialized( - parser_ext.parse_btree_set(struct_var), - )); - }; - - if struct_name - .as_ref() - .map(|name| name.starts_with("VecDeque")) - == Some(true) - && type_ns_h.contains(&["collections", "vec_deque"]) - { - return Some(VariableIR::Specialized(parser_ext.parse_vec_dequeue( - eval_ctx, - struct_var, - type_params, - ))); - }; - - if struct_name.as_ref().map(|name| name.starts_with("Cell")) == Some(true) - && type_ns_h.contains(&["cell"]) - { - return Some(VariableIR::Specialized(parser_ext.parse_cell(struct_var))); - }; - - if struct_name.as_ref().map(|name| name.starts_with("RefCell")) == Some(true) - && type_ns_h.contains(&["cell"]) - { - return Some(VariableIR::Specialized( - parser_ext.parse_refcell(struct_var), - )); - }; - - if struct_name - .as_ref() - .map(|name| name.starts_with("Rc<") | name.starts_with("Weak<")) - == Some(true) - && type_ns_h.contains(&["rc"]) - { - return Some(VariableIR::Specialized(parser_ext.parse_rc(struct_var))); - }; - - if struct_name - .as_ref() - .map(|name| name.starts_with("Arc<") | name.starts_with("Weak<")) - == Some(true) - && type_ns_h.contains(&["sync"]) - { - return Some(VariableIR::Specialized(parser_ext.parse_arc(struct_var))); - }; - - if struct_name.as_ref().map(|name| name == "Uuid") == Some(true) - && type_ns_h.contains(&["uuid"]) - { - return Some(VariableIR::Specialized(parser_ext.parse_uuid(struct_var))); - }; - - Some(VariableIR::Struct(struct_var)) - } - TypeDeclaration::Array(decl) => Some(VariableIR::Array( - self.parse_array(eval_ctx, identity, data, type_id, decl), - )), - TypeDeclaration::CStyleEnum { - discr_type, - enumerators, - .. - } => Some(VariableIR::CEnum(self.parse_c_enum( - eval_ctx, - identity, - data, - type_id, - *discr_type, - enumerators, - ))), - TypeDeclaration::RustEnum { - discr_type, - enumerators, - .. - } => Some(VariableIR::RustEnum(self.parse_rust_enum( - eval_ctx, - identity, - data, - type_id, - discr_type.as_ref().map(|t| t.as_ref()), - enumerators, - ))), - TypeDeclaration::Pointer { target_type, .. } => Some(VariableIR::Pointer( - self.parse_pointer(identity, data, type_id, *target_type), - )), - TypeDeclaration::Union { members, .. } => { - let struct_var = self.parse_struct_variable( - eval_ctx, - identity, - data, - type_id, - HashMap::new(), - members, - ); - Some(VariableIR::Struct(struct_var)) - } - TypeDeclaration::Subroutine { return_type, .. } => { - let ret_type = return_type.and_then(|t_id| self.r#type.type_name(t_id)); - let fn_var = SubroutineVariable { - identity, - type_id: Some(type_id), - return_type_name: ret_type, - address: data.and_then(|d| d.address), - }; - Some(VariableIR::Subroutine(fn_var)) - } - TypeDeclaration::ModifiedType { - inner, modifier, .. - } => { - let in_debugee_loc = data.as_ref().and_then(|d| d.address); - Some(VariableIR::CModifiedVariable(CModifiedVariable { - identity: identity.clone(), - type_id: Some(type_id), - type_name: self.r#type.type_name(type_id), - modifier: *modifier, - value: inner.and_then(|inner_type| { - Some(Box::new( - self.parse_inner(eval_ctx, identity, data, inner_type)?, - )) - }), - address: in_debugee_loc, - })) - } - } - } - - pub fn parse( - self, - eval_ctx: &EvaluationContext, - identity: VariableIdentity, - value: Option, - ) -> Option { - self.parse_inner(eval_ctx, identity, value, self.r#type.root) - } -} - -/// Iterator for visits underline values in BFS order. -struct BfsIterator<'a> { - queue: VecDeque<&'a VariableIR>, -} - -impl<'a> Iterator for BfsIterator<'a> { - type Item = &'a VariableIR; - - fn next(&mut self) -> Option { - let next_item = self.queue.pop_front()?; - - match next_item { - VariableIR::Struct(r#struct) => { - r#struct - .members - .iter() - .for_each(|member| self.queue.push_back(member)); - } - VariableIR::Array(array) => { - if let Some(items) = array.items.as_ref() { - items.iter().for_each(|item| self.queue.push_back(item)) - } - } - VariableIR::RustEnum(r#enum) => { - if let Some(enumerator) = r#enum.value.as_ref() { - self.queue.push_back(enumerator) - } - } - VariableIR::Pointer(_) => {} - VariableIR::Specialized(spec) => match spec { - SpecializedVariableIR::Vector { original, .. } - | SpecializedVariableIR::VecDeque { original, .. } => { - original - .members - .iter() - .for_each(|member| self.queue.push_back(member)); - } - SpecializedVariableIR::String { original, .. } => { - original - .members - .iter() - .for_each(|member| self.queue.push_back(member)); - } - SpecializedVariableIR::Str { original, .. } => { - original - .members - .iter() - .for_each(|member| self.queue.push_back(member)); - } - SpecializedVariableIR::Tls { original, .. } => { - original - .members - .iter() - .for_each(|member| self.queue.push_back(member)); - } - SpecializedVariableIR::HashMap { original, .. } - | SpecializedVariableIR::BTreeMap { original, .. } => { - original - .members - .iter() - .for_each(|member| self.queue.push_back(member)); - } - SpecializedVariableIR::HashSet { original, .. } - | SpecializedVariableIR::BTreeSet { original, .. } => { - original - .members - .iter() - .for_each(|member| self.queue.push_back(member)); - } - SpecializedVariableIR::Cell { original, .. } - | SpecializedVariableIR::RefCell { original, .. } => { - original - .members - .iter() - .for_each(|member| self.queue.push_back(member)); - } - SpecializedVariableIR::Rc { .. } | SpecializedVariableIR::Arc { .. } => {} - SpecializedVariableIR::Uuid { .. } => {} - }, - _ => {} - } - - Some(next_item) - } -} - -#[inline(never)] -fn scalar_from_bytes(bytes: &Bytes) -> T { - let ptr = bytes.as_ptr(); - unsafe { std::ptr::read_unaligned::(ptr as *const T) } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::debugger::variable::specialization::VecVariable; - - #[test] - fn test_bfs_iterator() { - struct TestCase { - variable: VariableIR, - expected_order: Vec<&'static str>, - } - - let test_cases = vec![ - TestCase { - variable: VariableIR::Struct(StructVariable { - identity: VariableIdentity::no_namespace(Some("struct_1".to_owned())), - type_name: None, - type_id: None, - members: vec![ - VariableIR::Array(ArrayVariable { - identity: VariableIdentity::no_namespace(Some("array_1".to_owned())), - type_id: None, - type_name: None, - items: Some(vec![ - VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some( - "scalar_1".to_owned(), - )), - type_name: None, - value: None, - raw_address: None, - type_id: None, - }), - VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some( - "scalar_2".to_owned(), - )), - type_name: None, - value: None, - raw_address: None, - type_id: None, - }), - ]), - raw_address: None, - }), - VariableIR::Array(ArrayVariable { - identity: VariableIdentity::no_namespace(Some("array_2".to_owned())), - type_name: None, - type_id: None, - items: Some(vec![ - VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some( - "scalar_3".to_owned(), - )), - type_name: None, - value: None, - raw_address: None, - type_id: None, - }), - VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some( - "scalar_4".to_owned(), - )), - type_name: None, - value: None, - raw_address: None, - type_id: None, - }), - ]), - raw_address: None, - }), - ], - type_params: Default::default(), - raw_address: None, - }), - expected_order: vec![ - "struct_1", "array_1", "array_2", "scalar_1", "scalar_2", "scalar_3", - "scalar_4", - ], - }, - TestCase { - variable: VariableIR::Struct(StructVariable { - identity: VariableIdentity::no_namespace(Some("struct_1".to_owned())), - type_id: None, - type_name: None, - members: vec![ - VariableIR::Struct(StructVariable { - identity: VariableIdentity::no_namespace(Some("struct_2".to_owned())), - type_id: None, - type_name: None, - members: vec![ - VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some( - "scalar_1".to_owned(), - )), - type_id: None, - type_name: None, - value: None, - raw_address: None, - }), - VariableIR::RustEnum(RustEnumVariable { - identity: VariableIdentity::no_namespace(Some( - "enum_1".to_owned(), - )), - type_id: None, - type_name: None, - value: Some(Box::new(VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some( - "scalar_2".to_owned(), - )), - type_id: None, - type_name: None, - value: None, - raw_address: None, - }))), - raw_address: None, - }), - VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some( - "scalar_3".to_owned(), - )), - type_id: None, - type_name: None, - value: None, - raw_address: None, - }), - ], - type_params: Default::default(), - raw_address: None, - }), - VariableIR::Pointer(PointerVariable { - identity: VariableIdentity::no_namespace(Some("pointer_1".to_owned())), - type_id: None, - type_name: None, - value: None, - target_type: None, - target_type_size: None, - raw_address: None, - }), - ], - type_params: Default::default(), - raw_address: None, - }), - expected_order: vec![ - "struct_1", - "struct_2", - "pointer_1", - "scalar_1", - "enum_1", - "scalar_3", - "scalar_2", - ], - }, - ]; - - for tc in test_cases { - let iter = tc.variable.bfs_iterator(); - let names: Vec<_> = iter - .map(|g| match g { - VariableIR::Scalar(s) => s.identity.name.as_deref().unwrap(), - VariableIR::Struct(s) => s.identity.name.as_deref().unwrap(), - VariableIR::Array(a) => a.identity.name.as_deref().unwrap(), - VariableIR::CEnum(e) => e.identity.name.as_deref().unwrap(), - VariableIR::RustEnum(e) => e.identity.name.as_deref().unwrap(), - VariableIR::Pointer(p) => p.identity.name.as_deref().unwrap(), - _ => { - unreachable!() - } - }) - .collect(); - assert_eq!(tc.expected_order, names); - } - } - - // test helpers -------------------------------------------------------------------------------- - // - fn make_scalar_var_ir( - name: Option<&str>, - type_name: &str, - scalar: SupportedScalar, - ) -> VariableIR { - VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - type_id: None, - type_name: Some(type_name.into()), - value: Some(scalar), - raw_address: None, - }) - } - - fn make_str_var_ir(name: Option<&str>, val: &str) -> VariableIR { - VariableIR::Specialized(SpecializedVariableIR::Str { - string: Some(StrVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - value: val.to_string(), - }), - original: StructVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - ..Default::default() - }, - }) - } - - fn make_string_var_ir(name: Option<&str>, val: &str) -> VariableIR { - VariableIR::Specialized(SpecializedVariableIR::String { - string: Some(StringVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - value: val.to_string(), - }), - original: StructVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - ..Default::default() - }, - }) - } - - fn make_vec_var_ir(name: Option<&str>, items: Vec) -> VecVariable { - let items_len = items.len(); - VecVariable { - structure: StructVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - type_id: None, - type_name: Some("vec".to_string()), - members: vec![ - VariableIR::Array(ArrayVariable { - identity: VariableIdentity::default(), - type_id: None, - type_name: Some("[item]".to_string()), - items: Some(items), - raw_address: None, - }), - VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some("cap".to_string())), - type_id: None, - type_name: Some("usize".to_owned()), - value: Some(SupportedScalar::Usize(items_len)), - raw_address: None, - }), - ], - type_params: HashMap::default(), - raw_address: None, - }, - } - } - - fn make_vector_var_ir(name: Option<&str>, items: Vec) -> VariableIR { - VariableIR::Specialized(SpecializedVariableIR::Vector { - vec: Some(make_vec_var_ir(name, items)), - original: StructVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - ..Default::default() - }, - }) - } - - fn make_vecdeque_var_ir(name: Option<&str>, items: Vec) -> VariableIR { - VariableIR::Specialized(SpecializedVariableIR::VecDeque { - vec: Some(make_vec_var_ir(name, items)), - original: StructVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - ..Default::default() - }, - }) - } - - fn make_hashset_var_ir(name: Option<&str>, items: Vec) -> VariableIR { - VariableIR::Specialized(SpecializedVariableIR::HashSet { - set: Some(HashSetVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - type_name: Some("hashset".to_string()), - items, - }), - original: StructVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - ..Default::default() - }, - }) - } - - fn make_btreeset_var_ir(name: Option<&str>, items: Vec) -> VariableIR { - VariableIR::Specialized(SpecializedVariableIR::BTreeSet { - set: Some(HashSetVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - type_name: Some("btreeset".to_string()), - items, - }), - original: StructVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - ..Default::default() - }, - }) - } - //---------------------------------------------------------------------------------------------- - - #[test] - fn test_equal_with_literal() { - struct TestCase { - variable: VariableIR, - eq_literal: Literal, - neq_literals: Vec, - } - - let test_cases = [ - TestCase { - variable: make_scalar_var_ir(None, "i8", SupportedScalar::I8(8)), - eq_literal: Literal::Int(8), - neq_literals: vec![Literal::Int(9)], - }, - TestCase { - variable: make_scalar_var_ir(None, "i32", SupportedScalar::I32(32)), - eq_literal: Literal::Int(32), - neq_literals: vec![Literal::Int(33)], - }, - TestCase { - variable: make_scalar_var_ir(None, "isize", SupportedScalar::Isize(-1234)), - eq_literal: Literal::Int(-1234), - neq_literals: vec![Literal::Int(-1233)], - }, - TestCase { - variable: make_scalar_var_ir(None, "u8", SupportedScalar::U8(8)), - eq_literal: Literal::Int(8), - neq_literals: vec![Literal::Int(9)], - }, - TestCase { - variable: make_scalar_var_ir(None, "u32", SupportedScalar::U32(32)), - eq_literal: Literal::Int(32), - neq_literals: vec![Literal::Int(33)], - }, - TestCase { - variable: make_scalar_var_ir(None, "usize", SupportedScalar::Usize(1234)), - eq_literal: Literal::Int(1234), - neq_literals: vec![Literal::Int(1235)], - }, - TestCase { - variable: make_scalar_var_ir(None, "f32", SupportedScalar::F32(1.1)), - eq_literal: Literal::Float(1.1), - neq_literals: vec![Literal::Float(1.2)], - }, - TestCase { - variable: make_scalar_var_ir(None, "f64", SupportedScalar::F64(-2.2)), - eq_literal: Literal::Float(-2.2), - neq_literals: vec![Literal::Float(2.2)], - }, - TestCase { - variable: make_scalar_var_ir(None, "bool", SupportedScalar::Bool(true)), - eq_literal: Literal::Bool(true), - neq_literals: vec![Literal::Bool(false)], - }, - TestCase { - variable: make_scalar_var_ir(None, "char", SupportedScalar::Char('b')), - eq_literal: Literal::String("b".into()), - neq_literals: vec![Literal::String("c".into())], - }, - TestCase { - variable: VariableIR::Pointer(PointerVariable { - identity: VariableIdentity::default(), - target_type: None, - type_id: None, - type_name: Some("ptr".into()), - value: Some(123usize as *const ()), - raw_address: None, - target_type_size: None, - }), - eq_literal: Literal::Address(123), - neq_literals: vec![Literal::Address(124), Literal::Int(123)], - }, - TestCase { - variable: VariableIR::Pointer(PointerVariable { - identity: VariableIdentity::default(), - target_type: None, - type_id: None, - type_name: Some("MyPtr".into()), - value: Some(123usize as *const ()), - raw_address: None, - target_type_size: None, - }), - eq_literal: Literal::Address(123), - neq_literals: vec![Literal::Address(124), Literal::Int(123)], - }, - TestCase { - variable: VariableIR::CEnum(CEnumVariable { - identity: VariableIdentity::default(), - type_id: None, - type_name: Some("MyEnum".into()), - value: Some("Variant1".into()), - raw_address: None, - }), - eq_literal: Literal::EnumVariant("Variant1".to_string(), None), - neq_literals: vec![ - Literal::EnumVariant("Variant2".to_string(), None), - Literal::String("Variant1".to_string()), - ], - }, - TestCase { - variable: VariableIR::RustEnum(RustEnumVariable { - identity: VariableIdentity::default(), - type_id: None, - type_name: Some("MyEnum".into()), - value: Some(Box::new(VariableIR::Struct(StructVariable { - identity: VariableIdentity::no_namespace(Some("Variant1".to_string())), - type_id: None, - type_name: None, - members: vec![VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some("Variant1".to_string())), - type_id: None, - type_name: Some("int".into()), - value: Some(SupportedScalar::I64(100)), - raw_address: None, - })], - type_params: Default::default(), - raw_address: None, - }))), - raw_address: None, - }), - eq_literal: Literal::EnumVariant( - "Variant1".to_string(), - Some(Box::new(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(100)), - ])))), - ), - neq_literals: vec![ - Literal::EnumVariant("Variant1".to_string(), Some(Box::new(Literal::Int(101)))), - Literal::EnumVariant("Variant2".to_string(), Some(Box::new(Literal::Int(100)))), - Literal::String("Variant1".to_string()), - ], - }, - ]; - - for tc in test_cases { - assert!(tc.variable.clone().match_literal(&tc.eq_literal)); - for neq_lit in tc.neq_literals { - assert!(!tc.variable.clone().match_literal(&neq_lit)); - } - } - } - - #[test] - fn test_equal_with_complex_literal() { - struct TestCase { - variable: VariableIR, - eq_literals: Vec, - neq_literals: Vec, - } - - let test_cases = [ - TestCase { - variable: make_str_var_ir(None, "str1"), - eq_literals: vec![Literal::String("str1".to_string())], - neq_literals: vec![Literal::String("str2".to_string()), Literal::Int(1)], - }, - TestCase { - variable: make_string_var_ir(None, "string1"), - eq_literals: vec![Literal::String("string1".to_string())], - neq_literals: vec![Literal::String("string2".to_string()), Literal::Int(1)], - }, - TestCase { - variable: VariableIR::Specialized(SpecializedVariableIR::Uuid { - value: Some([ - 0xd0, 0x60, 0x66, 0x29, 0x78, 0x6a, 0x44, 0xbe, 0x9d, 0x49, 0xb7, 0x02, - 0x0f, 0x3e, 0xb0, 0x5a, - ]), - original: StructVariable::default(), - }), - eq_literals: vec![Literal::String( - "d0606629-786a-44be-9d49-b7020f3eb05a".to_string(), - )], - neq_literals: vec![Literal::String( - "d0606629-786a-44be-9d49-b7020f3eb05b".to_string(), - )], - }, - TestCase { - variable: make_vector_var_ir( - None, - vec![ - make_scalar_var_ir(None, "char", SupportedScalar::Char('a')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('b')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - ], - ), - eq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Wildcard, - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - neq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Wildcard, - ])), - ], - }, - TestCase { - variable: make_vector_var_ir( - None, - vec![ - make_scalar_var_ir(None, "char", SupportedScalar::Char('a')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('b')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - ], - ), - eq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Wildcard, - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - neq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Wildcard, - ])), - ], - }, - TestCase { - variable: make_vecdeque_var_ir( - None, - vec![ - make_scalar_var_ir(None, "char", SupportedScalar::Char('a')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('b')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - ], - ), - eq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Wildcard, - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - neq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Wildcard, - ])), - ], - }, - TestCase { - variable: make_hashset_var_ir( - None, - vec![ - make_scalar_var_ir(None, "char", SupportedScalar::Char('a')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('b')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - ], - ), - eq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Wildcard, - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - neq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - }, - TestCase { - variable: make_btreeset_var_ir( - None, - vec![ - make_scalar_var_ir(None, "char", SupportedScalar::Char('a')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('b')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - ], - ), - eq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Wildcard, - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - neq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - }, - TestCase { - variable: VariableIR::Specialized(SpecializedVariableIR::Cell { - value: Some(Box::new(make_scalar_var_ir( - None, - "int", - SupportedScalar::I64(100), - ))), - original: StructVariable::default(), - }), - eq_literals: vec![Literal::Int(100)], - neq_literals: vec![Literal::Int(101), Literal::Float(100.1)], - }, - TestCase { - variable: VariableIR::Array(ArrayVariable { - identity: Default::default(), - type_id: None, - type_name: Some("array_str".to_string()), - items: Some(vec![ - make_str_var_ir(None, "ab"), - make_str_var_ir(None, "cd"), - make_str_var_ir(None, "ef"), - ]), - raw_address: None, - }), - eq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("ab".to_string())), - LiteralOrWildcard::Literal(Literal::String("cd".to_string())), - LiteralOrWildcard::Literal(Literal::String("ef".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("ab".to_string())), - LiteralOrWildcard::Literal(Literal::String("cd".to_string())), - LiteralOrWildcard::Wildcard, - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - neq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("ab".to_string())), - LiteralOrWildcard::Literal(Literal::String("cd".to_string())), - LiteralOrWildcard::Literal(Literal::String("gj".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("ab".to_string())), - LiteralOrWildcard::Literal(Literal::String("cd".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("ab".to_string())), - LiteralOrWildcard::Literal(Literal::String("cd".to_string())), - LiteralOrWildcard::Literal(Literal::String("ef".to_string())), - LiteralOrWildcard::Literal(Literal::String("gj".to_string())), - ])), - ], - }, - TestCase { - variable: VariableIR::Struct(StructVariable { - identity: Default::default(), - type_id: None, - type_name: Some("MyStruct".to_string()), - members: vec![ - make_str_var_ir(Some("str_field"), "str1"), - make_vector_var_ir( - Some("vec_field"), - vec![ - make_scalar_var_ir(None, "", SupportedScalar::I8(1)), - make_scalar_var_ir(None, "", SupportedScalar::I8(2)), - ], - ), - make_scalar_var_ir(Some("bool_field"), "", SupportedScalar::Bool(true)), - ], - type_params: Default::default(), - raw_address: None, - }), - eq_literals: vec![ - Literal::AssocArray(HashMap::from([ - ( - "str_field".to_string(), - LiteralOrWildcard::Literal(Literal::String("str1".to_string())), - ), - ( - "vec_field".to_string(), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - LiteralOrWildcard::Literal(Literal::Int(2)), - ]))), - ), - ( - "bool_field".to_string(), - LiteralOrWildcard::Literal(Literal::Bool(true)), - ), - ])), - Literal::AssocArray(HashMap::from([ - ( - "str_field".to_string(), - LiteralOrWildcard::Literal(Literal::String("str1".to_string())), - ), - ( - "vec_field".to_string(), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - LiteralOrWildcard::Wildcard, - ]))), - ), - ("bool_field".to_string(), LiteralOrWildcard::Wildcard), - ])), - ], - neq_literals: vec![ - Literal::AssocArray(HashMap::from([ - ( - "str_field".to_string(), - LiteralOrWildcard::Literal(Literal::String("str2".to_string())), - ), - ( - "vec_field".to_string(), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - LiteralOrWildcard::Literal(Literal::Int(2)), - ]))), - ), - ( - "bool_field".to_string(), - LiteralOrWildcard::Literal(Literal::Bool(true)), - ), - ])), - Literal::AssocArray(HashMap::from([ - ( - "str_field".to_string(), - LiteralOrWildcard::Literal(Literal::String("str1".to_string())), - ), - ( - "vec_field".to_string(), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - ]))), - ), - ( - "bool_field".to_string(), - LiteralOrWildcard::Literal(Literal::Bool(true)), - ), - ])), - ], - }, - TestCase { - variable: VariableIR::Struct(StructVariable { - identity: Default::default(), - type_id: None, - type_name: Some("MyTuple".to_string()), - members: vec![ - make_str_var_ir(None, "str1"), - make_vector_var_ir( - None, - vec![ - make_scalar_var_ir(None, "", SupportedScalar::I8(1)), - make_scalar_var_ir(None, "", SupportedScalar::I8(2)), - ], - ), - make_scalar_var_ir(None, "", SupportedScalar::Bool(true)), - ], - type_params: Default::default(), - raw_address: None, - }), - eq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("str1".to_string())), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - LiteralOrWildcard::Literal(Literal::Int(2)), - ]))), - LiteralOrWildcard::Literal(Literal::Bool(true)), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("str1".to_string())), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - LiteralOrWildcard::Wildcard, - ]))), - LiteralOrWildcard::Wildcard, - ])), - ], - neq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("str1".to_string())), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - LiteralOrWildcard::Literal(Literal::Int(2)), - ]))), - LiteralOrWildcard::Literal(Literal::Bool(false)), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("str1".to_string())), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - ]))), - LiteralOrWildcard::Literal(Literal::Bool(true)), - ])), - ], - }, - ]; - - for tc in test_cases { - for eq_lit in tc.eq_literals { - assert!(tc.variable.clone().match_literal(&eq_lit)); - } - for neq_lit in tc.neq_literals { - assert!(!tc.variable.clone().match_literal(&neq_lit)); - } - } - } + /// Binary size. + pub size: usize, } diff --git a/src/debugger/variable/render.rs b/src/debugger/variable/render.rs index 0531072..3b30b2a 100644 --- a/src/debugger/variable/render.rs +++ b/src/debugger/variable/render.rs @@ -1,235 +1,197 @@ -use crate::debugger::variable::SpecializedVariableIR; -use crate::debugger::variable::VariableIR; +use crate::debugger::debugee::dwarf::r#type::TypeIdentity; +use crate::debugger::variable::value::{ArrayItem, Member, SpecializedValue, Value}; +use nix::errno::Errno; +use nix::libc; +use nix::sys::time::TimeSpec; +use once_cell::sync::Lazy; use std::borrow::Cow; use std::fmt::{Debug, Formatter}; +use std::mem::MaybeUninit; +use std::ops::Sub; +use std::time::Duration; +/// Layout of a value from debugee program. +/// Used by UI for representing value to a user. pub enum ValueLayout<'a> { + /// Value already rendered, just print it! PreRendered(Cow<'a, str>), - Referential { - addr: *const (), - }, - Wrapped(&'a VariableIR), - Structure { - members: &'a [VariableIR], - }, - List { - members: &'a [VariableIR], - indexed: bool, - }, - Map(&'a [(VariableIR, VariableIR)]), + /// Value is an address in debugee memory. + Referential(*const ()), + /// Value wraps another value. + Wrapped(&'a Value), + /// Value is a structure. + Structure(&'a [Member]), + /// Value is a list with indexed elements. + IndexedList(&'a [ArrayItem]), + /// Value is an unordered list. + NonIndexedList(&'a [Value]), + /// Value is a map where keys and values are values too. + Map(&'a [(Value, Value)]), } impl<'a> Debug for ValueLayout<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { ValueLayout::PreRendered(s) => f.debug_tuple("PreRendered").field(s).finish(), - ValueLayout::Referential { addr, .. } => { - f.debug_tuple("Referential").field(addr).finish() - } - ValueLayout::Wrapped(v) => f.debug_tuple("Wrapped").field(v).finish(), - ValueLayout::Structure { members } => { + ValueLayout::Referential(addr) => f.debug_tuple("Referential").field(addr).finish(), + ValueLayout::Wrapped(v) => f.debug_tuple("Wrapped").field(&v).finish(), + ValueLayout::Structure(members) => { f.debug_struct("Nested").field("members", members).finish() } ValueLayout::Map(kvs) => { let mut list = f.debug_list(); for kv in kvs.iter() { - list.entry(kv); + list.entry(&kv); } list.finish() } - ValueLayout::List { members, indexed } => f - .debug_struct("List") - .field("members", members) - .field("indexed", indexed) - .finish(), + ValueLayout::IndexedList(items) => { + f.debug_struct("List").field("items", items).finish() + } + ValueLayout::NonIndexedList(items) => { + f.debug_struct("List").field("items", items).finish() + } } } } -pub trait RenderRepr { - fn name(&self) -> String; - fn r#type(&self) -> &str; - fn value(&self) -> Option; +pub trait RenderValue { + /// Return type identity for rendering. + fn r#type(&self) -> &TypeIdentity; + + /// Return value layout for rendering. + fn value_layout(&self) -> Option; } -impl RenderRepr for VariableIR { - fn name(&self) -> String { - self.identity().to_string() - } +impl RenderValue for Value { + fn r#type(&self) -> &TypeIdentity { + static STRING_TYPE: Lazy = Lazy::new(|| TypeIdentity::no_namespace("String")); + static STR_TYPE: Lazy = Lazy::new(|| TypeIdentity::no_namespace("&str")); + static UNKNOWN_TYPE: Lazy = Lazy::new(TypeIdentity::unknown); - fn r#type(&self) -> &str { - let r#type = match self { - VariableIR::Scalar(s) => &s.type_name, - VariableIR::Struct(s) => &s.type_name, - VariableIR::Array(a) => &a.type_name, - VariableIR::CEnum(e) => &e.type_name, - VariableIR::RustEnum(e) => &e.type_name, - VariableIR::Pointer(p) => &p.type_name, - VariableIR::Specialized(spec) => match spec { - SpecializedVariableIR::Vector { vec, original } - | SpecializedVariableIR::VecDeque { vec, original } => match vec { - None => &original.type_name, - Some(v) => &v.structure.type_name, - }, - SpecializedVariableIR::String { .. } => return "String", - SpecializedVariableIR::Str { .. } => return "&str", - SpecializedVariableIR::Tls { - tls_var: value, - original, - .. - } => match value { - None => &original.type_name, - Some(v) => &v.inner_type, - }, - SpecializedVariableIR::HashMap { map, original } => match map { - None => &original.type_name, - Some(map) => &map.type_name, - }, - SpecializedVariableIR::HashSet { set, original } => match set { - None => &original.type_name, - Some(set) => &set.type_name, - }, - SpecializedVariableIR::BTreeMap { map, original } => match map { - None => &original.type_name, - Some(map) => &map.type_name, - }, - SpecializedVariableIR::BTreeSet { set, original } => match set { - None => &original.type_name, - Some(set) => &set.type_name, - }, - SpecializedVariableIR::Cell { original, .. } - | SpecializedVariableIR::RefCell { original, .. } => &original.type_name, - SpecializedVariableIR::Rc { original, .. } - | SpecializedVariableIR::Arc { original, .. } => &original.type_name, - SpecializedVariableIR::Uuid { original, .. } => &original.type_name, + match self { + Value::Scalar(s) => &s.type_ident, + Value::Struct(s) => &s.type_ident, + Value::Array(a) => &a.type_ident, + Value::CEnum(e) => &e.type_ident, + Value::RustEnum(e) => &e.type_ident, + Value::Pointer(p) => &p.type_ident, + Value::Specialized { + value: Some(spec_val), + original, + } => match spec_val { + SpecializedValue::Vector(vec) | SpecializedValue::VecDeque(vec) => { + &vec.structure.type_ident + } + SpecializedValue::String { .. } => &STRING_TYPE, + SpecializedValue::Str { .. } => &STR_TYPE, + SpecializedValue::Tls(value) => &value.inner_type, + SpecializedValue::HashMap(map) => &map.type_ident, + SpecializedValue::HashSet(set) => &set.type_ident, + SpecializedValue::BTreeMap(map) => &map.type_ident, + SpecializedValue::BTreeSet(set) => &set.type_ident, + SpecializedValue::Cell(_) | SpecializedValue::RefCell(_) => &original.type_ident, + SpecializedValue::Rc(_) | SpecializedValue::Arc(_) => &original.type_ident, + SpecializedValue::Uuid(_) => &original.type_ident, + SpecializedValue::SystemTime(_) => &original.type_ident, + SpecializedValue::Instant(_) => &original.type_ident, }, - VariableIR::Subroutine(_) => { - // currently this line is unreachable cause dereference fn pointer is forbidden - &None + Value::Specialized { original, .. } => &original.type_ident, + Value::Subroutine(_) => { + // currently this line is unreachable because dereference of fn pointer is forbidden + &UNKNOWN_TYPE } - VariableIR::CModifiedVariable(v) => &v.type_name, - }; - r#type.as_deref().unwrap_or("unknown") + Value::CModifiedVariable(v) => &v.type_ident, + } } - fn value(&self) -> Option { + fn value_layout(&self) -> Option { let value_repr = match self { - VariableIR::Scalar(scalar) => { + Value::Scalar(scalar) => { ValueLayout::PreRendered(Cow::Owned(scalar.value.as_ref()?.to_string())) } - VariableIR::Struct(r#struct) => ValueLayout::Structure { - members: r#struct.members.as_ref(), - }, - VariableIR::Array(array) => ValueLayout::List { - members: array.items.as_deref()?, - indexed: true, - }, - VariableIR::CEnum(r#enum) => { - ValueLayout::PreRendered(Cow::Borrowed(r#enum.value.as_ref()?)) + Value::Struct(r#struct) => ValueLayout::Structure(r#struct.members.as_ref()), + Value::Array(array) => ValueLayout::IndexedList(array.items.as_deref()?), + Value::CEnum(r#enum) => ValueLayout::PreRendered(Cow::Borrowed(r#enum.value.as_ref()?)), + Value::RustEnum(r#enum) => { + let enum_val = &r#enum.value.as_ref()?.value; + ValueLayout::Wrapped(enum_val) } - VariableIR::RustEnum(r#enum) => ValueLayout::Wrapped(r#enum.value.as_ref()?), - VariableIR::Pointer(pointer) => { + Value::Pointer(pointer) => { let ptr = pointer.value?; - ValueLayout::Referential { addr: ptr } + ValueLayout::Referential(ptr) } - VariableIR::Specialized(spec) => match spec { - SpecializedVariableIR::Vector { vec, original } - | SpecializedVariableIR::VecDeque { vec, original } => match vec { - None => ValueLayout::Structure { - members: original.members.as_ref(), - }, - Some(v) => ValueLayout::List { - members: v.structure.members.as_ref(), - indexed: true, - }, - }, - SpecializedVariableIR::String { string, original } => match string { - None => ValueLayout::Structure { - members: original.members.as_ref(), - }, - Some(s) => ValueLayout::PreRendered(Cow::Borrowed(&s.value)), - }, - SpecializedVariableIR::Str { string, original } => match string { - None => ValueLayout::Structure { - members: original.members.as_ref(), - }, - Some(s) => ValueLayout::PreRendered(Cow::Borrowed(&s.value)), - }, - SpecializedVariableIR::Tls { - tls_var: value, - original, - } => match value { - None => ValueLayout::Structure { - members: original.members.as_ref(), - }, - Some(ref tls_val) => match tls_val.inner_value.as_ref() { - None => ValueLayout::PreRendered(Cow::Borrowed("uninit")), - Some(tls_inner_val) => tls_inner_val.value()?, - }, - }, - SpecializedVariableIR::HashMap { map, original } => match map { - None => ValueLayout::Structure { - members: original.members.as_ref(), - }, - Some(map) => ValueLayout::Map(&map.kv_items), - }, - SpecializedVariableIR::HashSet { set, original } => match set { - None => ValueLayout::Structure { - members: original.members.as_ref(), - }, - Some(set) => ValueLayout::List { - members: &set.items, - indexed: false, - }, - }, - SpecializedVariableIR::BTreeMap { map, original } => match map { - None => ValueLayout::Structure { - members: original.members.as_ref(), - }, - Some(map) => ValueLayout::Map(&map.kv_items), - }, - SpecializedVariableIR::BTreeSet { set, original } => match set { - None => ValueLayout::Structure { - members: original.members.as_ref(), - }, - Some(set) => ValueLayout::List { - members: &set.items, - indexed: false, - }, - }, - SpecializedVariableIR::Cell { value, original } - | SpecializedVariableIR::RefCell { value, original } => match value { - Some(v) => v.value()?, - None => ValueLayout::Structure { - members: original.members.as_ref(), - }, - }, - SpecializedVariableIR::Rc { value, original } - | SpecializedVariableIR::Arc { value, original } => match value { - None => ValueLayout::Structure { - members: original.members.as_ref(), - }, - Some(pointer) => { - let ptr = pointer.value?; - ValueLayout::Referential { addr: ptr } - } - }, - SpecializedVariableIR::Uuid { value, original } => match value { - None => ValueLayout::Structure { - members: original.members.as_ref(), - }, - Some(array) => { - let uuid = uuid::Uuid::from_slice(array).expect("infallible"); - ValueLayout::PreRendered(Cow::Owned(uuid.to_string())) - } - }, + Value::Specialized { + value: Some(spec_val), + .. + } => match spec_val { + SpecializedValue::Vector(vec) | SpecializedValue::VecDeque(vec) => { + ValueLayout::Structure(vec.structure.members.as_ref()) + } + SpecializedValue::String(string) => { + ValueLayout::PreRendered(Cow::Borrowed(&string.value)) + } + SpecializedValue::Str(string) => { + ValueLayout::PreRendered(Cow::Borrowed(&string.value)) + } + SpecializedValue::Tls(tls_value) => match tls_value.inner_value.as_ref() { + None => ValueLayout::PreRendered(Cow::Borrowed("uninit")), + Some(tls_inner_val) => tls_inner_val.value_layout()?, + }, + SpecializedValue::HashMap(map) => ValueLayout::Map(&map.kv_items), + SpecializedValue::HashSet(set) => ValueLayout::NonIndexedList(&set.items), + SpecializedValue::BTreeMap(map) => ValueLayout::Map(&map.kv_items), + SpecializedValue::BTreeSet(set) => ValueLayout::NonIndexedList(&set.items), + SpecializedValue::Cell(cell) | SpecializedValue::RefCell(cell) => { + cell.value_layout()? + } + SpecializedValue::Rc(ptr) | SpecializedValue::Arc(ptr) => { + let ptr = ptr.value?; + ValueLayout::Referential(ptr) + } + SpecializedValue::Uuid(bytes) => { + let uuid = uuid::Uuid::from_slice(bytes).expect("infallible"); + ValueLayout::PreRendered(Cow::Owned(uuid.to_string())) + } + SpecializedValue::SystemTime((sec, n_sec)) => { + let mb_dt = chrono::DateTime::from_timestamp(*sec, *n_sec); + let dt_rendered = mb_dt + .map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string()) + .unwrap_or("Broken date time".to_string()); + ValueLayout::PreRendered(Cow::Owned(dt_rendered)) + } + SpecializedValue::Instant((sec, n_sec)) => { + let now = now_timespec().expect("broken system clock"); + let instant = TimeSpec::new(*sec, *n_sec as i64); + let render = if now > instant { + let from_instant = Duration::from(now.sub(instant)); + format!("already happened {} seconds ago ", from_instant.as_secs()) + } else { + let from_now = Duration::from(instant.sub(now)); + format!("{} seconds from now", from_now.as_secs()) + }; + ValueLayout::PreRendered(Cow::Owned(render)) + } }, - VariableIR::Subroutine(_) => { - // currently this line is unreachable a cause dereference fn pointer is forbidden + Value::Specialized { original, .. } => { + ValueLayout::Structure(original.members.as_ref()) + } + Value::Subroutine(_) => { + // currently this line is unreachable because dereference of fn pointer is forbidden return None; } - VariableIR::CModifiedVariable(v) => ValueLayout::Wrapped(v.value.as_ref()?), + Value::CModifiedVariable(v) => ValueLayout::Wrapped(v.value.as_ref()?), }; Some(value_repr) } } + +fn now_timespec() -> Result { + let mut t = MaybeUninit::uninit(); + let res = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, t.as_mut_ptr()) }; + if res == -1 { + return Err(Errno::last()); + } + let t = unsafe { t.assume_init() }; + Ok(TimeSpec::new(t.tv_sec, t.tv_nsec)) +} diff --git a/src/debugger/variable/select.rs b/src/debugger/variable/select.rs index 1934be9..e69de29 100644 --- a/src/debugger/variable/select.rs +++ b/src/debugger/variable/select.rs @@ -1,499 +0,0 @@ -use crate::debugger::debugee::dwarf; -use crate::debugger::debugee::dwarf::r#type::ComplexType; -use crate::debugger::debugee::dwarf::unit::{DieRef, Node, VariableDie}; -use crate::debugger::debugee::dwarf::{ - AsAllocatedData, ContextualDieRef, EndianArcSlice, NamespaceHierarchy, -}; -use crate::debugger::error::Error; -use crate::debugger::error::Error::FunctionNotFound; -use crate::debugger::variable::{AssumeError, ParsingError, VariableIR, VariableIdentity}; -use crate::debugger::Error::TypeNotFound; -use crate::debugger::{variable, Debugger}; -use crate::{ctx_resolve_unit_call, weak_error}; -use bytes::Bytes; -use gimli::{Attribute, DebugInfoOffset, Range, UnitOffset}; -use std::collections::hash_map::Entry; -use std::collections::HashMap; - -/// This die not exists in debug information. -/// It may be used to represent variables that are -/// declared by user, for example, using pointer cast operator. -struct VirtualVariableDie { - type_ref: DieRef, -} - -impl VirtualVariableDie { - fn of_unknown_type() -> Self { - Self { - type_ref: DieRef::Unit(UnitOffset(0)), - } - } -} - -impl AsAllocatedData for VirtualVariableDie { - fn name(&self) -> Option<&str> { - None - } - - fn type_ref(&self) -> Option { - Some(self.type_ref) - } - - fn location(&self) -> Option<&Attribute> { - None - } -} - -#[derive(Debug, PartialEq, Clone)] -pub enum VariableSelector { - Name { var_name: String, only_local: bool }, - Any, -} - -impl VariableSelector { - pub fn by_name(name: &str, only_local: bool) -> Self { - Self::Name { - var_name: name.to_string(), - only_local, - } - } -} - -/// Literal object. Using it for a searching element by key in key-value containers. -#[derive(Debug, PartialEq, Clone)] -pub enum Literal { - String(String), - Int(i64), - Float(f64), - Address(usize), - Bool(bool), - EnumVariant(String, Option>), - Array(Box<[LiteralOrWildcard]>), - AssocArray(HashMap), -} - -#[derive(Debug, PartialEq, Clone)] -pub enum LiteralOrWildcard { - Literal(Literal), - Wildcard, -} - -macro_rules! impl_equal { - ($lhs: expr, $rhs: expr, $lit: path) => { - if let $lit(lhs) = $lhs { - lhs == &$rhs - } else { - false - } - }; -} - -impl Literal { - pub fn equal_with_string(&self, rhs: &str) -> bool { - impl_equal!(self, rhs, Literal::String) - } - - pub fn equal_with_address(&self, rhs: usize) -> bool { - impl_equal!(self, rhs, Literal::Address) - } - - pub fn equal_with_bool(&self, rhs: bool) -> bool { - impl_equal!(self, rhs, Literal::Bool) - } - - pub fn equal_with_int(&self, rhs: i64) -> bool { - impl_equal!(self, rhs, Literal::Int) - } - - pub fn equal_with_float(&self, rhs: f64) -> bool { - const EPS: f64 = 0.0000001f64; - if let Literal::Float(float) = self { - let diff = (*float - rhs).abs(); - diff < EPS - } else { - false - } - } -} - -/// Object binary representation in debugee memory. -pub struct ObjectBinaryRepr { - /// Binary representation. - pub raw_data: Bytes, - /// Possible address of object data in debugee memory. - /// It may not exist if there is no debug information, or if an object is allocated in registers. - pub address: Option, - /// Binary size. - pub size: usize, -} - -/// Data query expression. -/// List of operations for select variables and their properties. -/// -/// Expression can be parsed from an input string like `*(*variable1.field2)[1]` -/// (see [`crate::ui::command`] module) -/// -/// Supported operations are: dereference, get an element by index, get field by name, make slice from a pointer. -#[derive(Debug, PartialEq, Clone)] -pub enum DQE { - Variable(VariableSelector), - PtrCast(usize, String), - Field(Box, String), - Index(Box, Literal), - Slice(Box, Option, Option), - Deref(Box), - Address(Box), - Canonic(Box), -} - -impl DQE { - /// Return boxed expression. - pub fn boxed(self) -> Box { - Box::new(self) - } -} - -/// Result of DQE evaluation. -pub struct DqeResult { - /// Variable intermediate representation. - pub variable: VariableIR, - /// PC ranges where value is valid, `None` for global or virtual variables. - pub scope: Option>, -} - -/// Evaluate `Expression` at current breakpoint (for current debugee location). -pub struct SelectExpressionEvaluator<'a> { - debugger: &'a Debugger, - expression: DQE, -} - -macro_rules! type_from_cache { - ($variable: expr, $cache: expr) => { - $variable - .die - .type_ref() - .and_then( - |type_ref| match $cache.entry(($variable.unit().id, type_ref)) { - Entry::Occupied(o) => Some(&*o.into_mut()), - Entry::Vacant(v) => $variable.r#type().map(|t| &*v.insert(t)), - }, - ) - .ok_or_else(|| ParsingError::Assume(AssumeError::NoType("variable"))) - }; -} - -impl<'a> SelectExpressionEvaluator<'a> { - pub fn new(debugger: &'a Debugger, expression: DQE) -> Self { - Self { - debugger, - expression, - } - } - - fn extract_variable_by_selector( - &self, - selector: &VariableSelector, - ) -> Result>, Error> { - let ctx = self.debugger.exploration_ctx(); - - let debugee = &self.debugger.debugee; - let current_func = debugee - .debug_info(ctx.location().pc)? - .find_function_by_pc(ctx.location().global_pc)? - .ok_or(FunctionNotFound(ctx.location().global_pc))?; - - let vars = match selector { - VariableSelector::Name { - var_name, - only_local: local, - } => { - let local_variants = current_func - .local_variable(ctx.location().global_pc, var_name) - .map(|v| vec![v]) - .unwrap_or_default(); - - let local = *local; - - // local variables is in priority anyway, if there are no local variables and - // selector allow non-locals then try to search in a whole object - if !local && local_variants.is_empty() { - debugee - .debug_info(ctx.location().pc)? - .find_variables(ctx.location(), var_name)? - } else { - local_variants - } - } - VariableSelector::Any => current_func.local_variables(ctx.location().global_pc), - }; - - Ok(vars) - } - - fn fill_virtual_ptr_variable( - &self, - vv: &'a mut VirtualVariableDie, - node: &'a Node, - type_name: &str, - ) -> Result, Error> { - let debugee = &self.debugger.debugee; - let (debug_info, offset_of_unit, offset_of_die) = debugee - .debug_info_all() - .iter() - .find_map(|&debug_info| { - let (offset_of_unit, offset_of_die) = debug_info.find_type_die_ref(type_name)?; - Some((debug_info, offset_of_unit, offset_of_die)) - }) - .ok_or(TypeNotFound)?; - let unit = debug_info - .find_unit(DebugInfoOffset(offset_of_unit.0 + offset_of_die.0)) - .ok_or(TypeNotFound)?; - - vv.type_ref = DieRef::Unit(offset_of_die); - let var = ContextualDieRef { - debug_info, - unit_idx: unit.idx(), - node, - die: vv, - }; - - Ok(var) - } - - /// Evaluate only variable names. - /// Only filter expression supported. - /// - /// # Panics - /// - /// This method will panic - /// if select expression contains any operators excluding a variable selector. - pub fn evaluate_names(&self) -> Result, Error> { - match &self.expression { - DQE::Variable(selector) => { - let vars = self.extract_variable_by_selector(selector)?; - Ok(vars - .into_iter() - .filter_map(|die| die.die.name().map(ToOwned::to_owned)) - .collect()) - } - _ => unreachable!("unexpected expression variant"), - } - } - - fn evaluate_inner(&self, expression: &DQE) -> Result, Error> { - // evaluate variable one by one in `evaluate_single_variable` method - // here just filter variables - match expression { - DQE::Variable(selector) => { - let vars = self.extract_variable_by_selector(selector)?; - let mut type_cache = self.debugger.type_cache.borrow_mut(); - - Ok(vars - .iter() - .filter_map(|var| { - let r#type = weak_error!(type_from_cache!(var, type_cache))?; - let var_ir = - self.evaluate_single_variable(&self.expression, var, r#type)?; - Some(DqeResult { - variable: var_ir, - scope: var.ranges().map(Box::from), - }) - }) - .collect()) - } - DQE::PtrCast(_, target_type_name) => { - let vars_ir = self.evaluate_from_ptr_cast(target_type_name)?; - Ok(vars_ir - .into_iter() - .map(|var_ir| DqeResult { - variable: var_ir, - scope: None, - }) - .collect()) - } - DQE::Field(expr, _) - | DQE::Index(expr, _) - | DQE::Slice(expr, _, _) - | DQE::Deref(expr) - | DQE::Address(expr) - | DQE::Canonic(expr) => self.evaluate_inner(expr), - } - } - - /// Create virtual DIE from type name and constant address. Evaluate expression then on this DIE. - fn evaluate_from_ptr_cast(&self, type_name: &str) -> Result, Error> { - let any_node = Node::new_leaf(None); - let mut var_die = VirtualVariableDie::of_unknown_type(); - let var_die_ref = self.fill_virtual_ptr_variable(&mut var_die, &any_node, type_name)?; - - let mut type_cache = self.debugger.type_cache.borrow_mut(); - let r#type = type_from_cache!(var_die_ref, type_cache)?; - - if let Some(v) = self.evaluate_single_variable(&self.expression, &var_die_ref, r#type) { - return Ok(vec![v]); - } - Ok(vec![]) - } - - /// Evaluate a select expression and returns list of matched variables. - pub fn evaluate(&self) -> Result, Error> { - self.evaluate_inner(&self.expression) - } - - /// Same as [`SelectExpressionEvaluator::evaluate_names`] but for function arguments. - pub fn evaluate_on_arguments_names(&self) -> Result, Error> { - match &self.expression { - DQE::Variable(selector) => { - let expl_ctx_loc = self.debugger.exploration_ctx().location(); - let current_function = self - .debugger - .debugee - .debug_info(expl_ctx_loc.pc)? - .find_function_by_pc(expl_ctx_loc.global_pc)? - .ok_or(FunctionNotFound(expl_ctx_loc.global_pc))?; - let params = current_function.parameters(); - - let params = match selector { - VariableSelector::Name { var_name, .. } => params - .into_iter() - .filter(|param| param.die.base_attributes.name.as_ref() == Some(var_name)) - .collect::>(), - VariableSelector::Any => params, - }; - - Ok(params - .into_iter() - .filter_map(|die| die.die.name().map(ToOwned::to_owned)) - .collect()) - } - _ => unreachable!("unexpected expression variant"), - } - } - - /// Same as [`SelectExpressionEvaluator::evaluate`] but for function arguments. - pub fn evaluate_on_arguments(&self) -> Result, Error> { - self.evaluate_on_arguments_inner(&self.expression) - } - - fn evaluate_on_arguments_inner(&self, expression: &DQE) -> Result, Error> { - match expression { - DQE::Variable(selector) => { - let expl_ctx_loc = self.debugger.exploration_ctx().location(); - let debugee = &self.debugger.debugee; - let current_function = debugee - .debug_info(expl_ctx_loc.pc)? - .find_function_by_pc(expl_ctx_loc.global_pc)? - .ok_or(FunctionNotFound(expl_ctx_loc.global_pc))?; - let params = current_function.parameters(); - - let params = match selector { - VariableSelector::Name { var_name, .. } => params - .into_iter() - .filter(|param| param.die.base_attributes.name.as_ref() == Some(var_name)) - .collect::>(), - VariableSelector::Any => params, - }; - - let mut type_cache = self.debugger.type_cache.borrow_mut(); - - Ok(params - .iter() - .filter_map(|var| { - let r#type = weak_error!(type_from_cache!(var, type_cache))?; - let var_ir = - self.evaluate_single_variable(&self.expression, var, r#type)?; - Some(DqeResult { - variable: var_ir, - scope: var.max_range().map(|r| { - let scope: Box<[Range]> = Box::new([r]); - scope - }), - }) - }) - .collect()) - } - DQE::PtrCast(_, target_type_name) => { - let vars = self.evaluate_from_ptr_cast(target_type_name)?; - Ok(vars - .into_iter() - .map(|v| DqeResult { - variable: v, - scope: None, - }) - .collect()) - } - DQE::Field(expr, _) - | DQE::Index(expr, _) - | DQE::Slice(expr, _, _) - | DQE::Deref(expr) - | DQE::Address(expr) - | DQE::Canonic(expr) => self.evaluate_on_arguments_inner(expr), - } - } - - fn evaluate_single_variable( - &self, - expression: &DQE, - variable_die: &ContextualDieRef, - r#type: &ComplexType, - ) -> Option { - let parser = variable::VariableParser::new(r#type); - - let evaluator = ctx_resolve_unit_call!(variable_die, evaluator, &self.debugger.debugee); - let evaluation_context = &dwarf::r#type::EvaluationContext { - evaluator: &evaluator, - expl_ctx: self.debugger.exploration_ctx(), - }; - - match expression { - DQE::Variable(_) => { - let data = variable_die.read_value( - self.debugger.exploration_ctx(), - &self.debugger.debugee, - r#type, - ); - parser.parse( - evaluation_context, - VariableIdentity::from_variable_die(variable_die), - data, - ) - } - DQE::PtrCast(addr, ..) => { - let data = ObjectBinaryRepr { - raw_data: Bytes::copy_from_slice(&(*addr).to_le_bytes()), - address: None, - size: std::mem::size_of::(), - }; - parser.parse( - evaluation_context, - VariableIdentity::new(NamespaceHierarchy::default(), None), - Some(data), - ) - } - DQE::Field(expr, field) => { - let var = self.evaluate_single_variable(expr, variable_die, r#type)?; - var.field(field) - } - DQE::Index(expr, idx) => { - let var = self.evaluate_single_variable(expr, variable_die, r#type)?; - var.index(idx) - } - DQE::Slice(expr, left, right) => { - let var = self.evaluate_single_variable(expr, variable_die, r#type)?; - var.slice(evaluation_context, &parser, *left, *right) - } - DQE::Deref(expr) => { - let var = self.evaluate_single_variable(expr, variable_die, r#type)?; - var.deref(evaluation_context, &parser) - } - DQE::Address(expr) => { - let var = self.evaluate_single_variable(expr, variable_die, r#type)?; - var.address(evaluation_context, &parser) - } - DQE::Canonic(expr) => { - let var = self.evaluate_single_variable(expr, variable_die, r#type)?; - Some(var.canonic()) - } - } - } -} diff --git a/src/debugger/variable/specialization/mod.rs b/src/debugger/variable/specialization/mod.rs deleted file mode 100644 index 287497a..0000000 --- a/src/debugger/variable/specialization/mod.rs +++ /dev/null @@ -1,1081 +0,0 @@ -mod btree; -mod hashbrown; - -use crate::debugger::debugee::dwarf::r#type::{EvaluationContext, TypeIdentity}; -use crate::debugger::variable::render::RenderRepr; -use crate::debugger::variable::select::ObjectBinaryRepr; -use crate::debugger::variable::specialization::btree::BTreeReflection; -use crate::debugger::variable::specialization::hashbrown::HashmapReflection; -use crate::debugger::variable::AssumeError::{ - TypeParameterNotFound, TypeParameterTypeNotFound, UnexpectedType, -}; -use crate::debugger::variable::ParsingError::Assume; -use crate::debugger::variable::{ - ArrayVariable, AssumeError, ParsingError, PointerVariable, ScalarVariable, StructVariable, - SupportedScalar, VariableIR, VariableIdentity, VariableParser, -}; -use crate::{debugger, version_switch, weak_error}; -use anyhow::Context; -use bytes::Bytes; -use fallible_iterator::FallibleIterator; -use itertools::Itertools; -use log::warn; -use std::collections::HashMap; -use AssumeError::{FieldNotFound, IncompleteInterp, UnknownSize}; - -/// During program execution, the debugger may encounter uninitialized variables. -/// For example look at this code: -/// ```rust -/// let res: Result<(), String> = Ok(()); -/// if let Err(e) = res { -/// unreachable!(); -/// } -/// ``` -/// -/// if stop debugger at line 2 and and consider a variable `e` - capacity of this vector -/// may be over 9000, this is obviously not the size that user expect. -/// Therefore, artificial restrictions on size and capacity are introduced. This behavior may be -/// changed in the future. -const LEN_GUARD: i64 = 10_000; -const CAP_GUARD: i64 = 10_000; - -fn guard_len(len: i64) -> i64 { - if len > LEN_GUARD { - LEN_GUARD - } else { - len - } -} - -fn guard_cap(cap: i64) -> i64 { - if cap > CAP_GUARD { - CAP_GUARD - } else { - cap - } -} - -#[derive(Clone, PartialEq)] -pub struct VecVariable { - pub structure: StructVariable, -} - -impl VecVariable { - pub fn slice(&mut self, left: Option, right: Option) { - debug_assert!(matches!( - self.structure.members.get_mut(0), - Some(VariableIR::Array(_)) - )); - - if let Some(VariableIR::Array(array)) = self.structure.members.get_mut(0) { - array.slice(left, right); - } - } -} - -#[derive(Clone, PartialEq)] -pub struct StringVariable { - pub identity: VariableIdentity, - pub value: String, -} - -#[derive(Clone, PartialEq)] -pub struct HashMapVariable { - pub identity: VariableIdentity, - pub type_name: Option, - pub kv_items: Vec<(VariableIR, VariableIR)>, -} - -#[derive(Clone, PartialEq)] -pub struct HashSetVariable { - pub identity: VariableIdentity, - pub type_name: Option, - pub items: Vec, -} - -#[derive(Clone, PartialEq)] -pub struct StrVariable { - pub identity: VariableIdentity, - pub value: String, -} - -#[derive(Clone, PartialEq)] -pub struct TlsVariable { - pub identity: VariableIdentity, - pub inner_value: Option>, - pub inner_type: Option, -} - -#[derive(Clone, PartialEq)] -pub enum SpecializedVariableIR { - Vector { - vec: Option, - original: StructVariable, - }, - VecDeque { - vec: Option, - original: StructVariable, - }, - HashMap { - map: Option, - original: StructVariable, - }, - HashSet { - set: Option, - original: StructVariable, - }, - BTreeMap { - map: Option, - original: StructVariable, - }, - BTreeSet { - set: Option, - original: StructVariable, - }, - String { - string: Option, - original: StructVariable, - }, - Str { - string: Option, - original: StructVariable, - }, - Tls { - tls_var: Option, - original: StructVariable, - }, - Cell { - value: Option>, - original: StructVariable, - }, - RefCell { - value: Option>, - original: StructVariable, - }, - Rc { - value: Option, - original: StructVariable, - }, - Arc { - value: Option, - original: StructVariable, - }, - Uuid { - value: Option<[u8; 16]>, - original: StructVariable, - }, -} - -impl SpecializedVariableIR { - pub(super) fn in_memory_location(&self) -> Option { - match self { - SpecializedVariableIR::Vector { original, .. } => original.raw_address, - SpecializedVariableIR::VecDeque { original, .. } => original.raw_address, - SpecializedVariableIR::HashMap { original, .. } => original.raw_address, - SpecializedVariableIR::HashSet { original, .. } => original.raw_address, - SpecializedVariableIR::BTreeMap { original, .. } => original.raw_address, - SpecializedVariableIR::BTreeSet { original, .. } => original.raw_address, - SpecializedVariableIR::String { original, .. } => original.raw_address, - SpecializedVariableIR::Str { original, .. } => original.raw_address, - SpecializedVariableIR::Tls { original, .. } => original.raw_address, - SpecializedVariableIR::Cell { original, .. } => original.raw_address, - SpecializedVariableIR::RefCell { original, .. } => original.raw_address, - SpecializedVariableIR::Rc { original, .. } => original.raw_address, - SpecializedVariableIR::Arc { original, .. } => original.raw_address, - SpecializedVariableIR::Uuid { original, .. } => original.raw_address, - } - } - - pub(super) fn type_id(&self) -> Option { - match self { - SpecializedVariableIR::Vector { original, .. } => original.type_id, - SpecializedVariableIR::VecDeque { original, .. } => original.type_id, - SpecializedVariableIR::HashMap { original, .. } => original.type_id, - SpecializedVariableIR::HashSet { original, .. } => original.type_id, - SpecializedVariableIR::BTreeMap { original, .. } => original.type_id, - SpecializedVariableIR::BTreeSet { original, .. } => original.type_id, - SpecializedVariableIR::String { original, .. } => original.type_id, - SpecializedVariableIR::Str { original, .. } => original.type_id, - SpecializedVariableIR::Tls { original, .. } => original.type_id, - SpecializedVariableIR::Cell { original, .. } => original.type_id, - SpecializedVariableIR::RefCell { original, .. } => original.type_id, - SpecializedVariableIR::Rc { original, .. } => original.type_id, - SpecializedVariableIR::Arc { original, .. } => original.type_id, - SpecializedVariableIR::Uuid { original, .. } => original.type_id, - } - } -} - -pub struct VariableParserExtension<'a> { - parser: &'a VariableParser<'a>, -} - -impl<'a> VariableParserExtension<'a> { - pub fn new(parser: &'a VariableParser) -> Self { - Self { parser } - } - - pub fn parse_str( - &self, - eval_ctx: &EvaluationContext, - structure: StructVariable, - ) -> SpecializedVariableIR { - SpecializedVariableIR::Str { - string: weak_error!(self - .parse_str_inner(eval_ctx, VariableIR::Struct(structure.clone())) - .context("&str interpretation")), - original: structure, - } - } - - fn parse_str_inner( - &self, - eval_ctx: &EvaluationContext, - ir: VariableIR, - ) -> Result { - let len = ir.assume_field_as_scalar_number("length")?; - let len = guard_len(len); - - let data_ptr = ir.assume_field_as_pointer("data_ptr")?; - - let data = debugger::read_memory_by_pid( - eval_ctx.expl_ctx.pid_on_focus(), - data_ptr as usize, - len as usize, - ) - .map(Bytes::from)?; - - Ok(StrVariable { - identity: ir.identity().clone(), - value: String::from_utf8(data.to_vec()).map_err(AssumeError::from)?, - }) - } - - pub fn parse_string( - &self, - eval_ctx: &EvaluationContext, - structure: StructVariable, - ) -> SpecializedVariableIR { - SpecializedVariableIR::String { - string: weak_error!(self - .parse_string_inner(eval_ctx, VariableIR::Struct(structure.clone())) - .context("String interpretation")), - original: structure, - } - } - - fn parse_string_inner( - &self, - eval_ctx: &EvaluationContext, - ir: VariableIR, - ) -> Result { - let len = ir.assume_field_as_scalar_number("len")?; - let len = guard_len(len); - - let data_ptr = ir.assume_field_as_pointer("pointer")?; - - let data = debugger::read_memory_by_pid( - eval_ctx.expl_ctx.pid_on_focus(), - data_ptr as usize, - len as usize, - )?; - - Ok(StringVariable { - identity: ir.identity().clone(), - value: String::from_utf8(data).map_err(AssumeError::from)?, - }) - } - - pub fn parse_vector( - &self, - eval_ctx: &EvaluationContext, - structure: StructVariable, - type_params: &HashMap>, - ) -> SpecializedVariableIR { - SpecializedVariableIR::Vector { - vec: weak_error!(self - .parse_vector_inner(eval_ctx, VariableIR::Struct(structure.clone()), type_params) - .context("Vec interpretation")), - original: structure, - } - } - - fn parse_vector_inner( - &self, - eval_ctx: &EvaluationContext, - ir: VariableIR, - type_params: &HashMap>, - ) -> Result { - let inner_type = type_params - .get("T") - .ok_or(TypeParameterNotFound("T"))? - .ok_or(TypeParameterTypeNotFound("T"))?; - let len = ir.assume_field_as_scalar_number("len")?; - let len = guard_len(len); - - let cap = extract_capacity(eval_ctx, &ir)? as i64; - let cap = guard_cap(cap); - - let data_ptr = ir.assume_field_as_pointer("pointer")? as usize; - - let el_type = self.parser.r#type; - let el_type_size = el_type - .type_size_in_bytes(eval_ctx, inner_type) - .ok_or(UnknownSize( - el_type.type_name(inner_type).unwrap_or_default(), - ))? as usize; - - let raw_data = debugger::read_memory_by_pid( - eval_ctx.expl_ctx.pid_on_focus(), - data_ptr, - len as usize * el_type_size, - ) - .map(Bytes::from)?; - - let (mut bytes_chunks, mut empty_chunks); - let raw_items_iter: &mut dyn Iterator = if el_type_size != 0 { - bytes_chunks = raw_data.chunks(el_type_size).enumerate(); - &mut bytes_chunks - } else { - // if an item type is zst - let v: Vec<&[u8]> = vec![&[]; len as usize]; - empty_chunks = v.into_iter().enumerate(); - &mut empty_chunks - }; - - let items = raw_items_iter - .filter_map(|(i, chunk)| { - let data = ObjectBinaryRepr { - raw_data: raw_data.slice_ref(chunk), - address: Some(data_ptr + (i * el_type_size)), - size: el_type_size, - }; - self.parser.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(Some(format!("{}", i as i64))), - Some(data), - inner_type, - ) - }) - .collect::>(); - - Ok(VecVariable { - structure: StructVariable { - identity: ir.identity().clone(), - type_id: None, - type_name: Some(ir.r#type().to_owned()), - members: vec![ - VariableIR::Array(ArrayVariable { - identity: VariableIdentity::no_namespace(Some("buf".to_owned())), - type_id: None, - type_name: self - .parser - .r#type - .type_name(inner_type) - .map(|tp| format!("[{tp}]")), - items: Some(items), - // set to `None` because the address operator unavailable for spec vars - raw_address: None, - }), - VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some("cap".to_owned())), - type_id: None, - type_name: Some("usize".to_owned()), - value: Some(SupportedScalar::Usize(cap as usize)), - // set to `None` because the address operator unavailable for spec vars - raw_address: None, - }), - ], - type_params: type_params.clone(), - // set to `None` because the address operator unavailable for spec vars - raw_address: None, - }, - }) - } - - pub fn parse_tls_old( - &self, - structure: StructVariable, - type_params: &HashMap>, - ) -> SpecializedVariableIR { - SpecializedVariableIR::Tls { - tls_var: weak_error!(self - .parse_tls_inner_old(VariableIR::Struct(structure.clone()), type_params) - .context("TLS variable interpretation")), - original: structure, - } - } - - fn parse_tls_inner_old( - &self, - ir: VariableIR, - type_params: &HashMap>, - ) -> Result { - // we assume that tls variable name represents in dwarf - // as namespace flowed before "__getit" namespace - let namespace = &ir.identity().namespace; - let name = namespace - .iter() - .find_position(|&ns| ns == "__getit") - .map(|(pos, _)| namespace[pos - 1].clone()); - - let inner_type = type_params - .get("T") - .ok_or(TypeParameterNotFound("T"))? - .ok_or(TypeParameterTypeNotFound("T"))?; - - let inner = ir - .bfs_iterator() - .find(|child| child.name() == "inner") - .ok_or(FieldNotFound("inner"))?; - let inner_option = inner.assume_field_as_rust_enum("value")?; - let inner_value = inner_option.value.ok_or(IncompleteInterp("value"))?; - - // we assume that dwarf representation of tls variable contains ::Option - if let VariableIR::Struct(opt_variant) = inner_value.as_ref() { - let tls_value = if opt_variant.type_name.as_deref() == Some("None") { - None - } else { - Some(Box::new( - inner_value - .bfs_iterator() - .find(|child| child.name() == "0") - .ok_or(FieldNotFound("__0"))? - .clone(), - )) - }; - - return Ok(TlsVariable { - identity: VariableIdentity::no_namespace(name), - inner_value: tls_value, - inner_type: self.parser.r#type.type_name(inner_type), - }); - } - - Err(ParsingError::Assume(IncompleteInterp( - "expect TLS inner value as option", - ))) - } - - pub fn parse_tls( - &self, - structure: StructVariable, - type_params: &HashMap>, - ) -> Option { - let tls_var = self - .parse_tls_inner(VariableIR::Struct(structure.clone()), type_params) - .context("TLS variable interpretation"); - - let var = match tls_var { - Ok(Some(var)) => Some(var), - Ok(None) => return None, - Err(e) => { - let e = e.context("TLS variable interpretation"); - warn!(target: "debugger", "{:#}", e); - None - } - }; - - Some(SpecializedVariableIR::Tls { - tls_var: var, - original: structure, - }) - } - - fn parse_tls_inner( - &self, - ir: VariableIR, - type_params: &HashMap>, - ) -> Result, ParsingError> { - // we assume that tls variable name represents in dwarf - // as namespace flowed before "__getit" namespace - let namespace = &ir.identity().namespace; - let name = namespace - .iter() - .find_position(|&ns| ns.contains("{constant#")) - .map(|(pos, _)| namespace[pos - 1].clone()); - - let inner_type = type_params - .get("T") - .ok_or(TypeParameterNotFound("T"))? - .ok_or(TypeParameterTypeNotFound("T"))?; - - let state = ir - .bfs_iterator() - .find(|child| child.name() == "state") - .ok_or(FieldNotFound("state"))?; - - let state = state.assume_field_as_rust_enum("value")?; - if let Some(VariableIR::Struct(val)) = state.value.as_deref() { - let tls_val = if val.identity.name.as_deref() == Some("Alive") { - Some(Box::new(val.members[0].clone())) - } else { - return Ok(None); - }; - - return Ok(Some(TlsVariable { - identity: VariableIdentity::no_namespace(name), - inner_value: tls_val, - inner_type: self.parser.r#type.type_name(inner_type), - })); - }; - - Err(ParsingError::Assume(IncompleteInterp( - "expect TLS inner value as option", - ))) - } - - pub fn parse_hashmap( - &self, - eval_ctx: &EvaluationContext, - structure: StructVariable, - ) -> SpecializedVariableIR { - SpecializedVariableIR::HashMap { - map: weak_error!(self - .parse_hashmap_inner(eval_ctx, VariableIR::Struct(structure.clone())) - .context("HashMap interpretation")), - original: structure, - } - } - - fn parse_hashmap_inner( - &self, - eval_ctx: &EvaluationContext, - ir: VariableIR, - ) -> Result { - let ctrl = ir.assume_field_as_pointer("pointer")?; - let bucket_mask = ir.assume_field_as_scalar_number("bucket_mask")?; - - let table = ir.assume_field_as_struct("table")?; - let kv_type = table - .type_params - .get("T") - .ok_or(TypeParameterNotFound("T"))? - .ok_or(TypeParameterTypeNotFound("T"))?; - - let r#type = self.parser.r#type; - let kv_size = r#type - .type_size_in_bytes(eval_ctx, kv_type) - .ok_or(UnknownSize(r#type.type_name(kv_type).unwrap_or_default()))?; - - let reflection = - HashmapReflection::new(ctrl as *mut u8, bucket_mask as usize, kv_size as usize); - - let iterator = reflection.iter(eval_ctx.expl_ctx.pid_on_focus())?; - let kv_items = iterator - .map_err(ParsingError::from) - .filter_map(|bucket| { - let raw_data = bucket.read(eval_ctx.expl_ctx.pid_on_focus()); - let data = weak_error!(raw_data).map(|d| ObjectBinaryRepr { - raw_data: Bytes::from(d), - address: Some(bucket.location()), - size: bucket.size(), - }); - - let tuple = self.parser.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(Some("kv".to_string())), - data, - kv_type, - ); - - if let Some(VariableIR::Struct(mut tuple)) = tuple { - if tuple.members.len() == 2 { - let v = tuple.members.pop(); - let k = tuple.members.pop(); - return Ok(Some((k.unwrap(), v.unwrap()))); - } - } - - Err(Assume(UnexpectedType("hashmap bucket"))) - }) - .collect()?; - - Ok(HashMapVariable { - identity: ir.identity().clone(), - type_name: Some(ir.r#type().to_owned()), - kv_items, - }) - } - - pub fn parse_hashset( - &self, - eval_ctx: &EvaluationContext, - structure: StructVariable, - ) -> SpecializedVariableIR { - SpecializedVariableIR::HashSet { - set: weak_error!(self - .parse_hashset_inner(eval_ctx, VariableIR::Struct(structure.clone())) - .context("HashSet interpretation")), - original: structure, - } - } - - fn parse_hashset_inner( - &self, - eval_ctx: &EvaluationContext, - ir: VariableIR, - ) -> Result { - let ctrl = ir.assume_field_as_pointer("pointer")?; - let bucket_mask = ir.assume_field_as_scalar_number("bucket_mask")?; - - let table = ir.assume_field_as_struct("table")?; - let kv_type = table - .type_params - .get("T") - .ok_or(TypeParameterNotFound("T"))? - .ok_or(TypeParameterTypeNotFound("T"))?; - let r#type = self.parser.r#type; - let kv_size = self - .parser - .r#type - .type_size_in_bytes(eval_ctx, kv_type) - .ok_or_else(|| UnknownSize(r#type.type_name(kv_type).unwrap_or_default()))?; - - let reflection = - HashmapReflection::new(ctrl as *mut u8, bucket_mask as usize, kv_size as usize); - - let iterator = reflection.iter(eval_ctx.expl_ctx.pid_on_focus())?; - let items = iterator - .map_err(ParsingError::from) - .filter_map(|bucket| { - let raw_data = bucket.read(eval_ctx.expl_ctx.pid_on_focus()); - let data = weak_error!(raw_data).map(|d| ObjectBinaryRepr { - raw_data: Bytes::from(d), - address: Some(bucket.location()), - size: bucket.size(), - }); - - let tuple = self.parser.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(Some("kv".to_string())), - data, - kv_type, - ); - - if let Some(VariableIR::Struct(mut tuple)) = tuple { - if tuple.members.len() == 2 { - let _ = tuple.members.pop(); - let k = tuple.members.pop().unwrap(); - return Ok(Some(k)); - } - } - - Err(Assume(UnexpectedType("hashset bucket"))) - }) - .collect()?; - - Ok(HashSetVariable { - identity: ir.identity().clone(), - type_name: Some(ir.r#type().to_owned()), - items, - }) - } - - pub fn parse_btree_map( - &self, - eval_ctx: &EvaluationContext, - structure: StructVariable, - identity: TypeIdentity, - type_params: &HashMap>, - ) -> SpecializedVariableIR { - SpecializedVariableIR::BTreeMap { - map: weak_error!(self - .parse_btree_map_inner( - eval_ctx, - VariableIR::Struct(structure.clone()), - identity, - type_params - ) - .context("BTreeMap interpretation")), - original: structure, - } - } - - fn parse_btree_map_inner( - &self, - eval_ctx: &EvaluationContext, - ir: VariableIR, - identity: TypeIdentity, - type_params: &HashMap>, - ) -> Result { - let height = ir.assume_field_as_scalar_number("height")?; - let ptr = ir.assume_field_as_pointer("pointer")?; - - let k_type = type_params - .get("K") - .ok_or(TypeParameterNotFound("K"))? - .ok_or(TypeParameterTypeNotFound("K"))?; - let v_type = type_params - .get("V") - .ok_or(TypeParameterNotFound("V"))? - .ok_or(TypeParameterTypeNotFound("V"))?; - - let reflection = BTreeReflection::new( - self.parser.r#type, - ptr, - height as usize, - identity, - k_type, - v_type, - )?; - let iterator = reflection.iter(eval_ctx)?; - let kv_items = iterator - .map_err(ParsingError::from) - .filter_map(|(k, v)| { - let Some(key) = self.parser.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(Some("k".to_string())), - Some(k), - k_type, - ) else { - return Ok(None); - }; - - let Some(value) = self.parser.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(Some("v".to_string())), - Some(v), - v_type, - ) else { - return Ok(None); - }; - - Ok(Some((key, value))) - }) - .collect::>()?; - - Ok(HashMapVariable { - identity: ir.identity().clone(), - type_name: Some(ir.r#type().to_owned()), - kv_items, - }) - } - - pub fn parse_btree_set(&self, structure: StructVariable) -> SpecializedVariableIR { - SpecializedVariableIR::BTreeSet { - set: weak_error!(self - .parse_btree_set_inner(VariableIR::Struct(structure.clone())) - .context("BTreeSet interpretation")), - original: structure, - } - } - - fn parse_btree_set_inner(&self, ir: VariableIR) -> Result { - let inner_map = ir - .bfs_iterator() - .find_map(|child| { - if let VariableIR::Specialized(SpecializedVariableIR::BTreeMap { - map: Some(ref map), - .. - }) = child - { - if map.identity.name.as_deref() == Some("map") { - return Some(map.clone()); - } - } - None - }) - .ok_or(IncompleteInterp("BTreeMap"))?; - - Ok(HashSetVariable { - identity: ir.identity().clone(), - type_name: Some(ir.r#type().to_owned()), - items: inner_map.kv_items.into_iter().map(|(k, _)| k).collect(), - }) - } - - pub fn parse_vec_dequeue( - &self, - eval_ctx: &EvaluationContext, - structure: StructVariable, - type_params: &HashMap>, - ) -> SpecializedVariableIR { - SpecializedVariableIR::VecDeque { - vec: weak_error!(self - .parse_vec_dequeue_inner( - eval_ctx, - VariableIR::Struct(structure.clone()), - type_params - ) - .context("VeqDequeue interpretation")), - original: structure, - } - } - - fn parse_vec_dequeue_inner( - &self, - eval_ctx: &EvaluationContext, - ir: VariableIR, - type_params: &HashMap>, - ) -> Result { - let inner_type = type_params - .get("T") - .ok_or(TypeParameterNotFound("T"))? - .ok_or(TypeParameterTypeNotFound("T"))?; - let len = ir.assume_field_as_scalar_number("len")? as usize; - let len = guard_len(len as i64) as usize; - - let r#type = self.parser.r#type; - let el_type_size = r#type - .type_size_in_bytes(eval_ctx, inner_type) - .ok_or_else(|| UnknownSize(r#type.type_name(inner_type).unwrap_or_default()))? - as usize; - let cap = if el_type_size == 0 { - usize::MAX - } else { - extract_capacity(eval_ctx, &ir)? - }; - let head = ir.assume_field_as_scalar_number("head")? as usize; - - let wrapped_start = if head >= cap { head - cap } else { head }; - let head_len = cap - wrapped_start; - - let slice_ranges = if head_len >= len { - (wrapped_start..wrapped_start + len, 0..0) - } else { - let tail_len = len - head_len; - (wrapped_start..cap, 0..tail_len) - }; - - let data_ptr = ir.assume_field_as_pointer("pointer")? as usize; - - let data = debugger::read_memory_by_pid( - eval_ctx.expl_ctx.pid_on_focus(), - data_ptr, - cap * el_type_size, - ) - .map(Bytes::from)?; - - let items = slice_ranges - .0 - .chain(slice_ranges.1) - .enumerate() - .filter_map(|(i, real_idx)| { - let offset = real_idx * el_type_size; - let el_raw_data = &data[offset..(real_idx + 1) * el_type_size]; - let el_data = ObjectBinaryRepr { - raw_data: data.slice_ref(el_raw_data), - address: Some(data_ptr + offset), - size: el_type_size, - }; - self.parser.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(Some(format!("{}", i as i64))), - Some(el_data), - inner_type, - ) - }) - .collect::>(); - - Ok(VecVariable { - structure: StructVariable { - identity: ir.identity().clone(), - type_id: None, - type_name: Some(ir.r#type().to_owned()), - members: vec![ - VariableIR::Array(ArrayVariable { - identity: VariableIdentity::no_namespace(Some("buf".to_owned())), - type_id: None, - type_name: self - .parser - .r#type - .type_name(inner_type) - .map(|tp| format!("[{tp}]")), - items: Some(items), - // set to `None` because the address operator unavailable for spec vars - raw_address: None, - }), - VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some("cap".to_owned())), - type_id: None, - type_name: Some("usize".to_owned()), - value: Some(SupportedScalar::Usize(if el_type_size == 0 { - 0 - } else { - cap - })), - // set to `None` because the address operator unavailable for spec vars - raw_address: None, - }), - ], - type_params: type_params.clone(), - // set to `None` because the address operator unavailable for spec vars - raw_address: None, - }, - }) - } - - pub fn parse_cell(&self, structure: StructVariable) -> SpecializedVariableIR { - SpecializedVariableIR::Cell { - value: weak_error!(self - .parse_cell_inner(VariableIR::Struct(structure.clone())) - .context("Cell interpretation")) - .map(Box::new), - original: structure, - } - } - - fn parse_cell_inner(&self, ir: VariableIR) -> Result { - let unsafe_cell = ir.assume_field_as_struct("value")?; - let value = unsafe_cell - .members - .first() - .ok_or(IncompleteInterp("UnsafeCell"))?; - Ok(value.clone()) - } - - pub fn parse_refcell(&self, structure: StructVariable) -> SpecializedVariableIR { - SpecializedVariableIR::RefCell { - value: weak_error!(self - .parse_refcell_inner(VariableIR::Struct(structure.clone())) - .context("RefCell interpretation")) - .map(Box::new), - original: structure, - } - } - - fn parse_refcell_inner(&self, ir: VariableIR) -> Result { - let borrow = ir - .bfs_iterator() - .find_map(|child| { - if let VariableIR::Specialized(SpecializedVariableIR::Cell { - value: Some(val), - .. - }) = child - { - return Some(val.clone()); - } - None - }) - .ok_or(IncompleteInterp("Cell"))?; - let VariableIR::Scalar(mut var) = *borrow else { - return Err(IncompleteInterp("Cell").into()); - }; - var.identity = VariableIdentity::no_namespace(Some("borrow".to_string())); - let borrow = VariableIR::Scalar(var); - - let unsafe_cell = ir.assume_field_as_struct("value")?; - let value = unsafe_cell - .members - .first() - .ok_or(IncompleteInterp("UnsafeCell"))?; - - Ok(VariableIR::Struct(StructVariable { - identity: ir.identity().clone(), - type_id: None, - type_name: Some(ir.r#type().to_owned()), - members: vec![borrow, value.clone()], - type_params: Default::default(), - // set to `None` because the address operator unavailable for spec vars - raw_address: None, - })) - } - - pub fn parse_rc(&self, structure: StructVariable) -> SpecializedVariableIR { - SpecializedVariableIR::Rc { - value: weak_error!(self - .parse_rc_inner(VariableIR::Struct(structure.clone())) - .context("Rc interpretation")), - original: structure, - } - } - - fn parse_rc_inner(&self, ir: VariableIR) -> Result { - Ok(ir - .bfs_iterator() - .find_map(|child| { - if let VariableIR::Pointer(pointer) = child { - if pointer.identity.name.as_deref()? == "pointer" { - let mut new_pointer = pointer.clone(); - new_pointer.identity = ir.identity().clone(); - return Some(new_pointer); - } - } - None - }) - .ok_or(IncompleteInterp("rc"))?) - } - - pub fn parse_arc(&self, structure: StructVariable) -> SpecializedVariableIR { - SpecializedVariableIR::Arc { - value: weak_error!(self - .parse_arc_inner(VariableIR::Struct(structure.clone())) - .context("Arc interpretation")), - original: structure, - } - } - - fn parse_arc_inner(&self, ir: VariableIR) -> Result { - Ok(ir - .bfs_iterator() - .find_map(|child| { - if let VariableIR::Pointer(pointer) = child { - if pointer.identity.name.as_deref()? == "pointer" { - let mut new_pointer = pointer.clone(); - new_pointer.identity = ir.identity().clone(); - return Some(new_pointer); - } - } - None - }) - .ok_or(IncompleteInterp("Arc"))?) - } - - pub fn parse_uuid(&self, structure: StructVariable) -> SpecializedVariableIR { - SpecializedVariableIR::Uuid { - value: weak_error!(self - .parse_uuid_inner(&structure) - .context("Uuid interpretation")), - original: structure, - } - } - - fn parse_uuid_inner(&self, structure: &StructVariable) -> Result<[u8; 16], ParsingError> { - let member0 = structure.members.first().ok_or(FieldNotFound("member 0"))?; - let VariableIR::Array(arr) = member0 else { - return Err(UnexpectedType("uuid struct member must be an array").into()); - }; - let items = arr - .items - .as_ref() - .ok_or(AssumeError::NoData("uuid items"))?; - if items.len() != 16 { - return Err(AssumeError::UnexpectedType("uuid struct member must be [u8; 16]").into()); - } - - let mut bytes_repr = [0; 16]; - for (i, item) in items.iter().enumerate() { - let VariableIR::Scalar(ScalarVariable { - value: Some(SupportedScalar::U8(byte)), - .. - }) = item - else { - return Err(UnexpectedType("uuid struct member must be [u8; 16]").into()); - }; - bytes_repr[i] = *byte; - } - - Ok(bytes_repr) - } -} - -fn extract_capacity(eval_ctx: &EvaluationContext, ir: &VariableIR) -> Result { - let rust_version = eval_ctx - .rustc_version() - .ok_or(ParsingError::UnsupportedVersion)?; - - version_switch!( - rust_version, - (1, 0, 0) ..= (1, 75, u32::MAX) => ir.assume_field_as_scalar_number("cap")? as usize, - (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => { - let cap_s = ir.assume_field_as_struct("cap")?; - let cap = cap_s.members.first().ok_or(IncompleteInterp("Vec"))?; - if let VariableIR::Scalar(ScalarVariable {value: Some(SupportedScalar::Usize(cap)), ..}) = cap { - Ok(*cap) - } else { - Err(AssumeError::FieldNotANumber("cap")) - }? - }, - ).ok_or(ParsingError::UnsupportedVersion) -} diff --git a/src/debugger/variable/value/bfs.rs b/src/debugger/variable/value/bfs.rs new file mode 100644 index 0000000..6930466 --- /dev/null +++ b/src/debugger/variable/value/bfs.rs @@ -0,0 +1,242 @@ +use crate::debugger::variable::value::Value; +use std::collections::VecDeque; + +#[derive(PartialEq, Debug)] +pub(super) enum FieldOrIndex<'a> { + Field(Option<&'a str>), + Index(i64), + Root, +} + +/// Iterator for visits underline values in BFS order. +pub(super) struct BfsIterator<'a> { + pub(super) queue: VecDeque<(FieldOrIndex<'a>, &'a Value)>, +} + +impl<'a> Iterator for BfsIterator<'a> { + type Item = (FieldOrIndex<'a>, &'a Value); + + fn next(&mut self) -> Option { + let (field_or_idx, next_value) = self.queue.pop_front()?; + + match next_value { + Value::Struct(r#struct) => { + r#struct.members.iter().for_each(|member| { + let item = ( + FieldOrIndex::Field(member.field_name.as_deref()), + &member.value, + ); + + self.queue.push_back(item) + }); + } + Value::Array(array) => { + if let Some(items) = array.items.as_ref() { + items.iter().for_each(|item| { + let item = (FieldOrIndex::Index(item.index), &item.value); + self.queue.push_back(item) + }) + } + } + Value::RustEnum(r#enum) => { + if let Some(enumerator) = r#enum.value.as_ref() { + let item = ( + FieldOrIndex::Field(enumerator.field_name.as_deref()), + &enumerator.value, + ); + self.queue.push_back(item) + } + } + Value::Pointer(_) => {} + Value::Specialized { + original: origin, .. + } => origin.members.iter().for_each(|member| { + let item = ( + FieldOrIndex::Field(member.field_name.as_deref()), + &member.value, + ); + self.queue.push_back(item) + }), + _ => {} + } + + Some((field_or_idx, next_value)) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::debugger::debugee::dwarf::r#type::TypeIdentity; + use crate::debugger::variable::value::{ + ArrayItem, ArrayValue, Member, PointerValue, RustEnumValue, ScalarValue, StructValue, + }; + + #[test] + fn test_bfs_iterator() { + struct TestCase { + variable: Value, + expected_order: Vec>, + } + + let test_cases = vec![ + TestCase { + variable: Value::Struct(StructValue { + type_ident: TypeIdentity::unknown(), + type_id: None, + members: vec![ + Member { + field_name: Some("array_1".to_owned()), + value: Value::Array(ArrayValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + items: Some(vec![ + ArrayItem { + index: 1, + value: Value::Scalar(ScalarValue { + type_ident: TypeIdentity::unknown(), + value: None, + raw_address: None, + type_id: None, + }), + }, + ArrayItem { + index: 2, + value: Value::Scalar(ScalarValue { + type_ident: TypeIdentity::unknown(), + value: None, + raw_address: None, + type_id: None, + }), + }, + ]), + raw_address: None, + }), + }, + Member { + field_name: Some("array_2".to_owned()), + value: Value::Array(ArrayValue { + type_ident: TypeIdentity::unknown(), + type_id: None, + items: Some(vec![ + ArrayItem { + index: 3, + value: Value::Scalar(ScalarValue { + type_ident: TypeIdentity::unknown(), + value: None, + raw_address: None, + type_id: None, + }), + }, + ArrayItem { + index: 4, + value: Value::Scalar(ScalarValue { + type_ident: TypeIdentity::unknown(), + value: None, + raw_address: None, + type_id: None, + }), + }, + ]), + raw_address: None, + }), + }, + ], + type_params: Default::default(), + raw_address: None, + }), + expected_order: vec![ + FieldOrIndex::Root, + FieldOrIndex::Field(Some("array_1")), + FieldOrIndex::Field(Some("array_2")), + FieldOrIndex::Index(1), + FieldOrIndex::Index(2), + FieldOrIndex::Index(3), + FieldOrIndex::Index(4), + ], + }, + TestCase { + variable: Value::Struct(StructValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + members: vec![ + Member { + field_name: Some("struct_2".to_owned()), + value: Value::Struct(StructValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + members: vec![ + Member { + field_name: Some("scalar_1".to_owned()), + value: Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + value: None, + raw_address: None, + }), + }, + Member { + field_name: Some("enum_1".to_owned()), + value: Value::RustEnum(RustEnumValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + value: Some(Box::new(Member { + field_name: Some("scalar_2".to_owned()), + value: Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + value: None, + raw_address: None, + }), + })), + raw_address: None, + }), + }, + Member { + field_name: Some("scalar_3".to_owned()), + value: Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + value: None, + raw_address: None, + }), + }, + ], + type_params: Default::default(), + raw_address: None, + }), + }, + Member { + field_name: Some("pointer_1".to_owned()), + value: Value::Pointer(PointerValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + value: None, + target_type: None, + target_type_size: None, + raw_address: None, + }), + }, + ], + type_params: Default::default(), + raw_address: None, + }), + expected_order: vec![ + FieldOrIndex::Root, + FieldOrIndex::Field(Some("struct_2")), + FieldOrIndex::Field(Some("pointer_1")), + FieldOrIndex::Field(Some("scalar_1")), + FieldOrIndex::Field(Some("enum_1")), + FieldOrIndex::Field(Some("scalar_3")), + FieldOrIndex::Field(Some("scalar_2")), + ], + }, + ]; + + for tc in test_cases { + let iter = tc.variable.bfs_iterator(); + let names: Vec<_> = iter.map(|(field_or_idx, _)| field_or_idx).collect(); + assert_eq!(tc.expected_order, names); + } + } +} diff --git a/src/debugger/variable/value/mod.rs b/src/debugger/variable/value/mod.rs new file mode 100644 index 0000000..4dc2130 --- /dev/null +++ b/src/debugger/variable/value/mod.rs @@ -0,0 +1,1693 @@ +use crate::debugger::debugee::dwarf::r#type::{CModifier, TypeId, TypeIdentity}; +use crate::debugger::variable::dqe::{Literal, LiteralOrWildcard}; +use crate::debugger::variable::render::RenderValue; +use crate::debugger::variable::value::bfs::BfsIterator; +use crate::debugger::variable::value::bfs::FieldOrIndex; +use crate::debugger::variable::value::parser::{ParseContext, ValueParser}; +use crate::debugger::variable::value::specialization::{ + HashSetVariable, StrVariable, StringVariable, +}; +use crate::debugger::variable::ObjectBinaryRepr; +use crate::debugger::TypeDeclaration; +use crate::{debugger, weak_error}; +use bytes::Bytes; +use std::collections::{HashMap, VecDeque}; +use std::fmt::{Debug, Display, Formatter}; +use std::string::FromUtf8Error; +use uuid::Uuid; + +mod bfs; +pub(super) mod parser; +mod specialization; + +pub use crate::debugger::variable::value::specialization::SpecializedValue; + +#[derive(Debug, thiserror::Error, PartialEq)] +pub enum AssumeError { + #[error("field `{0}` not found")] + FieldNotFound(&'static str), + #[error("field `{0}` not a number")] + FieldNotANumber(&'static str), + #[error("incomplete interpretation of `{0}`")] + IncompleteInterp(&'static str), + #[error("not data for {0}")] + NoData(&'static str), + #[error("not type for {0}")] + NoType(&'static str), + #[error("underline data not a string")] + DataNotAString(#[from] FromUtf8Error), + #[error("undefined size of type `{}`", .0.name_fmt())] + UnknownSize(TypeIdentity), + #[error("type parameter `{0}` not found")] + TypeParameterNotFound(&'static str), + #[error("unknown type for type parameter `{0}`")] + TypeParameterTypeNotFound(&'static str), + #[error("unexpected type for {0}")] + UnexpectedType(&'static str), + #[error("unexpected binary representation of {0}, expect {1} got {2} bytes")] + UnexpectedBinaryRepr(&'static str, usize, usize), +} + +#[derive(Debug, thiserror::Error, PartialEq)] +pub enum ParsingError { + #[error(transparent)] + Assume(#[from] AssumeError), + #[error("unsupported language version")] + UnsupportedVersion, + #[error("error while reading from debugee memory: {0}")] + ReadDebugeeMemory(#[from] nix::Error), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum SupportedScalar { + I8(i8), + I16(i16), + I32(i32), + I64(i64), + I128(i128), + Isize(isize), + U8(u8), + U16(u16), + U32(u32), + U64(u64), + U128(u128), + Usize(usize), + F32(f32), + F64(f64), + Bool(bool), + Char(char), + Empty(), +} + +impl Display for SupportedScalar { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + SupportedScalar::I8(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::I16(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::I32(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::I64(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::I128(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::Isize(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::U8(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::U16(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::U32(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::U64(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::U128(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::Usize(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::F32(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::F64(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::Bool(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::Char(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::Empty() => f.write_str("()"), + } + } +} + +impl SupportedScalar { + fn equal_with_literal(&self, lhs: &Literal) -> bool { + match self { + SupportedScalar::I8(i) => lhs.equal_with_int(*i as i64), + SupportedScalar::I16(i) => lhs.equal_with_int(*i as i64), + SupportedScalar::I32(i) => lhs.equal_with_int(*i as i64), + SupportedScalar::I64(i) => lhs.equal_with_int(*i), + SupportedScalar::I128(i) => lhs.equal_with_int(*i as i64), + SupportedScalar::Isize(i) => lhs.equal_with_int(*i as i64), + SupportedScalar::U8(u) => lhs.equal_with_int(*u as i64), + SupportedScalar::U16(u) => lhs.equal_with_int(*u as i64), + SupportedScalar::U32(u) => lhs.equal_with_int(*u as i64), + SupportedScalar::U64(u) => lhs.equal_with_int(*u as i64), + SupportedScalar::U128(u) => lhs.equal_with_int(*u as i64), + SupportedScalar::Usize(u) => lhs.equal_with_int(*u as i64), + SupportedScalar::F32(f) => lhs.equal_with_float(*f as f64), + SupportedScalar::F64(f) => lhs.equal_with_float(*f), + SupportedScalar::Bool(b) => lhs.equal_with_bool(*b), + SupportedScalar::Char(c) => lhs.equal_with_string(&c.to_string()), + SupportedScalar::Empty() => false, + } + } +} + +/// Represents scalars: integer's, float's, bool, char and () types. +#[derive(Clone, PartialEq)] +pub struct ScalarValue { + pub value: Option, + pub raw_address: Option, + pub type_ident: TypeIdentity, + pub type_id: Option, +} + +impl ScalarValue { + fn try_as_number(&self) -> Option { + match self.value { + Some(SupportedScalar::I8(num)) => Some(num as i64), + Some(SupportedScalar::I16(num)) => Some(num as i64), + Some(SupportedScalar::I32(num)) => Some(num as i64), + Some(SupportedScalar::I64(num)) => Some(num), + Some(SupportedScalar::Isize(num)) => Some(num as i64), + Some(SupportedScalar::U8(num)) => Some(num as i64), + Some(SupportedScalar::U16(num)) => Some(num as i64), + Some(SupportedScalar::U32(num)) => Some(num as i64), + Some(SupportedScalar::U64(num)) => Some(num as i64), + Some(SupportedScalar::Usize(num)) => Some(num as i64), + _ => None, + } + } +} + +/// Structure member representation. +#[derive(Clone, PartialEq, Debug)] +pub struct Member { + pub field_name: Option, + pub value: Value, +} + +/// Represents structures. +#[derive(Clone, Default, PartialEq)] +pub struct StructValue { + pub type_ident: TypeIdentity, + pub type_id: Option, + /// Structure members. + pub members: Vec, + /// Map of type parameters of a structure type. + pub type_params: HashMap>, + pub raw_address: Option, +} + +impl StructValue { + pub fn field(self, field_name: &str) -> Option { + self.members.into_iter().find_map(|member| { + if member.field_name.as_deref() == Some(field_name) { + Some(member.value) + } else { + None + } + }) + } +} + +/// Array item representation. +#[derive(Clone, PartialEq, Debug)] +pub struct ArrayItem { + pub index: i64, + pub value: Value, +} + +/// Represents arrays. +#[derive(Clone, PartialEq)] +pub struct ArrayValue { + pub type_ident: TypeIdentity, + pub type_id: Option, + /// Array items. + pub items: Option>, + pub raw_address: Option, +} + +impl ArrayValue { + fn slice(&mut self, left: Option, right: Option) { + if let Some(items) = self.items.as_mut() { + if let Some(left) = left { + items.drain(..left); + } + + if let Some(right) = right { + let remove_range = right - left.unwrap_or_default()..; + if remove_range.start < items.len() { + items.drain(remove_range); + }; + } + } + } +} + +/// Simple c-style enums (each option in which does not contain the underlying values). +#[derive(Clone, PartialEq)] +pub struct CEnumValue { + pub type_ident: TypeIdentity, + pub type_id: Option, + /// String representation of selected variant. + pub value: Option, + pub raw_address: Option, +} + +/// Represents all enum's that more complex than c-style enums. +#[derive(Clone, PartialEq)] +pub struct RustEnumValue { + pub type_ident: TypeIdentity, + pub type_id: Option, + /// Variable IR representation of selected variant. + pub value: Option>, + pub raw_address: Option, +} + +/// Raw pointers, references, Box. +#[derive(Clone, PartialEq)] +pub struct PointerValue { + pub type_ident: TypeIdentity, + pub type_id: Option, + /// Raw pointer to underline value. + pub value: Option<*const ()>, + /// Underline type identity. + pub target_type: Option, + pub target_type_size: Option, + pub raw_address: Option, +} + +impl PointerValue { + /// Dereference pointer and return variable IR that represents underline value. + pub fn deref(&self, ctx: &ParseContext) -> Option { + let target_type = self.target_type?; + let deref_size = self.target_type_size.or_else(|| { + ctx.type_graph + .type_size_in_bytes(ctx.evaluation_context, target_type) + }); + + let target_type_decl = ctx.type_graph.types.get(&target_type); + if matches!(target_type_decl, Some(TypeDeclaration::Subroutine { .. })) { + // this variable is a fn pointer - don't deref it + return None; + } + + self.value.and_then(|ptr| { + let data = deref_size.and_then(|sz| { + let raw_data = debugger::read_memory_by_pid( + ctx.evaluation_context.expl_ctx.pid_on_focus(), + ptr as usize, + sz as usize, + ) + .ok()?; + + Some(ObjectBinaryRepr { + raw_data: Bytes::from(raw_data), + address: Some(ptr as usize), + size: sz as usize, + }) + }); + let parser = ValueParser::new(); + parser.parse_inner(ctx, data, target_type) + }) + } + + /// Interpret a pointer as a pointer on first array element. + /// Returns variable IR that represents an array. + pub fn slice(&self, ctx: &ParseContext, left: Option, right: usize) -> Option { + let target_type = self.target_type?; + let deref_size = + ctx.type_graph + .type_size_in_bytes(ctx.evaluation_context, target_type)? as usize; + + self.value.and_then(|ptr| { + let left = left.unwrap_or_default(); + let base_addr = ptr as usize + deref_size * left; + let raw_data = weak_error!(debugger::read_memory_by_pid( + ctx.evaluation_context.expl_ctx.pid_on_focus(), + base_addr, + deref_size * (right - left) + ))?; + let raw_data = bytes::Bytes::from(raw_data); + + let parser = ValueParser::new(); + let items = raw_data + .chunks(deref_size) + .enumerate() + .filter_map(|(i, chunk)| { + let data = ObjectBinaryRepr { + raw_data: raw_data.slice_ref(chunk), + address: Some(base_addr + (i * deref_size)), + size: deref_size, + }; + Some(ArrayItem { + index: i as i64, + value: parser.parse_inner(ctx, Some(data), target_type)?, + }) + }) + .collect::>(); + + Some(Value::Array(ArrayValue { + items: Some(items), + type_id: None, + type_ident: ctx.type_graph.identity(target_type).as_array_type(), + raw_address: Some(base_addr), + })) + }) + } +} + +/// Represents subroutine. +#[derive(Clone, PartialEq)] +pub struct SubroutineValue { + pub type_id: Option, + pub return_type_ident: Option, + pub address: Option, +} + +/// Represent a variable with C modifiers (volatile, const, typedef, etc.) +#[derive(Clone, PartialEq)] +pub struct CModifiedValue { + pub type_ident: TypeIdentity, + pub type_id: Option, + pub modifier: CModifier, + pub value: Option>, + pub address: Option, +} + +/// Program typed value representation. +#[derive(Clone, PartialEq)] +pub enum Value { + Scalar(ScalarValue), + Struct(StructValue), + Array(ArrayValue), + CEnum(CEnumValue), + RustEnum(RustEnumValue), + Pointer(PointerValue), + Subroutine(SubroutineValue), + Specialized { + value: Option, + original: StructValue, + }, + CModifiedVariable(CModifiedValue), +} + +// SAFETY: this enum may contain a raw pointers on memory in a debugee process, +// it is safe to dereference it using public API of *Variable structures. +unsafe impl Send for Value {} + +impl Debug for Value { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self.as_literal() { + None => Ok(()), + Some(lit) => f.write_fmt(format_args!("{lit}")), + } + } +} + +impl Value { + /// Return literal equals representation of a value. + pub fn as_literal(&self) -> Option { + match self { + Value::Scalar(scalar) => { + if let Some(i) = scalar.try_as_number() { + return Some(Literal::Int(i)); + } + + match scalar.value.as_ref()? { + SupportedScalar::F32(f) => Some(Literal::Float(*f as f64)), + SupportedScalar::F64(f) => Some(Literal::Float(*f)), + SupportedScalar::Bool(b) => Some(Literal::Bool(*b)), + SupportedScalar::Char(c) => Some(Literal::String(c.to_string())), + SupportedScalar::Empty() => None, + _ => None, + } + } + Value::Struct(s) => { + let mut assoc_array = HashMap::new(); + for member in &s.members { + let field = member.field_name.as_ref()?.clone(); + let literal = member.value.as_literal()?; + assoc_array.insert(field, LiteralOrWildcard::Literal(literal)); + } + Some(Literal::AssocArray(assoc_array)) + } + Value::Array(arr) => { + let mut array = vec![]; + for item in arr.items.as_ref()? { + array.push(LiteralOrWildcard::Literal(item.value.as_literal()?)) + } + Some(Literal::Array(array.into_boxed_slice())) + } + Value::CEnum(e) => Some(Literal::EnumVariant(e.value.as_ref()?.to_string(), None)), + Value::RustEnum(e) => { + let member = e.value.as_ref()?; + Some(Literal::EnumVariant( + member.field_name.as_ref()?.clone(), + member.value.as_literal().map(Box::new), + )) + } + Value::Pointer(ptr) => Some(Literal::Address(ptr.value? as usize)), + Value::Subroutine(_) => None, + Value::Specialized { value, .. } => match value.as_ref()? { + SpecializedValue::Vector(vec) | SpecializedValue::VecDeque(vec) => { + let member = vec.structure.members.first(); + let array = member?; + array.value.as_literal() + } + SpecializedValue::HashMap(map) | SpecializedValue::BTreeMap(map) => { + let mut assoc_array = HashMap::new(); + for (key, val) in &map.kv_items { + let Some(Literal::String(key_str)) = key.as_literal() else { + return None; + }; + let value = val.as_literal()?; + assoc_array.insert(key_str, LiteralOrWildcard::Literal(value)); + } + Some(Literal::AssocArray(assoc_array)) + } + SpecializedValue::HashSet(set) | SpecializedValue::BTreeSet(set) => { + let mut array = vec![]; + for item in &set.items { + array.push(LiteralOrWildcard::Literal(item.as_literal()?)) + } + Some(Literal::Array(array.into_boxed_slice())) + } + SpecializedValue::String(str) => Some(Literal::String(str.value.clone())), + SpecializedValue::Str(str) => Some(Literal::String(str.value.clone())), + SpecializedValue::Tls(tls) => tls.inner_value.as_ref()?.as_literal(), + SpecializedValue::Cell(c) => c.as_literal(), + SpecializedValue::RefCell(c) => c.as_literal(), + SpecializedValue::Rc(ptr) => Some(Literal::Address(ptr.raw_address?)), + SpecializedValue::Arc(ptr) => Some(Literal::Address(ptr.raw_address?)), + SpecializedValue::Uuid(uuid) => { + let uuid = Uuid::from_bytes(*uuid); + Some(Literal::String(uuid.to_string())) + } + SpecializedValue::SystemTime(time) => { + let time = chrono::DateTime::from_timestamp(time.0, time.1)?; + Some(Literal::String( + time.format("%Y-%m-%d %H:%M:%S").to_string(), + )) + } + SpecializedValue::Instant(_) => None, + }, + Value::CModifiedVariable(val) => Some(val.value.as_ref()?.as_literal()?), + } + } + + /// Return address in debugee memory for variable data. + pub fn in_memory_location(&self) -> Option { + match self { + Value::Scalar(s) => s.raw_address, + Value::Struct(s) => s.raw_address, + Value::Array(a) => a.raw_address, + Value::CEnum(ce) => ce.raw_address, + Value::RustEnum(re) => re.raw_address, + Value::Pointer(p) => p.raw_address, + Value::Subroutine(s) => s.address, + Value::Specialized { + original: origin, .. + } => origin.raw_address, + Value::CModifiedVariable(cmv) => cmv.address, + } + } + + pub fn type_id(&self) -> Option { + match self { + Value::Scalar(s) => s.type_id, + Value::Struct(s) => s.type_id, + Value::Array(a) => a.type_id, + Value::CEnum(ce) => ce.type_id, + Value::RustEnum(re) => re.type_id, + Value::Pointer(p) => p.type_id, + Value::Subroutine(s) => s.type_id, + Value::Specialized { + original: origin, .. + } => origin.type_id, + Value::CModifiedVariable(cmv) => cmv.type_id, + } + } + + /// Visit variable children in BFS order. + fn bfs_iterator(&self) -> BfsIterator { + BfsIterator { + queue: VecDeque::from([(FieldOrIndex::Root, self)]), + } + } + + /// Returns i64 value representation or error if cast fail. + fn assume_field_as_scalar_number(&self, field_name: &'static str) -> Result { + let val = self + .bfs_iterator() + .find_map(|(field_or_idx, child)| { + (field_or_idx == FieldOrIndex::Field(Some(field_name))).then_some(child) + }) + .ok_or(AssumeError::FieldNotFound(field_name))?; + if let Value::Scalar(s) = val { + Ok(s.try_as_number() + .ok_or(AssumeError::FieldNotANumber(field_name))?) + } else { + Err(AssumeError::FieldNotANumber(field_name)) + } + } + + /// Returns value as a raw pointer or error if cast fails. + fn assume_field_as_pointer(&self, field_name: &'static str) -> Result<*const (), AssumeError> { + self.bfs_iterator() + .find_map(|(field_or_idx, child)| { + if let Value::Pointer(pointer) = child { + if field_or_idx == FieldOrIndex::Field(Some(field_name)) { + return pointer.value; + } + } + None + }) + .ok_or(AssumeError::IncompleteInterp("pointer")) + } + + /// Returns value as enum or error if cast fail. + fn assume_field_as_rust_enum( + &self, + field_name: &'static str, + ) -> Result { + self.bfs_iterator() + .find_map(|(field_or_idx, child)| { + if let Value::RustEnum(r_enum) = child { + if field_or_idx == FieldOrIndex::Field(Some(field_name)) { + return Some(r_enum.clone()); + } + } + None + }) + .ok_or(AssumeError::IncompleteInterp("pointer")) + } + + /// Returns value as structure or error if cast fail. + fn assume_field_as_struct(&self, field_name: &'static str) -> Result { + self.bfs_iterator() + .find_map(|(field_or_idx, child)| { + if let Value::Struct(structure) = child { + if field_or_idx == FieldOrIndex::Field(Some(field_name)) { + return Some(structure.clone()); + } + } + None + }) + .ok_or(AssumeError::IncompleteInterp("structure")) + } + + /// Return an underlying structure for specialized values (vectors, strings, etc.). + pub fn canonic(self) -> Self { + match self { + Value::Specialized { + original: origin, .. + } => Value::Struct(origin), + _ => self, + } + } + + /// Try to dereference variable and returns underline variable IR. + /// Return `None` if dereference not allowed. + pub fn deref(self, ctx: &ParseContext) -> Option { + match self { + Value::Pointer(ptr) => ptr.deref(ctx), + Value::RustEnum(r_enum) => r_enum.value.and_then(|v| v.value.deref(ctx)), + Value::Specialized { + value: Some(SpecializedValue::Rc(ptr)), + .. + } + | Value::Specialized { + value: Some(SpecializedValue::Arc(ptr)), + .. + } => ptr.deref(ctx), + Value::Specialized { + value: Some(SpecializedValue::Tls(tls_var)), + .. + } => tls_var.inner_value.and_then(|inner| inner.deref(ctx)), + Value::Specialized { + value: Some(SpecializedValue::Cell(cell)), + .. + } + | Value::Specialized { + value: Some(SpecializedValue::RefCell(cell)), + .. + } => cell.deref(ctx), + _ => None, + } + } + + /// Return address (as pointer variable) of raw data in debugee memory. + pub fn address(self, ctx: &ParseContext) -> Option { + let addr = self.in_memory_location()?; + Some(Value::Pointer(PointerValue { + type_ident: self.r#type().as_address_type(), + value: Some(addr as *const ()), + target_type: self.type_id(), + target_type_size: self + .type_id() + .and_then(|t| ctx.type_graph.type_size_in_bytes(ctx.evaluation_context, t)), + raw_address: None, + type_id: None, + })) + } + + /// Return variable field, `None` if field is not allowed for a variable type. + /// Supported: structures, rust-style enums, hashmaps, btree-maps. + pub fn field(self, field_name: &str) -> Option { + match self { + Value::Struct(structure) => structure.field(field_name), + Value::RustEnum(r_enum) => r_enum.value.and_then(|v| v.value.field(field_name)), + Value::Specialized { + value: specialized, .. + } => match specialized { + Some(SpecializedValue::HashMap(map)) | Some(SpecializedValue::BTreeMap(map)) => { + map.kv_items.into_iter().find_map(|(key, value)| match key { + Value::Specialized { + value: specialized, .. + } => match specialized { + Some(SpecializedValue::String(string_key)) => { + (string_key.value == field_name).then_some(value) + } + Some(SpecializedValue::Str(string_key)) => { + (string_key.value == field_name).then_some(value) + } + _ => None, + }, + _ => None, + }) + } + Some(SpecializedValue::Tls(tls_var)) => tls_var + .inner_value + .and_then(|inner| inner.field(field_name)), + Some(SpecializedValue::Cell(cell)) | Some(SpecializedValue::RefCell(cell)) => { + cell.field(field_name) + } + _ => None, + }, + _ => None, + } + } + + /// Return variable element by its index, `None` if indexing is not allowed for a variable type. + /// Supported: array, rust-style enums, vector, hashmap, hashset, btreemap, btreeset. + pub fn index(self, idx: &Literal) -> Option { + match self { + Value::Array(array) => array.items.and_then(|mut items| { + if let Literal::Int(idx) = idx { + let idx = *idx as usize; + if idx < items.len() { + return Some(items.swap_remove(idx).value); + } + } + None + }), + Value::RustEnum(r_enum) => r_enum.value.and_then(|v| v.value.index(idx)), + Value::Specialized { + value: Some(spec_val), + .. + } => match spec_val { + SpecializedValue::Vector(mut vec) | SpecializedValue::VecDeque(mut vec) => { + let inner_array = vec.structure.members.swap_remove(0).value; + inner_array.index(idx) + } + SpecializedValue::Tls(tls_var) => { + tls_var.inner_value.and_then(|inner| inner.index(idx)) + } + SpecializedValue::Cell(cell) | SpecializedValue::RefCell(cell) => cell.index(idx), + SpecializedValue::BTreeMap(map) | SpecializedValue::HashMap(map) => { + for (k, v) in map.kv_items { + if k.match_literal(idx) { + return Some(v); + } + } + + None + } + SpecializedValue::BTreeSet(set) | SpecializedValue::HashSet(set) => { + let found = set.items.into_iter().any(|it| it.match_literal(idx)); + + Some(Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("bool"), + value: Some(SupportedScalar::Bool(found)), + raw_address: None, + })) + } + _ => None, + }, + _ => None, + } + } + + pub fn slice( + self, + ctx: &ParseContext, + left: Option, + right: Option, + ) -> Option { + match self { + Value::Array(mut array) => { + array.slice(left, right); + Some(Value::Array(array)) + } + Value::Pointer(ptr) => { + // for pointer the right bound must always be specified + let right = right?; + ptr.slice(ctx, left, right) + } + Value::Specialized { + value: Some(spec_val), + original, + } => match spec_val { + SpecializedValue::Rc(ptr) | SpecializedValue::Arc(ptr) => { + // for pointer the right bound must always be specified + let right = right?; + ptr.slice(ctx, left, right) + } + SpecializedValue::Vector(mut vec) => { + vec.slice(left, right); + Some(Value::Specialized { + value: Some(SpecializedValue::Vector(vec)), + original, + }) + } + SpecializedValue::VecDeque(mut vec) => { + vec.slice(left, right); + Some(Value::Specialized { + value: Some(SpecializedValue::VecDeque(vec)), + original, + }) + } + SpecializedValue::Tls(mut tls_var) => { + let inner = tls_var.inner_value.take()?; + inner.slice(ctx, left, right) + } + SpecializedValue::Cell(cell) | SpecializedValue::RefCell(cell) => { + cell.slice(ctx, left, right) + } + _ => None, + }, + _ => None, + } + } + + /// Match variable with a literal object. + /// Return true if variable matched to literal. + fn match_literal(self, literal: &Literal) -> bool { + match self { + Value::Scalar(ScalarValue { + value: Some(scalar), + .. + }) => scalar.equal_with_literal(literal), + Value::Pointer(PointerValue { + value: Some(ptr), .. + }) => literal.equal_with_address(ptr as usize), + Value::Array(ArrayValue { + items: Some(items), .. + }) => { + let Literal::Array(arr_literal) = literal else { + return false; + }; + if arr_literal.len() != items.len() { + return false; + } + + for (i, item) in items.into_iter().enumerate() { + match &arr_literal[i] { + LiteralOrWildcard::Literal(lit) => { + if !item.value.match_literal(lit) { + return false; + } + } + LiteralOrWildcard::Wildcard => continue, + } + } + true + } + Value::Struct(StructValue { members, .. }) => { + match literal { + Literal::Array(array_literal) => { + // structure must be a tuple + if array_literal.len() != members.len() { + return false; + } + + for (i, member) in members.into_iter().enumerate() { + let field_literal = &array_literal[i]; + match field_literal { + LiteralOrWildcard::Literal(lit) => { + if !member.value.match_literal(lit) { + return false; + } + } + LiteralOrWildcard::Wildcard => continue, + } + } + + true + } + Literal::AssocArray(struct_literal) => { + // default structure + if struct_literal.len() != members.len() { + return false; + } + + for member in members { + let Some(member_name) = member.field_name else { + return false; + }; + + let Some(field_literal) = struct_literal.get(&member_name) else { + return false; + }; + + match field_literal { + LiteralOrWildcard::Literal(lit) => { + if !member.value.match_literal(lit) { + return false; + } + } + LiteralOrWildcard::Wildcard => continue, + } + } + true + } + _ => false, + } + } + Value::Specialized { + value: Some(spec), .. + } => match spec { + SpecializedValue::String(StringVariable { value, .. }) => { + literal.equal_with_string(&value) + } + SpecializedValue::Str(StrVariable { value, .. }) => { + literal.equal_with_string(&value) + } + SpecializedValue::Uuid(bytes) => { + let uuid = Uuid::from_bytes(bytes); + literal.equal_with_string(&uuid.to_string()) + } + SpecializedValue::Cell(cell) | SpecializedValue::RefCell(cell) => { + cell.match_literal(literal) + } + SpecializedValue::Rc(PointerValue { + value: Some(ptr), .. + }) + | SpecializedValue::Arc(PointerValue { + value: Some(ptr), .. + }) => literal.equal_with_address(ptr as usize), + SpecializedValue::Vector(mut v) | SpecializedValue::VecDeque(mut v) => { + let inner_array = v.structure.members.swap_remove(0).value; + debug_assert!(matches!(inner_array, Value::Array(_))); + inner_array.match_literal(literal) + } + SpecializedValue::HashSet(HashSetVariable { items, .. }) + | SpecializedValue::BTreeSet(HashSetVariable { items, .. }) => { + let Literal::Array(arr_literal) = literal else { + return false; + }; + if arr_literal.len() != items.len() { + return false; + } + let mut arr_literal = arr_literal.to_vec(); + + for item in items { + let mut item_found = false; + + // try to find equals item + let mb_literal_idx = arr_literal.iter().position(|lit| { + if let LiteralOrWildcard::Literal(lit) = lit { + item.clone().match_literal(lit) + } else { + false + } + }); + if let Some(literal_idx) = mb_literal_idx { + arr_literal.swap_remove(literal_idx); + item_found = true; + } + + // try to find wildcard + if !item_found { + let mb_wildcard_idx = arr_literal + .iter() + .position(|lit| matches!(lit, LiteralOrWildcard::Wildcard)); + if let Some(wildcard_idx) = mb_wildcard_idx { + arr_literal.swap_remove(wildcard_idx); + item_found = true; + } + } + + // still not found - set aren't equal + if !item_found { + return false; + } + } + true + } + SpecializedValue::Tls(inner) => inner + .inner_value + .map(|v| v.match_literal(literal)) + .unwrap_or_default(), + SpecializedValue::SystemTime(time) => { + let Some(time) = chrono::DateTime::from_timestamp(time.0, time.1) else { + return false; + }; + + literal.equal_with_string(&time.format("%Y-%m-%d %H:%M:%S").to_string()) + } + _ => false, + }, + Value::CEnum(CEnumValue { + value: Some(ref value), + .. + }) => { + let Literal::EnumVariant(variant, None) = literal else { + return false; + }; + value == variant + } + Value::RustEnum(RustEnumValue { + value: Some(value), .. + }) => { + let Literal::EnumVariant(variant, variant_value) = literal else { + return false; + }; + + if value.field_name.as_ref() != Some(variant) { + return false; + } + + match variant_value { + None => true, + Some(lit) => value.value.match_literal(lit), + } + } + _ => false, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::debugger::variable::value::specialization::VecValue; + + // test helpers -------------------------------------------------------------------------------- + // + fn make_scalar_val(type_name: &str, scalar: SupportedScalar) -> Value { + Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::no_namespace(type_name), + value: Some(scalar), + raw_address: None, + }) + } + + fn make_str_val(val: &str) -> Value { + Value::Specialized { + value: Some(SpecializedValue::Str(StrVariable { + value: val.to_string(), + })), + original: StructValue { + ..Default::default() + }, + } + } + + fn make_string_val(val: &str) -> Value { + Value::Specialized { + value: Some(SpecializedValue::String(StringVariable { + value: val.to_string(), + })), + original: StructValue { + ..Default::default() + }, + } + } + + fn make_vec_val(items: Vec) -> VecValue { + let items_len = items.len(); + VecValue { + structure: StructValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("vec"), + members: vec![ + Member { + field_name: None, + value: Value::Array(ArrayValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("[item]"), + items: Some(items), + raw_address: None, + }), + }, + Member { + field_name: Some("cap".to_string()), + value: Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("usize"), + value: Some(SupportedScalar::Usize(items_len)), + raw_address: None, + }), + }, + ], + type_params: HashMap::default(), + raw_address: None, + }, + } + } + + fn make_vector_val(items: Vec) -> Value { + Value::Specialized { + value: Some(SpecializedValue::Vector(make_vec_val(items))), + original: StructValue { + ..Default::default() + }, + } + } + + fn make_vecdeque_val(items: Vec) -> Value { + Value::Specialized { + value: Some(SpecializedValue::VecDeque(make_vec_val(items))), + original: StructValue { + ..Default::default() + }, + } + } + + fn make_hashset_val(items: Vec) -> Value { + Value::Specialized { + value: Some(SpecializedValue::HashSet(HashSetVariable { + type_ident: TypeIdentity::no_namespace("hashset"), + items, + })), + original: StructValue { + ..Default::default() + }, + } + } + + fn make_btreeset_var_val(items: Vec) -> Value { + Value::Specialized { + value: Some(SpecializedValue::BTreeSet(HashSetVariable { + type_ident: TypeIdentity::no_namespace("btreeset"), + items, + })), + original: StructValue { + ..Default::default() + }, + } + } + //---------------------------------------------------------------------------------------------- + + #[test] + fn test_equal_with_literal() { + struct TestCase { + variable: Value, + eq_literal: Literal, + neq_literals: Vec, + } + + let test_cases = [ + TestCase { + variable: make_scalar_val("i8", SupportedScalar::I8(8)), + eq_literal: Literal::Int(8), + neq_literals: vec![Literal::Int(9)], + }, + TestCase { + variable: make_scalar_val("i32", SupportedScalar::I32(32)), + eq_literal: Literal::Int(32), + neq_literals: vec![Literal::Int(33)], + }, + TestCase { + variable: make_scalar_val("isize", SupportedScalar::Isize(-1234)), + eq_literal: Literal::Int(-1234), + neq_literals: vec![Literal::Int(-1233)], + }, + TestCase { + variable: make_scalar_val("u8", SupportedScalar::U8(8)), + eq_literal: Literal::Int(8), + neq_literals: vec![Literal::Int(9)], + }, + TestCase { + variable: make_scalar_val("u32", SupportedScalar::U32(32)), + eq_literal: Literal::Int(32), + neq_literals: vec![Literal::Int(33)], + }, + TestCase { + variable: make_scalar_val("usize", SupportedScalar::Usize(1234)), + eq_literal: Literal::Int(1234), + neq_literals: vec![Literal::Int(1235)], + }, + TestCase { + variable: make_scalar_val("f32", SupportedScalar::F32(1.1)), + eq_literal: Literal::Float(1.1), + neq_literals: vec![Literal::Float(1.2)], + }, + TestCase { + variable: make_scalar_val("f64", SupportedScalar::F64(-2.2)), + eq_literal: Literal::Float(-2.2), + neq_literals: vec![Literal::Float(2.2)], + }, + TestCase { + variable: make_scalar_val("bool", SupportedScalar::Bool(true)), + eq_literal: Literal::Bool(true), + neq_literals: vec![Literal::Bool(false)], + }, + TestCase { + variable: make_scalar_val("char", SupportedScalar::Char('b')), + eq_literal: Literal::String("b".into()), + neq_literals: vec![Literal::String("c".into())], + }, + TestCase { + variable: Value::Pointer(PointerValue { + target_type: None, + type_id: None, + type_ident: TypeIdentity::no_namespace("ptr"), + value: Some(123usize as *const ()), + raw_address: None, + target_type_size: None, + }), + eq_literal: Literal::Address(123), + neq_literals: vec![Literal::Address(124), Literal::Int(123)], + }, + TestCase { + variable: Value::Pointer(PointerValue { + target_type: None, + type_id: None, + type_ident: TypeIdentity::no_namespace("MyPtr"), + value: Some(123usize as *const ()), + raw_address: None, + target_type_size: None, + }), + eq_literal: Literal::Address(123), + neq_literals: vec![Literal::Address(124), Literal::Int(123)], + }, + TestCase { + variable: Value::CEnum(CEnumValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("MyEnum"), + value: Some("Variant1".into()), + raw_address: None, + }), + eq_literal: Literal::EnumVariant("Variant1".to_string(), None), + neq_literals: vec![ + Literal::EnumVariant("Variant2".to_string(), None), + Literal::String("Variant1".to_string()), + ], + }, + TestCase { + variable: Value::RustEnum(RustEnumValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("MyEnum"), + value: Some(Box::new(Member { + field_name: Some("Variant1".to_string()), + value: Value::Struct(StructValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + members: vec![Member { + field_name: Some("Variant1".to_string()), + value: Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("int"), + value: Some(SupportedScalar::I64(100)), + raw_address: None, + }), + }], + type_params: Default::default(), + raw_address: None, + }), + })), + raw_address: None, + }), + eq_literal: Literal::EnumVariant( + "Variant1".to_string(), + Some(Box::new(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(100)), + ])))), + ), + neq_literals: vec![ + Literal::EnumVariant("Variant1".to_string(), Some(Box::new(Literal::Int(101)))), + Literal::EnumVariant("Variant2".to_string(), Some(Box::new(Literal::Int(100)))), + Literal::String("Variant1".to_string()), + ], + }, + ]; + + for tc in test_cases { + assert!(tc.variable.clone().match_literal(&tc.eq_literal)); + for neq_lit in tc.neq_literals { + assert!(!tc.variable.clone().match_literal(&neq_lit)); + } + } + } + + #[test] + fn test_equal_with_complex_literal() { + struct TestCase { + variable: Value, + eq_literals: Vec, + neq_literals: Vec, + } + + let test_cases = [ + TestCase { + variable: make_str_val("str1"), + eq_literals: vec![Literal::String("str1".to_string())], + neq_literals: vec![Literal::String("str2".to_string()), Literal::Int(1)], + }, + TestCase { + variable: make_string_val("string1"), + eq_literals: vec![Literal::String("string1".to_string())], + neq_literals: vec![Literal::String("string2".to_string()), Literal::Int(1)], + }, + TestCase { + variable: Value::Specialized { + value: Some(SpecializedValue::Uuid([ + 0xd0, 0x60, 0x66, 0x29, 0x78, 0x6a, 0x44, 0xbe, 0x9d, 0x49, 0xb7, 0x02, + 0x0f, 0x3e, 0xb0, 0x5a, + ])), + original: StructValue::default(), + }, + eq_literals: vec![Literal::String( + "d0606629-786a-44be-9d49-b7020f3eb05a".to_string(), + )], + neq_literals: vec![Literal::String( + "d0606629-786a-44be-9d49-b7020f3eb05b".to_string(), + )], + }, + TestCase { + variable: make_vector_val(vec![ + ArrayItem { + index: 0, + value: make_scalar_val("char", SupportedScalar::Char('a')), + }, + ArrayItem { + index: 1, + value: make_scalar_val("char", SupportedScalar::Char('b')), + }, + ArrayItem { + index: 2, + value: make_scalar_val("char", SupportedScalar::Char('c')), + }, + ArrayItem { + index: 3, + value: make_scalar_val("char", SupportedScalar::Char('c')), + }, + ]), + eq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Wildcard, + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + ])), + ], + neq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Wildcard, + ])), + ], + }, + TestCase { + variable: make_vecdeque_val(vec![ + ArrayItem { + index: 0, + value: make_scalar_val("char", SupportedScalar::Char('a')), + }, + ArrayItem { + index: 1, + value: make_scalar_val("char", SupportedScalar::Char('b')), + }, + ArrayItem { + index: 2, + value: make_scalar_val("char", SupportedScalar::Char('c')), + }, + ArrayItem { + index: 3, + value: make_scalar_val("char", SupportedScalar::Char('c')), + }, + ]), + eq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Wildcard, + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + ])), + ], + neq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Wildcard, + ])), + ], + }, + TestCase { + variable: make_hashset_val(vec![ + make_scalar_val("char", SupportedScalar::Char('a')), + make_scalar_val("char", SupportedScalar::Char('b')), + make_scalar_val("char", SupportedScalar::Char('c')), + ]), + eq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Wildcard, + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + ])), + ], + neq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + ])), + ], + }, + TestCase { + variable: make_btreeset_var_val(vec![ + make_scalar_val("char", SupportedScalar::Char('a')), + make_scalar_val("char", SupportedScalar::Char('b')), + make_scalar_val("char", SupportedScalar::Char('c')), + ]), + eq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Wildcard, + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + ])), + ], + neq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + ])), + ], + }, + TestCase { + variable: Value::Specialized { + value: Some(SpecializedValue::Cell(Box::new(make_scalar_val( + "int", + SupportedScalar::I64(100), + )))), + original: StructValue::default(), + }, + eq_literals: vec![Literal::Int(100)], + neq_literals: vec![Literal::Int(101), Literal::Float(100.1)], + }, + TestCase { + variable: Value::Array(ArrayValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("array_str"), + items: Some(vec![ + ArrayItem { + index: 0, + value: make_str_val("ab"), + }, + ArrayItem { + index: 1, + value: make_str_val("cd"), + }, + ArrayItem { + index: 2, + value: make_str_val("ef"), + }, + ]), + raw_address: None, + }), + eq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("ab".to_string())), + LiteralOrWildcard::Literal(Literal::String("cd".to_string())), + LiteralOrWildcard::Literal(Literal::String("ef".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("ab".to_string())), + LiteralOrWildcard::Literal(Literal::String("cd".to_string())), + LiteralOrWildcard::Wildcard, + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + ])), + ], + neq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("ab".to_string())), + LiteralOrWildcard::Literal(Literal::String("cd".to_string())), + LiteralOrWildcard::Literal(Literal::String("gj".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("ab".to_string())), + LiteralOrWildcard::Literal(Literal::String("cd".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("ab".to_string())), + LiteralOrWildcard::Literal(Literal::String("cd".to_string())), + LiteralOrWildcard::Literal(Literal::String("ef".to_string())), + LiteralOrWildcard::Literal(Literal::String("gj".to_string())), + ])), + ], + }, + TestCase { + variable: Value::Struct(StructValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("MyStruct"), + members: vec![ + Member { + field_name: Some("str_field".to_string()), + value: make_str_val("str1"), + }, + Member { + field_name: Some("vec_field".to_string()), + value: make_vector_val(vec![ + ArrayItem { + index: 0, + value: make_scalar_val("", SupportedScalar::I8(1)), + }, + ArrayItem { + index: 1, + value: make_scalar_val("", SupportedScalar::I8(2)), + }, + ]), + }, + Member { + field_name: Some("bool_field".to_string()), + value: make_scalar_val("", SupportedScalar::Bool(true)), + }, + ], + type_params: Default::default(), + raw_address: None, + }), + eq_literals: vec![ + Literal::AssocArray(HashMap::from([ + ( + "str_field".to_string(), + LiteralOrWildcard::Literal(Literal::String("str1".to_string())), + ), + ( + "vec_field".to_string(), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Literal(Literal::Int(2)), + ]))), + ), + ( + "bool_field".to_string(), + LiteralOrWildcard::Literal(Literal::Bool(true)), + ), + ])), + Literal::AssocArray(HashMap::from([ + ( + "str_field".to_string(), + LiteralOrWildcard::Literal(Literal::String("str1".to_string())), + ), + ( + "vec_field".to_string(), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Wildcard, + ]))), + ), + ("bool_field".to_string(), LiteralOrWildcard::Wildcard), + ])), + ], + neq_literals: vec![ + Literal::AssocArray(HashMap::from([ + ( + "str_field".to_string(), + LiteralOrWildcard::Literal(Literal::String("str2".to_string())), + ), + ( + "vec_field".to_string(), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Literal(Literal::Int(2)), + ]))), + ), + ( + "bool_field".to_string(), + LiteralOrWildcard::Literal(Literal::Bool(true)), + ), + ])), + Literal::AssocArray(HashMap::from([ + ( + "str_field".to_string(), + LiteralOrWildcard::Literal(Literal::String("str1".to_string())), + ), + ( + "vec_field".to_string(), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + ]))), + ), + ( + "bool_field".to_string(), + LiteralOrWildcard::Literal(Literal::Bool(true)), + ), + ])), + ], + }, + TestCase { + variable: Value::Struct(StructValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("MyTuple"), + members: vec![ + Member { + field_name: None, + value: make_str_val("str1"), + }, + Member { + field_name: None, + value: make_vector_val(vec![ + ArrayItem { + index: 0, + value: make_scalar_val("", SupportedScalar::I8(1)), + }, + ArrayItem { + index: 1, + value: make_scalar_val("", SupportedScalar::I8(2)), + }, + ]), + }, + Member { + field_name: None, + value: make_scalar_val("", SupportedScalar::Bool(true)), + }, + ], + type_params: Default::default(), + raw_address: None, + }), + eq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("str1".to_string())), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Literal(Literal::Int(2)), + ]))), + LiteralOrWildcard::Literal(Literal::Bool(true)), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("str1".to_string())), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Wildcard, + ]))), + LiteralOrWildcard::Wildcard, + ])), + ], + neq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("str1".to_string())), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Literal(Literal::Int(2)), + ]))), + LiteralOrWildcard::Literal(Literal::Bool(false)), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("str1".to_string())), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + ]))), + LiteralOrWildcard::Literal(Literal::Bool(true)), + ])), + ], + }, + ]; + + for tc in test_cases { + for eq_lit in tc.eq_literals { + assert!(tc.variable.clone().match_literal(&eq_lit)); + } + for neq_lit in tc.neq_literals { + assert!(!tc.variable.clone().match_literal(&neq_lit)); + } + } + } +} diff --git a/src/debugger/variable/value/parser.rs b/src/debugger/variable/value/parser.rs new file mode 100644 index 0000000..7f86602 --- /dev/null +++ b/src/debugger/variable/value/parser.rs @@ -0,0 +1,685 @@ +use crate::debugger::debugee::dwarf::eval::EvaluationContext; +use crate::debugger::debugee::dwarf::r#type::{ + ArrayType, ComplexType, ScalarType, StructureMember, TypeId, +}; +use crate::debugger::variable::value::specialization::VariableParserExtension; +use crate::debugger::variable::value::{ + ArrayItem, ArrayValue, CEnumValue, CModifiedValue, Member, PointerValue, RustEnumValue, + ScalarValue, SpecializedValue, StructValue, SubroutineValue, SupportedScalar, Value, +}; +use crate::debugger::variable::{Identity, ObjectBinaryRepr}; +use crate::debugger::TypeDeclaration; +use crate::version::Version; +use crate::version_switch; +use bytes::Bytes; +use gimli::{ + DW_ATE_address, DW_ATE_boolean, DW_ATE_float, DW_ATE_signed, DW_ATE_signed_char, + DW_ATE_unsigned, DW_ATE_unsigned_char, DW_ATE_ASCII, DW_ATE_UTF, +}; +use log::warn; +use std::collections::HashMap; +use std::fmt::Display; + +/// Additional information about value. +// +// FIXME: this modifier currently using only for TLS variables and should be deleted +// after minimal supported rust version will be greater than 1.80.0 +#[derive(Default)] +pub struct ValueModifiers { + tls: bool, + tls_const: bool, + const_tls_duplicate: bool, +} + +impl ValueModifiers { + pub fn from_identity(p_ctx: &ParseContext, ident: Identity) -> ValueModifiers { + let mut this = ValueModifiers::default(); + + let ver = p_ctx.evaluation_context.rustc_version().unwrap_or_default(); + if ver >= Version((1, 80, 0)) { + // not sure that value is tls, but some additional checks will be occurred on + // a value type at parsing stage + this.tls = ident.name.as_deref() == Some("VAL"); + + // This condition protects against duplication of the constant tls variables + if ident.namespace.contains(&["thread_local_const_init"]) + && !ident.namespace.contains(&["{closure#0}"]) + { + this.const_tls_duplicate = true; + } + } else { + let var_name_is_tls = ident.namespace.contains(&["__getit"]) + && (ident.name.as_deref() == Some("VAL") || ident.name.as_deref() == Some("__KEY")); + if var_name_is_tls { + this.tls = true; + if ident.name.as_deref() == Some("VAL") { + this.tls_const = true + } + } + } + + this + } +} + +pub struct ParseContext<'a> { + pub evaluation_context: &'a EvaluationContext<'a>, + pub type_graph: &'a ComplexType, +} + +/// Value parser object. +#[derive(Default)] +pub struct ValueParser; + +impl ValueParser { + /// Create parser for a type graph. + /// + /// # Arguments + /// + /// * `type_graph`: types that value parser can create + pub fn new() -> Self { + ValueParser + } + + fn parse_scalar( + &self, + data: Option, + type_id: TypeId, + r#type: &ScalarType, + ) -> ScalarValue { + fn render_scalar(data: Option) -> Option { + data.as_ref().map(|v| scalar_from_bytes::(&v.raw_data)) + } + let in_debugee_loc = data.as_ref().and_then(|d| d.address); + #[allow(non_upper_case_globals)] + let value_view = r#type.encoding.and_then(|encoding| match encoding { + DW_ATE_address => render_scalar::(data).map(SupportedScalar::Usize), + DW_ATE_signed_char => render_scalar::(data).map(SupportedScalar::I8), + DW_ATE_unsigned_char => render_scalar::(data).map(SupportedScalar::U8), + DW_ATE_signed => match r#type.byte_size.unwrap_or(0) { + 0 => Some(SupportedScalar::Empty()), + 1 => render_scalar::(data).map(SupportedScalar::I8), + 2 => render_scalar::(data).map(SupportedScalar::I16), + 4 => render_scalar::(data).map(SupportedScalar::I32), + 8 => { + if r#type.name.as_deref() == Some("isize") { + render_scalar::(data).map(SupportedScalar::Isize) + } else { + render_scalar::(data).map(SupportedScalar::I64) + } + } + 16 => render_scalar::(data).map(SupportedScalar::I128), + _ => { + warn!( + "parse scalar: unexpected signed size: {size:?}", + size = r#type.byte_size + ); + None + } + }, + DW_ATE_unsigned => match r#type.byte_size.unwrap_or(0) { + 0 => Some(SupportedScalar::Empty()), + 1 => render_scalar::(data).map(SupportedScalar::U8), + 2 => render_scalar::(data).map(SupportedScalar::U16), + 4 => render_scalar::(data).map(SupportedScalar::U32), + 8 => { + if r#type.name.as_deref() == Some("usize") { + render_scalar::(data).map(SupportedScalar::Usize) + } else { + render_scalar::(data).map(SupportedScalar::U64) + } + } + 16 => render_scalar::(data).map(SupportedScalar::U128), + _ => { + warn!( + "parse scalar: unexpected unsigned size: {size:?}", + size = r#type.byte_size + ); + None + } + }, + DW_ATE_float => match r#type.byte_size.unwrap_or(0) { + 4 => render_scalar::(data).map(SupportedScalar::F32), + 8 => render_scalar::(data).map(SupportedScalar::F64), + _ => { + warn!( + "parse scalar: unexpected float size: {size:?}", + size = r#type.byte_size + ); + None + } + }, + DW_ATE_boolean => render_scalar::(data).map(SupportedScalar::Bool), + DW_ATE_UTF => render_scalar::(data).map(|char| { + // WAITFORFIX: https://github.com/rust-lang/rust/issues/113819 + // this check is meaningfully here cause in case above there is a random bytes here, + // and it may lead to panic in other places + // (specially when someone tries to render this char) + if String::from_utf8(char.to_string().into_bytes()).is_err() { + SupportedScalar::Char('?') + } else { + SupportedScalar::Char(char) + } + }), + DW_ATE_ASCII => render_scalar::(data).map(SupportedScalar::Char), + _ => { + warn!("parse scalar: unexpected base type encoding: {encoding}"); + None + } + }); + + ScalarValue { + type_ident: r#type.identity(), + type_id: Some(type_id), + value: value_view, + raw_address: in_debugee_loc, + } + } + + fn parse_struct_variable( + &self, + ctx: &ParseContext, + data: Option, + type_id: TypeId, + type_params: HashMap>, + members: &[StructureMember], + ) -> StructValue { + let children = members + .iter() + .filter_map(|member| self.parse_struct_member(ctx, member, data.as_ref())) + .collect(); + + StructValue { + type_id: Some(type_id), + type_ident: ctx.type_graph.identity(type_id), + members: children, + type_params, + raw_address: data.and_then(|d| d.address), + } + } + + fn parse_struct_member( + &self, + ctx: &ParseContext, + member: &StructureMember, + parent_data: Option<&ObjectBinaryRepr>, + ) -> Option { + let name = member.name.clone(); + let Some(type_ref) = member.type_ref else { + warn!( + "parse structure: unknown type for member {}", + name.as_deref().unwrap_or_default() + ); + return None; + }; + let member_val = + parent_data.and_then(|data| member.value(ctx.evaluation_context, ctx.type_graph, data)); + let value = self.parse_inner(ctx, member_val, type_ref)?; + Some(Member { + field_name: member.name.clone(), + value, + }) + } + + fn parse_array( + &self, + ctx: &ParseContext, + data: Option, + type_id: TypeId, + array_decl: &ArrayType, + ) -> ArrayValue { + let items = array_decl + .bounds(ctx.evaluation_context) + .and_then(|bounds| { + let len = bounds.1 - bounds.0; + let data = data.as_ref()?; + let el_size = (array_decl.size_in_bytes(ctx.evaluation_context, ctx.type_graph)? + / len as u64) as usize; + let bytes = &data.raw_data; + let el_type_id = array_decl.element_type?; + + let (mut bytes_chunks, mut empty_chunks); + let raw_items_iter: &mut dyn Iterator = if el_size != 0 { + bytes_chunks = bytes.chunks(el_size).enumerate(); + &mut bytes_chunks + } else { + // if an item type is zst + let v: Vec<&[u8]> = vec![&[]; len as usize]; + empty_chunks = v.into_iter().enumerate(); + &mut empty_chunks + }; + + Some( + raw_items_iter + .filter_map(|(i, chunk)| { + let offset = i * el_size; + let data = ObjectBinaryRepr { + raw_data: bytes.slice_ref(chunk), + address: data.address.map(|addr| addr + offset), + size: el_size, + }; + + let value = self.parse_inner(ctx, Some(data), el_type_id)?; + Some(ArrayItem { + index: bounds.0 + i as i64, + value, + }) + }) + .collect::>(), + ) + }); + + ArrayValue { + items, + type_id: Some(type_id), + type_ident: ctx.type_graph.identity(type_id), + raw_address: data.and_then(|d| d.address), + } + } + + fn parse_c_enum( + &self, + ctx: &ParseContext, + data: Option, + type_id: TypeId, + discr_type: Option, + enumerators: &HashMap, + ) -> CEnumValue { + let in_debugee_loc = data.as_ref().and_then(|d| d.address); + let mb_discr = discr_type.and_then(|type_id| self.parse_inner(ctx, data, type_id)); + + let value = mb_discr.and_then(|discr| { + if let Value::Scalar(scalar) = discr { + scalar.try_as_number() + } else { + None + } + }); + + CEnumValue { + type_ident: ctx.type_graph.identity(type_id), + type_id: Some(type_id), + value: value.and_then(|val| enumerators.get(&val).cloned()), + raw_address: in_debugee_loc, + } + } + + fn parse_rust_enum( + &self, + ctx: &ParseContext, + data: Option, + type_id: TypeId, + discr_member: Option<&StructureMember>, + enumerators: &HashMap, StructureMember>, + ) -> RustEnumValue { + let discr_value = discr_member.and_then(|member| { + let discr = self.parse_struct_member(ctx, member, data.as_ref())?.value; + if let Value::Scalar(scalar) = discr { + return scalar.try_as_number(); + } + None + }); + + let enumerator = + discr_value.and_then(|v| enumerators.get(&Some(v)).or_else(|| enumerators.get(&None))); + + let enumerator = enumerator.and_then(|member| { + Some(Box::new(self.parse_struct_member( + ctx, + member, + data.as_ref(), + )?)) + }); + + RustEnumValue { + type_id: Some(type_id), + type_ident: ctx.type_graph.identity(type_id), + value: enumerator, + raw_address: data.and_then(|d| d.address), + } + } + + fn parse_pointer( + &self, + ctx: &ParseContext, + data: Option, + type_id: TypeId, + target_type: Option, + ) -> PointerValue { + let mb_ptr = data + .as_ref() + .map(|v| scalar_from_bytes::<*const ()>(&v.raw_data)); + + let mut type_ident = ctx.type_graph.identity(type_id); + if type_ident.is_unknown() { + if let Some(target_type) = target_type { + type_ident = ctx.type_graph.identity(target_type).as_deref_type(); + } + } + + PointerValue { + type_id: Some(type_id), + type_ident, + value: mb_ptr, + target_type, + target_type_size: None, + raw_address: data.and_then(|d| d.address), + } + } + + fn parse_inner_with_modifiers( + &self, + ctx: &ParseContext, + data: Option, + type_id: TypeId, + modifiers: &ValueModifiers, + ) -> Option { + let type_graph = ctx.type_graph; + match &type_graph.types[&type_id] { + TypeDeclaration::Scalar(scalar_type) => { + Some(Value::Scalar(self.parse_scalar(data, type_id, scalar_type))) + } + TypeDeclaration::Structure { + namespaces: type_ns_h, + members, + type_params, + name: struct_name, + .. + } => { + let struct_var = + self.parse_struct_variable(ctx, data, type_id, type_params.clone(), members); + + let parser_ext = VariableParserExtension::new(self); + // Reinterpret structure if underline data type is: + // - Vector + // - String + // - &str + // - tls variable + // - hashmaps + // - hashset + // - btree map + // - btree set + // - vecdeque + // - cell/refcell + // - rc/arc + // - uuid + // - SystemTime/Instant + if struct_name.as_deref() == Some("&str") { + return Some(Value::Specialized { + value: parser_ext.parse_str(ctx, &struct_var), + original: struct_var, + }); + }; + + if struct_name.as_deref() == Some("String") { + return Some(Value::Specialized { + value: parser_ext.parse_string(ctx, &struct_var), + original: struct_var, + }); + }; + + if struct_name.as_ref().map(|name| name.starts_with("Vec")) == Some(true) + && type_ns_h.contains(&["vec"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_vector(ctx, &struct_var, type_params), + original: struct_var, + }); + }; + + let rust_version = ctx.evaluation_context.rustc_version().unwrap_or_default(); + let type_is_tls = version_switch!( + rust_version, + (1, 0, 0) ..= (1, 76, u32::MAX) => type_ns_h.contains(&["std", "sys", "common", "thread_local", "fast_local"]), + (1, 77, 0) ..= (1, 77, u32::MAX) => type_ns_h.contains(&["std", "sys", "pal", "common", "thread_local", "fast_local"]), + (1, 78, 0) ..= (1, u32::MAX, u32::MAX) => type_ns_h.contains(&["std", "sys", "thread_local", "fast_local"]), + ).unwrap_or_default(); + + if type_is_tls || modifiers.tls { + return if rust_version >= Version((1, 80, 0)) { + match parser_ext.parse_tls(ctx, &struct_var, type_params) { + Ok(Some(value)) => Some(Value::Specialized { + value: Some(SpecializedValue::Tls(value)), + original: struct_var, + }), + Ok(None) => None, + Err(e) => { + warn!(target: "debugger", "{:#}", e); + Some(Value::Specialized { + value: None, + original: struct_var, + }) + } + } + } else { + Some(Value::Specialized { + value: parser_ext.parse_tls_old( + ctx, + &struct_var, + type_params, + modifiers.tls_const, + ), + original: struct_var, + }) + }; + } + + if struct_name.as_ref().map(|name| name.starts_with("HashMap")) == Some(true) + && type_ns_h.contains(&["collections", "hash", "map"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_hashmap(ctx, &struct_var), + original: struct_var, + }); + }; + + if struct_name.as_ref().map(|name| name.starts_with("HashSet")) == Some(true) + && type_ns_h.contains(&["collections", "hash", "set"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_hashset(ctx, &struct_var), + original: struct_var, + }); + }; + + if struct_name + .as_ref() + .map(|name| name.starts_with("BTreeMap")) + == Some(true) + && type_ns_h.contains(&["collections", "btree", "map"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_btree_map(ctx, &struct_var, type_id, type_params), + original: struct_var, + }); + }; + + if struct_name + .as_ref() + .map(|name| name.starts_with("BTreeSet")) + == Some(true) + && type_ns_h.contains(&["collections", "btree", "set"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_btree_set(&struct_var), + original: struct_var, + }); + }; + + if struct_name + .as_ref() + .map(|name| name.starts_with("VecDeque")) + == Some(true) + && type_ns_h.contains(&["collections", "vec_deque"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_vec_dequeue(ctx, &struct_var, type_params), + original: struct_var, + }); + }; + + if struct_name.as_ref().map(|name| name.starts_with("Cell")) == Some(true) + && type_ns_h.contains(&["cell"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_cell(&struct_var), + original: struct_var, + }); + }; + + if struct_name.as_ref().map(|name| name.starts_with("RefCell")) == Some(true) + && type_ns_h.contains(&["cell"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_refcell(&struct_var), + original: struct_var, + }); + }; + + if struct_name + .as_ref() + .map(|name| name.starts_with("Rc<") | name.starts_with("Weak<")) + == Some(true) + && type_ns_h.contains(&["rc"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_rc(&struct_var), + original: struct_var, + }); + }; + + if struct_name + .as_ref() + .map(|name| name.starts_with("Arc<") | name.starts_with("Weak<")) + == Some(true) + && type_ns_h.contains(&["sync"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_arc(&struct_var), + original: struct_var, + }); + }; + + if struct_name.as_ref().map(|name| name == "Uuid") == Some(true) + && type_ns_h.contains(&["uuid"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_uuid(&struct_var), + original: struct_var, + }); + }; + + if struct_name.as_ref().map(|name| name == "Instant") == Some(true) + && type_ns_h.contains(&["std", "time"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_instant(&struct_var), + original: struct_var, + }); + }; + + if struct_name.as_ref().map(|name| name == "SystemTime") == Some(true) + && type_ns_h.contains(&["std", "time"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_sys_time(&struct_var), + original: struct_var, + }); + }; + + Some(Value::Struct(struct_var)) + } + TypeDeclaration::Array(decl) => { + Some(Value::Array(self.parse_array(ctx, data, type_id, decl))) + } + TypeDeclaration::CStyleEnum { + discr_type, + enumerators, + .. + } => Some(Value::CEnum(self.parse_c_enum( + ctx, + data, + type_id, + *discr_type, + enumerators, + ))), + TypeDeclaration::RustEnum { + discr_type, + enumerators, + .. + } => Some(Value::RustEnum(self.parse_rust_enum( + ctx, + data, + type_id, + discr_type.as_ref().map(|t| t.as_ref()), + enumerators, + ))), + TypeDeclaration::Pointer { target_type, .. } => Some(Value::Pointer( + self.parse_pointer(ctx, data, type_id, *target_type), + )), + TypeDeclaration::Union { members, .. } => { + let struct_var = + self.parse_struct_variable(ctx, data, type_id, HashMap::new(), members); + Some(Value::Struct(struct_var)) + } + TypeDeclaration::Subroutine { return_type, .. } => { + let ret_type = return_type.map(|t_id| ctx.type_graph.identity(t_id)); + let fn_var = SubroutineValue { + type_id: Some(type_id), + return_type_ident: ret_type, + address: data.and_then(|d| d.address), + }; + Some(Value::Subroutine(fn_var)) + } + TypeDeclaration::ModifiedType { + inner, modifier, .. + } => { + let in_debugee_loc = data.as_ref().and_then(|d| d.address); + Some(Value::CModifiedVariable(CModifiedValue { + type_id: Some(type_id), + type_ident: ctx.type_graph.identity(type_id), + modifier: *modifier, + value: inner.and_then(|inner_type| { + Some(Box::new(self.parse_inner(ctx, data, inner_type)?)) + }), + address: in_debugee_loc, + })) + } + } + } + + pub(super) fn parse_inner( + &self, + ctx: &ParseContext, + data: Option, + type_id: TypeId, + ) -> Option { + self.parse_inner_with_modifiers(ctx, data, type_id, &ValueModifiers::default()) + } + + /// Return a new value of a root type from the underlying type graph. + /// + /// # Arguments + /// + /// * `ctx`: parsing context + /// * `bin_data`: binary value representation from debugee memory + /// * `modifiers`: value addition info + pub fn parse( + self, + ctx: &ParseContext, + bin_data: Option, + modifiers: &ValueModifiers, + ) -> Option { + if modifiers.const_tls_duplicate { + return None; + } + + self.parse_inner_with_modifiers(ctx, bin_data, ctx.type_graph.root(), modifiers) + } +} + +#[inline(never)] +fn scalar_from_bytes(bytes: &Bytes) -> T { + let ptr = bytes.as_ptr(); + unsafe { std::ptr::read_unaligned::(ptr as *const T) } +} diff --git a/src/debugger/variable/specialization/btree.rs b/src/debugger/variable/value/specialization/btree.rs similarity index 94% rename from src/debugger/variable/specialization/btree.rs rename to src/debugger/variable/value/specialization/btree.rs index 879a489..1179079 100644 --- a/src/debugger/variable/specialization/btree.rs +++ b/src/debugger/variable/value/specialization/btree.rs @@ -1,11 +1,10 @@ use crate::debugger; -use crate::debugger::debugee::dwarf::r#type::{ - ComplexType, EvaluationContext, StructureMember, TypeIdentity, -}; -use crate::debugger::variable::select::ObjectBinaryRepr; -use crate::debugger::variable::AssumeError::NoType; -use crate::debugger::variable::ParsingError::ReadDebugeeMemory; -use crate::debugger::variable::{AssumeError, ParsingError}; +use crate::debugger::debugee::dwarf::eval::EvaluationContext; +use crate::debugger::debugee::dwarf::r#type::{ComplexType, StructureMember, TypeId, TypeIdentity}; +use crate::debugger::variable::value::AssumeError::NoType; +use crate::debugger::variable::value::ParsingError::ReadDebugeeMemory; +use crate::debugger::variable::value::{AssumeError, ParsingError}; +use crate::debugger::variable::ObjectBinaryRepr; use crate::debugger::TypeDeclaration; use fallible_iterator::FallibleIterator; use std::mem; @@ -37,9 +36,9 @@ impl LeafNodeMarkup { /// `value_type_id` as an id of V type. Result node are closest to type with id `map_id`. fn from_type( r#type: &ComplexType, - map_id: TypeIdentity, - key_type_id: TypeIdentity, - value_type_id: TypeIdentity, + map_id: TypeId, + key_type_id: TypeId, + value_type_id: TypeId, ) -> Option { let mut iterator = r#type.bfs_iterator(map_id); @@ -100,9 +99,9 @@ impl InternalNodeMarkup { /// `value_type_id` as an id of V type. Result node are closest to type with id `map_id`. fn from_type( r#type: &ComplexType, - map_id: TypeIdentity, - key_type_id: TypeIdentity, - value_type_id: TypeIdentity, + map_id: TypeId, + key_type_id: TypeId, + value_type_id: TypeId, ) -> Option { let mut iterator = r#type.bfs_iterator(map_id); @@ -451,8 +450,8 @@ pub struct BTreeReflection<'a> { internal_markup: InternalNodeMarkup, leaf_markup: LeafNodeMarkup, r#type: &'a ComplexType, - k_type_id: TypeIdentity, - v_type_id: TypeIdentity, + k_type_id: TypeId, + v_type_id: TypeId, } impl<'a> BTreeReflection<'a> { @@ -461,9 +460,9 @@ impl<'a> BTreeReflection<'a> { r#type: &'a ComplexType, root_ptr: *const (), root_height: usize, - map_id: TypeIdentity, - k_type_id: TypeIdentity, - v_type_id: TypeIdentity, + map_id: TypeId, + k_type_id: TypeId, + v_type_id: TypeId, ) -> Result { Ok(Self { root: root_ptr, @@ -509,11 +508,15 @@ impl<'a> BTreeReflection<'a> { let k_size = self .r#type .type_size_in_bytes(eval_ctx, self.k_type_id) - .ok_or(AssumeError::UnknownSize("btree key type".into()))?; + .ok_or(AssumeError::UnknownSize(TypeIdentity::no_namespace( + "btree key type", + )))?; let v_size = self .r#type .type_size_in_bytes(eval_ctx, self.v_type_id) - .ok_or(AssumeError::UnknownSize("btree value type".into()))?; + .ok_or(AssumeError::UnknownSize(TypeIdentity::no_namespace( + "btree value type", + )))?; Ok(KVIterator { reflection: self, diff --git a/src/debugger/variable/specialization/hashbrown.rs b/src/debugger/variable/value/specialization/hashbrown.rs similarity index 100% rename from src/debugger/variable/specialization/hashbrown.rs rename to src/debugger/variable/value/specialization/hashbrown.rs diff --git a/src/debugger/variable/value/specialization/mod.rs b/src/debugger/variable/value/specialization/mod.rs new file mode 100644 index 0000000..4c54317 --- /dev/null +++ b/src/debugger/variable/value/specialization/mod.rs @@ -0,0 +1,1040 @@ +use crate::debugger::debugee::dwarf::r#type::{TypeId, TypeIdentity}; +use crate::debugger::variable::render::RenderValue; +use crate::debugger::variable::value::parser::{ParseContext, ValueParser}; +use crate::debugger::variable::value::specialization::btree::BTreeReflection; +use crate::debugger::variable::value::specialization::hashbrown::HashmapReflection; +use crate::debugger::variable::value::AssumeError::{ + TypeParameterNotFound, TypeParameterTypeNotFound, UnexpectedType, +}; +use crate::debugger::variable::value::ParsingError::Assume; +use crate::debugger::variable::value::{ + ArrayItem, ArrayValue, AssumeError, FieldOrIndex, Member, ParsingError, ScalarValue, + SupportedScalar, +}; +use crate::debugger::variable::value::{PointerValue, StructValue, Value}; +use crate::debugger::variable::ObjectBinaryRepr; +use crate::{debugger, version_switch, weak_error}; +use anyhow::Context; +use bytes::Bytes; +use fallible_iterator::FallibleIterator; +use std::collections::HashMap; +use AssumeError::{FieldNotFound, IncompleteInterp, UnknownSize}; + +mod btree; +mod hashbrown; + +/// During program execution, the debugger may encounter uninitialized variables. +/// For example, look at this code: +/// ```rust +/// let res: Result<(), String> = Ok(()); +/// if let Err(e) = res { +/// unreachable!(); +/// } +/// ``` +/// +/// if stop debugger at line 2 and consider a variable `e` - capacity of this vector +/// may be over 9000, this is obviously not the size that user expects. +/// Therefore, artificial restrictions on size and capacity are introduced. This behavior may be +/// changed in the future. +const LEN_GUARD: i64 = 10_000; +const CAP_GUARD: i64 = 10_000; + +fn guard_len(len: i64) -> i64 { + if len > LEN_GUARD { + LEN_GUARD + } else { + len + } +} + +fn guard_cap(cap: i64) -> i64 { + if cap > CAP_GUARD { + CAP_GUARD + } else { + cap + } +} + +#[derive(Clone, PartialEq)] +pub struct VecValue { + pub structure: StructValue, +} + +impl VecValue { + pub fn slice(&mut self, left: Option, right: Option) { + debug_assert!(matches!( + self.structure.members.get_mut(0).map(|m| &m.value), + Some(Value::Array(_)) + )); + + if let Some(Member { + value: Value::Array(array), + .. + }) = self.structure.members.get_mut(0) + { + array.slice(left, right); + } + } +} + +#[derive(Clone, PartialEq)] +pub struct StringVariable { + pub value: String, +} + +#[derive(Clone, PartialEq)] +pub struct HashMapVariable { + pub type_ident: TypeIdentity, + pub kv_items: Vec<(Value, Value)>, +} + +#[derive(Clone, PartialEq)] +pub struct HashSetVariable { + pub type_ident: TypeIdentity, + pub items: Vec, +} + +#[derive(Clone, PartialEq)] +pub struct StrVariable { + pub value: String, +} + +#[derive(Clone, PartialEq)] +pub struct TlsVariable { + pub inner_value: Option>, + pub inner_type: TypeIdentity, +} + +#[derive(Clone, PartialEq)] +pub enum SpecializedValue { + Vector(VecValue), + VecDeque(VecValue), + HashMap(HashMapVariable), + HashSet(HashSetVariable), + BTreeMap(HashMapVariable), + BTreeSet(HashSetVariable), + String(StringVariable), + Str(StrVariable), + Tls(TlsVariable), + Cell(Box), + RefCell(Box), + Rc(PointerValue), + Arc(PointerValue), + Uuid([u8; 16]), + SystemTime((i64, u32)), + Instant((i64, u32)), +} + +pub struct VariableParserExtension<'a> { + parser: &'a ValueParser, +} + +impl<'a> VariableParserExtension<'a> { + pub fn new(parser: &'a ValueParser) -> Self { + Self { parser } + } + + pub fn parse_str( + &self, + ctx: &ParseContext, + structure: &StructValue, + ) -> Option { + weak_error!(self + .parse_str_inner(ctx, Value::Struct(structure.clone())) + .context("&str interpretation")) + .map(SpecializedValue::Str) + } + + fn parse_str_inner(&self, ctx: &ParseContext, val: Value) -> Result { + let len = val.assume_field_as_scalar_number("length")?; + let len = guard_len(len); + + let data_ptr = val.assume_field_as_pointer("data_ptr")?; + + let data = debugger::read_memory_by_pid( + ctx.evaluation_context.expl_ctx.pid_on_focus(), + data_ptr as usize, + len as usize, + ) + .map(Bytes::from)?; + + Ok(StrVariable { + value: String::from_utf8(data.to_vec()).map_err(AssumeError::from)?, + }) + } + + pub fn parse_string( + &self, + ctx: &ParseContext, + structure: &StructValue, + ) -> Option { + weak_error!(self + .parse_string_inner(ctx, Value::Struct(structure.clone())) + .context("String interpretation")) + .map(SpecializedValue::String) + } + + fn parse_string_inner( + &self, + ctx: &ParseContext, + val: Value, + ) -> Result { + let len = val.assume_field_as_scalar_number("len")?; + let len = guard_len(len); + + let data_ptr = val.assume_field_as_pointer("pointer")?; + + let data = debugger::read_memory_by_pid( + ctx.evaluation_context.expl_ctx.pid_on_focus(), + data_ptr as usize, + len as usize, + )?; + + Ok(StringVariable { + value: String::from_utf8(data).map_err(AssumeError::from)?, + }) + } + + pub fn parse_vector( + &self, + ctx: &ParseContext, + structure: &StructValue, + type_params: &HashMap>, + ) -> Option { + weak_error!(self + .parse_vector_inner(ctx, Value::Struct(structure.clone()), type_params) + .context("Vec interpretation")) + .map(SpecializedValue::Vector) + } + + fn parse_vector_inner( + &self, + ctx: &ParseContext, + val: Value, + type_params: &HashMap>, + ) -> Result { + let inner_type = type_params + .get("T") + .ok_or(TypeParameterNotFound("T"))? + .ok_or(TypeParameterTypeNotFound("T"))?; + let len = val.assume_field_as_scalar_number("len")?; + let len = guard_len(len); + + let cap = extract_capacity(ctx, &val)? as i64; + let cap = guard_cap(cap); + + let data_ptr = val.assume_field_as_pointer("pointer")? as usize; + + let el_type = ctx.type_graph; + let el_type_size = el_type + .type_size_in_bytes(ctx.evaluation_context, inner_type) + .ok_or(UnknownSize(el_type.identity(inner_type)))? as usize; + + let raw_data = debugger::read_memory_by_pid( + ctx.evaluation_context.expl_ctx.pid_on_focus(), + data_ptr, + len as usize * el_type_size, + ) + .map(Bytes::from)?; + + let (mut bytes_chunks, mut empty_chunks); + let raw_items_iter: &mut dyn Iterator = if el_type_size != 0 { + bytes_chunks = raw_data.chunks(el_type_size).enumerate(); + &mut bytes_chunks + } else { + // if an item type is zst + let v: Vec<&[u8]> = vec![&[]; len as usize]; + empty_chunks = v.into_iter().enumerate(); + &mut empty_chunks + }; + + let items = raw_items_iter + .filter_map(|(i, chunk)| { + let data = ObjectBinaryRepr { + raw_data: raw_data.slice_ref(chunk), + address: Some(data_ptr + (i * el_type_size)), + size: el_type_size, + }; + Some(ArrayItem { + index: i as i64, + value: self.parser.parse_inner(ctx, Some(data), inner_type)?, + }) + }) + .collect::>(); + + Ok(VecValue { + structure: StructValue { + type_id: None, + type_ident: val.r#type().clone(), + members: vec![ + Member { + field_name: Some("buf".to_owned()), + value: Value::Array(ArrayValue { + type_id: None, + type_ident: ctx.type_graph.identity(inner_type).as_array_type(), + items: Some(items), + // set to `None` because the address operator unavailable for spec vars + raw_address: None, + }), + }, + Member { + field_name: Some("cap".to_owned()), + value: Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("usize"), + value: Some(SupportedScalar::Usize(cap as usize)), + // set to `None` because the address operator unavailable for spec vars + raw_address: None, + }), + }, + ], + type_params: type_params.clone(), + // set to `None` because the address operator unavailable for spec vars + raw_address: None, + }, + }) + } + + pub fn parse_tls_old( + &self, + ctx: &ParseContext, + structure: &StructValue, + type_params: &HashMap>, + is_const_initialized: bool, + ) -> Option { + let tls_var = if is_const_initialized { + self.parse_const_init_tls_inner(ctx, Value::Struct(structure.clone()), type_params) + } else { + self.parse_tls_inner_old(ctx, Value::Struct(structure.clone()), type_params) + }; + + weak_error!(tls_var.context("TLS variable interpretation")).map(SpecializedValue::Tls) + } + + fn parse_const_init_tls_inner( + &self, + ctx: &ParseContext, + inner: Value, + type_params: &HashMap>, + ) -> Result { + let value_type = type_params + .get("T") + .ok_or(TypeParameterNotFound("T"))? + .ok_or(TypeParameterTypeNotFound("T"))?; + let value = inner.field("value"); + Ok(TlsVariable { + inner_value: value.map(Box::new), + inner_type: ctx.type_graph.identity(value_type), + }) + } + + fn parse_tls_inner_old( + &self, + ctx: &ParseContext, + inner_val: Value, + type_params: &HashMap>, + ) -> Result { + let inner_type = type_params + .get("T") + .ok_or(TypeParameterNotFound("T"))? + .ok_or(TypeParameterTypeNotFound("T"))?; + + let inner = inner_val + .bfs_iterator() + .find_map(|(field, child)| { + (field == FieldOrIndex::Field(Some("inner"))).then_some(child) + }) + .ok_or(FieldNotFound("inner"))?; + let inner_option = inner.assume_field_as_rust_enum("value")?; + let inner_value = inner_option.value.ok_or(IncompleteInterp("value"))?; + + // we assume that DWARF representation of tls variable contains ::Option + if let Value::Struct(ref opt_variant) = inner_value.value { + let tls_value = if opt_variant.type_ident.name() == Some("None") { + None + } else { + Some(Box::new( + inner_value + .value + .bfs_iterator() + .find_map(|(field, child)| { + (field == FieldOrIndex::Field(Some("__0"))).then_some(child) + }) + .ok_or(FieldNotFound("__0"))? + .clone(), + )) + }; + + return Ok(TlsVariable { + inner_value: tls_value, + inner_type: ctx.type_graph.identity(inner_type), + }); + } + + Err(ParsingError::Assume(IncompleteInterp( + "expect TLS inner value as option", + ))) + } + + pub fn parse_tls( + &self, + ctx: &ParseContext, + structure: &StructValue, + type_params: &HashMap>, + ) -> Result, ParsingError> { + if structure.type_ident.namespace().contains(&["eager"]) { + // constant tls + self.parse_const_tls_inner(ctx, Value::Struct(structure.clone()), type_params) + } else { + self.parse_tls_inner(ctx, Value::Struct(structure.clone()), type_params) + } + } + + fn parse_tls_inner( + &self, + ctx: &ParseContext, + inner_val: Value, + type_params: &HashMap>, + ) -> Result, ParsingError> { + let inner_type = type_params + .get("T") + .ok_or(TypeParameterNotFound("T"))? + .ok_or(TypeParameterTypeNotFound("T"))?; + + let state = inner_val + .bfs_iterator() + .find_map(|(field, child)| { + (field == FieldOrIndex::Field(Some("state"))).then_some(child) + }) + .ok_or(FieldNotFound("state"))?; + + let state = state.assume_field_as_rust_enum("value")?; + if let Some(member) = state.value { + let tls_val = if member.field_name.as_deref() == Some("Alive") { + member.value.field("__0").map(Box::new) + } else { + return Ok(None); + }; + + return Ok(Some(TlsVariable { + inner_value: tls_val, + inner_type: ctx.type_graph.identity(inner_type), + })); + }; + Ok(None) + } + + fn parse_const_tls_inner( + &self, + ctx: &ParseContext, + inner_val: Value, + type_params: &HashMap>, + ) -> Result, ParsingError> { + let inner_type = type_params + .get("T") + .ok_or(TypeParameterNotFound("T"))? + .ok_or(TypeParameterTypeNotFound("T"))?; + + if let Some(val) = inner_val.field("val") { + return Ok(Some(TlsVariable { + inner_value: val.field("value").map(Box::new), + inner_type: ctx.type_graph.identity(inner_type), + })); + } + + Err(ParsingError::Assume(IncompleteInterp( + "expect TLS inner value as `val` field", + ))) + } + + pub fn parse_hashmap( + &self, + ctx: &ParseContext, + structure: &StructValue, + ) -> Option { + weak_error!(self + .parse_hashmap_inner(ctx, Value::Struct(structure.clone())) + .context("HashMap interpretation")) + .map(SpecializedValue::HashMap) + } + + fn parse_hashmap_inner( + &self, + ctx: &ParseContext, + val: Value, + ) -> Result { + let ctrl = val.assume_field_as_pointer("pointer")?; + let bucket_mask = val.assume_field_as_scalar_number("bucket_mask")?; + + let table = val.assume_field_as_struct("table")?; + let kv_type = table + .type_params + .get("T") + .ok_or(TypeParameterNotFound("T"))? + .ok_or(TypeParameterTypeNotFound("T"))?; + + let r#type = ctx.type_graph; + let kv_size = r#type + .type_size_in_bytes(ctx.evaluation_context, kv_type) + .ok_or(UnknownSize(r#type.identity(kv_type)))?; + + let reflection = + HashmapReflection::new(ctrl as *mut u8, bucket_mask as usize, kv_size as usize); + + let iterator = reflection.iter(ctx.evaluation_context.expl_ctx.pid_on_focus())?; + let kv_items = iterator + .map_err(ParsingError::from) + .filter_map(|bucket| { + let raw_data = bucket.read(ctx.evaluation_context.expl_ctx.pid_on_focus()); + let data = weak_error!(raw_data).map(|d| ObjectBinaryRepr { + raw_data: Bytes::from(d), + address: Some(bucket.location()), + size: bucket.size(), + }); + + let tuple = self.parser.parse_inner(ctx, data, kv_type); + + if let Some(Value::Struct(mut tuple)) = tuple { + if tuple.members.len() == 2 { + let v = tuple.members.pop(); + let k = tuple.members.pop(); + return Ok(Some((k.unwrap().value, v.unwrap().value))); + } + } + + Err(Assume(UnexpectedType("hashmap bucket"))) + }) + .collect()?; + + Ok(HashMapVariable { + type_ident: val.r#type().to_owned(), + kv_items, + }) + } + + pub fn parse_hashset( + &self, + ctx: &ParseContext, + structure: &StructValue, + ) -> Option { + weak_error!(self + .parse_hashset_inner(ctx, Value::Struct(structure.clone())) + .context("HashSet interpretation")) + .map(SpecializedValue::HashSet) + } + + fn parse_hashset_inner( + &self, + ctx: &ParseContext, + val: Value, + ) -> Result { + let ctrl = val.assume_field_as_pointer("pointer")?; + let bucket_mask = val.assume_field_as_scalar_number("bucket_mask")?; + + let table = val.assume_field_as_struct("table")?; + let kv_type = table + .type_params + .get("T") + .ok_or(TypeParameterNotFound("T"))? + .ok_or(TypeParameterTypeNotFound("T"))?; + let r#type = ctx.type_graph; + let kv_size = r#type + .type_size_in_bytes(ctx.evaluation_context, kv_type) + .ok_or_else(|| UnknownSize(r#type.identity(kv_type)))?; + + let reflection = + HashmapReflection::new(ctrl as *mut u8, bucket_mask as usize, kv_size as usize); + + let iterator = reflection.iter(ctx.evaluation_context.expl_ctx.pid_on_focus())?; + let items = iterator + .map_err(ParsingError::from) + .filter_map(|bucket| { + let raw_data = bucket.read(ctx.evaluation_context.expl_ctx.pid_on_focus()); + let data = weak_error!(raw_data).map(|d| ObjectBinaryRepr { + raw_data: Bytes::from(d), + address: Some(bucket.location()), + size: bucket.size(), + }); + + let tuple = self.parser.parse_inner(ctx, data, kv_type); + + if let Some(Value::Struct(mut tuple)) = tuple { + if tuple.members.len() == 2 { + let _ = tuple.members.pop(); + let k = tuple.members.pop().unwrap(); + return Ok(Some(k.value)); + } + } + + Err(Assume(UnexpectedType("hashset bucket"))) + }) + .collect()?; + + Ok(HashSetVariable { + type_ident: val.r#type().to_owned(), + items, + }) + } + + pub fn parse_btree_map( + &self, + ctx: &ParseContext, + structure: &StructValue, + identity: TypeId, + type_params: &HashMap>, + ) -> Option { + weak_error!(self + .parse_btree_map_inner(ctx, Value::Struct(structure.clone()), identity, type_params) + .context("BTreeMap interpretation")) + .map(SpecializedValue::BTreeMap) + } + + fn parse_btree_map_inner( + &self, + ctx: &ParseContext, + val: Value, + identity: TypeId, + type_params: &HashMap>, + ) -> Result { + let height = val.assume_field_as_scalar_number("height")?; + let ptr = val.assume_field_as_pointer("pointer")?; + + let k_type = type_params + .get("K") + .ok_or(TypeParameterNotFound("K"))? + .ok_or(TypeParameterTypeNotFound("K"))?; + let v_type = type_params + .get("V") + .ok_or(TypeParameterNotFound("V"))? + .ok_or(TypeParameterTypeNotFound("V"))?; + + let reflection = BTreeReflection::new( + ctx.type_graph, + ptr, + height as usize, + identity, + k_type, + v_type, + )?; + let iterator = reflection.iter(ctx.evaluation_context)?; + let kv_items = iterator + .map_err(ParsingError::from) + .filter_map(|(k, v)| { + let Some(key) = self.parser.parse_inner(ctx, Some(k), k_type) else { + return Ok(None); + }; + + let Some(value) = self.parser.parse_inner(ctx, Some(v), v_type) else { + return Ok(None); + }; + + Ok(Some((key, value))) + }) + .collect::>()?; + + Ok(HashMapVariable { + type_ident: val.r#type().to_owned(), + kv_items, + }) + } + + pub fn parse_btree_set(&self, structure: &StructValue) -> Option { + weak_error!(self + .parse_btree_set_inner(Value::Struct(structure.clone())) + .context("BTreeSet interpretation")) + .map(SpecializedValue::BTreeSet) + } + + fn parse_btree_set_inner(&self, val: Value) -> Result { + let inner_map = val + .bfs_iterator() + .find_map(|(field_or_idx, child)| { + if let Value::Specialized { + value: Some(SpecializedValue::BTreeMap(ref map)), + .. + } = child + { + if field_or_idx == FieldOrIndex::Field(Some("map")) { + return Some(map.clone()); + } + } + None + }) + .ok_or(IncompleteInterp("BTreeSet"))?; + + Ok(HashSetVariable { + type_ident: val.r#type().to_owned(), + items: inner_map.kv_items.into_iter().map(|(k, _)| k).collect(), + }) + } + + pub fn parse_vec_dequeue( + &self, + ctx: &ParseContext, + structure: &StructValue, + type_params: &HashMap>, + ) -> Option { + weak_error!(self + .parse_vec_dequeue_inner(ctx, Value::Struct(structure.clone()), type_params) + .context("VeqDequeue interpretation")) + .map(SpecializedValue::VecDeque) + } + + fn parse_vec_dequeue_inner( + &self, + ctx: &ParseContext, + val: Value, + type_params: &HashMap>, + ) -> Result { + let inner_type = type_params + .get("T") + .ok_or(TypeParameterNotFound("T"))? + .ok_or(TypeParameterTypeNotFound("T"))?; + let len = val.assume_field_as_scalar_number("len")? as usize; + let len = guard_len(len as i64) as usize; + + let r#type = ctx.type_graph; + let el_type_size = r#type + .type_size_in_bytes(ctx.evaluation_context, inner_type) + .ok_or_else(|| UnknownSize(r#type.identity(inner_type)))? + as usize; + let cap = if el_type_size == 0 { + usize::MAX + } else { + extract_capacity(ctx, &val)? + }; + let head = val.assume_field_as_scalar_number("head")? as usize; + + let wrapped_start = if head >= cap { head - cap } else { head }; + let head_len = cap - wrapped_start; + + let slice_ranges = if head_len >= len { + (wrapped_start..wrapped_start + len, 0..0) + } else { + let tail_len = len - head_len; + (wrapped_start..cap, 0..tail_len) + }; + + let data_ptr = val.assume_field_as_pointer("pointer")? as usize; + + let data = debugger::read_memory_by_pid( + ctx.evaluation_context.expl_ctx.pid_on_focus(), + data_ptr, + cap * el_type_size, + ) + .map(Bytes::from)?; + + let items = slice_ranges + .0 + .chain(slice_ranges.1) + .enumerate() + .filter_map(|(i, real_idx)| { + let offset = real_idx * el_type_size; + let el_raw_data = &data[offset..(real_idx + 1) * el_type_size]; + let el_data = ObjectBinaryRepr { + raw_data: data.slice_ref(el_raw_data), + address: Some(data_ptr + offset), + size: el_type_size, + }; + + Some(ArrayItem { + index: i as i64, + value: self.parser.parse_inner(ctx, Some(el_data), inner_type)?, + }) + }) + .collect::>(); + + Ok(VecValue { + structure: StructValue { + type_id: None, + type_ident: val.r#type().to_owned(), + members: vec![ + Member { + field_name: Some("buf".to_owned()), + value: Value::Array(ArrayValue { + type_id: None, + type_ident: ctx.type_graph.identity(inner_type).as_array_type(), + items: Some(items), + // set to `None` because the address operator unavailable for spec vars + raw_address: None, + }), + }, + Member { + field_name: Some("cap".to_owned()), + value: Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("usize"), + value: Some(SupportedScalar::Usize(if el_type_size == 0 { + 0 + } else { + cap + })), + // set to `None` because the address operator unavailable for spec vars + raw_address: None, + }), + }, + ], + type_params: type_params.clone(), + // set to `None` because the address operator unavailable for spec vars + raw_address: None, + }, + }) + } + + pub fn parse_cell(&self, structure: &StructValue) -> Option { + weak_error!(self + .parse_cell_inner(Value::Struct(structure.clone())) + .context("Cell interpretation")) + .map(Box::new) + .map(SpecializedValue::Cell) + } + + fn parse_cell_inner(&self, val: Value) -> Result { + let unsafe_cell = val.assume_field_as_struct("value")?; + let member = unsafe_cell + .members + .first() + .ok_or(IncompleteInterp("UnsafeCell"))?; + Ok(member.value.clone()) + } + + pub fn parse_refcell(&self, structure: &StructValue) -> Option { + weak_error!(self + .parse_refcell_inner(Value::Struct(structure.clone())) + .context("RefCell interpretation")) + .map(Box::new) + .map(SpecializedValue::RefCell) + } + + fn parse_refcell_inner(&self, val: Value) -> Result { + let borrow = val + .bfs_iterator() + .find_map(|(_, child)| { + if let Value::Specialized { + value: Some(SpecializedValue::Cell(val)), + .. + } = child + { + return Some(val.clone()); + } + None + }) + .ok_or(IncompleteInterp("Cell"))?; + let Value::Scalar(var) = *borrow else { + return Err(IncompleteInterp("Cell").into()); + }; + let borrow = Value::Scalar(var); + + let unsafe_cell = val.assume_field_as_struct("value")?; + let value = unsafe_cell + .members + .first() + .ok_or(IncompleteInterp("UnsafeCell"))?; + + Ok(Value::Struct(StructValue { + type_id: None, + type_ident: val.r#type().to_owned(), + members: vec![ + Member { + field_name: Some("borrow".to_string()), + value: borrow, + }, + value.clone(), + ], + type_params: Default::default(), + // set to `None` because the address operator unavailable for spec vars + raw_address: None, + })) + } + + pub fn parse_rc(&self, structure: &StructValue) -> Option { + weak_error!(self + .parse_rc_inner(Value::Struct(structure.clone())) + .context("Rc interpretation")) + .map(SpecializedValue::Rc) + } + + fn parse_rc_inner(&self, val: Value) -> Result { + Ok(val + .bfs_iterator() + .find_map(|(field_or_idx, child)| { + if let Value::Pointer(pointer) = child { + if field_or_idx == FieldOrIndex::Field(Some("pointer")) { + let new_pointer = pointer.clone(); + return Some(new_pointer); + } + } + None + }) + .ok_or(IncompleteInterp("rc"))?) + } + + pub fn parse_arc(&self, structure: &StructValue) -> Option { + weak_error!(self + .parse_arc_inner(Value::Struct(structure.clone())) + .context("Arc interpretation")) + .map(SpecializedValue::Arc) + } + + fn parse_arc_inner(&self, val: Value) -> Result { + Ok(val + .bfs_iterator() + .find_map(|(field_or_idx, child)| { + if let Value::Pointer(pointer) = child { + if field_or_idx == FieldOrIndex::Field(Some("pointer")) { + let new_pointer = pointer.clone(); + return Some(new_pointer); + } + } + None + }) + .ok_or(IncompleteInterp("Arc"))?) + } + + pub fn parse_uuid(&self, structure: &StructValue) -> Option { + weak_error!(self + .parse_uuid_inner(structure) + .context("Uuid interpretation")) + .map(SpecializedValue::Uuid) + } + + fn parse_uuid_inner(&self, structure: &StructValue) -> Result<[u8; 16], ParsingError> { + let member0 = structure.members.first().ok_or(FieldNotFound("member 0"))?; + let Value::Array(ref arr) = member0.value else { + return Err(UnexpectedType("uuid struct member must be an array").into()); + }; + let items = arr + .items + .as_ref() + .ok_or(AssumeError::NoData("uuid items"))?; + if items.len() != 16 { + return Err(AssumeError::UnexpectedType("uuid struct member must be [u8; 16]").into()); + } + + let mut bytes_repr = [0; 16]; + for (i, item) in items.iter().enumerate() { + let Value::Scalar(ScalarValue { + value: Some(SupportedScalar::U8(byte)), + .. + }) = item.value + else { + return Err(UnexpectedType("uuid struct member must be [u8; 16]").into()); + }; + bytes_repr[i] = byte; + } + + Ok(bytes_repr) + } + + fn parse_timespec(&self, timespec: &StructValue) -> Result<(i64, u32), ParsingError> { + let &[Member { + value: Value::Scalar(secs), + .. + }, Member { + value: Value::Struct(n_secs), + .. + }] = ×pec.members.as_slice() + else { + let err = "`Timespec` should contains secs and n_secs fields"; + return Err(UnexpectedType(err).into()); + }; + + let &[Member { + value: Value::Scalar(n_secs), + .. + }] = &n_secs.members.as_slice() + else { + let err = "`Nanoseconds` should contains u32 field"; + return Err(UnexpectedType(err).into()); + }; + + let secs = secs + .try_as_number() + .ok_or(UnexpectedType("`Timespec::tv_sec` not an int"))?; + let n_secs = n_secs + .try_as_number() + .ok_or(UnexpectedType("Timespec::tv_nsec` not an int"))? as u32; + + Ok((secs, n_secs)) + } + + pub fn parse_sys_time(&self, structure: &StructValue) -> Option { + weak_error!(self + .parse_sys_time_inner(structure) + .context("SystemTime interpretation")) + .map(SpecializedValue::SystemTime) + } + + fn parse_sys_time_inner(&self, structure: &StructValue) -> Result<(i64, u32), ParsingError> { + let &[Member { + value: Value::Struct(time_instant), + .. + }] = &structure.members.as_slice() + else { + let err = "`std::time::SystemTime` should contains a `time::SystemTime` field"; + return Err(UnexpectedType(err).into()); + }; + + let &[Member { + value: Value::Struct(timespec), + .. + }] = &time_instant.members.as_slice() + else { + let err = "`time::SystemTime` should contains a `Timespec` field"; + return Err(UnexpectedType(err).into()); + }; + + self.parse_timespec(timespec) + } + + pub fn parse_instant(&self, structure: &StructValue) -> Option { + weak_error!(self + .parse_instant_inner(structure) + .context("Instant interpretation")) + .map(SpecializedValue::Instant) + } + + fn parse_instant_inner(&self, structure: &StructValue) -> Result<(i64, u32), ParsingError> { + let &[Member { + value: Value::Struct(time_instant), + .. + }] = &structure.members.as_slice() + else { + let err = "`std::time::Instant` should contains a `time::Instant` field"; + return Err(UnexpectedType(err).into()); + }; + + let &[Member { + value: Value::Struct(timespec), + .. + }] = &time_instant.members.as_slice() + else { + let err = "`time::Instant` should contains a `Timespec` field"; + return Err(UnexpectedType(err).into()); + }; + + self.parse_timespec(timespec) + } +} + +fn extract_capacity(ctx: &ParseContext, val: &Value) -> Result { + let rust_version = ctx + .evaluation_context + .rustc_version() + .ok_or(ParsingError::UnsupportedVersion)?; + + version_switch!( + rust_version, + (1, 0, 0) ..= (1, 75, u32::MAX) => val.assume_field_as_scalar_number("cap")? as usize, + (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => { + let cap_s = val.assume_field_as_struct("cap")?; + let cap = &cap_s.members.first().ok_or(IncompleteInterp("Vec"))?.value; + if let Value::Scalar(ScalarValue {value: Some(SupportedScalar::Usize(cap)), ..}) = cap { + Ok(*cap) + } else { + Err(AssumeError::FieldNotANumber("cap")) + }? + }, + ) + .ok_or(ParsingError::UnsupportedVersion) +} diff --git a/src/debugger/variable/virtual.rs b/src/debugger/variable/virtual.rs new file mode 100644 index 0000000..22ea555 --- /dev/null +++ b/src/debugger/variable/virtual.rs @@ -0,0 +1,67 @@ +use crate::debugger::debugee::dwarf::unit::{DieRef, Node}; +use crate::debugger::debugee::dwarf::{AsAllocatedData, ContextualDieRef, EndianArcSlice}; +use crate::debugger::debugee::Debugee; +use crate::debugger::Error; +use crate::debugger::Error::TypeNotFound; +use gimli::{Attribute, DebugInfoOffset, UnitOffset}; + +/// This DIE does not actually exist in debug information. +/// It may be used to represent variables that are +/// declared by user, for example, using pointer cast operator. +#[derive(Clone, Copy)] +pub struct VirtualVariableDie { + type_ref: DieRef, +} + +impl VirtualVariableDie { + pub(super) const ANY_NODE: &'static Node = &Node::new_leaf(None); + + /// Create blank virtual variable DIE. + pub fn workpiece() -> Self { + Self { + type_ref: DieRef::Unit(UnitOffset(0)), + } + } + + /// Initialize virtual variable with a concrete type. + /// Return reference to virtual DIE. + pub fn init_with_type<'this, 'dbg>( + &'this mut self, + debugee: &'dbg Debugee, + type_name: &str, + ) -> Result, Error> { + let (debug_info, offset_of_unit, offset_of_die) = debugee + .debug_info_all() + .iter() + .find_map(|&debug_info| { + let (offset_of_unit, offset_of_die) = debug_info.find_type_die_ref(type_name)?; + Some((debug_info, offset_of_unit, offset_of_die)) + }) + .ok_or(TypeNotFound)?; + let unit = debug_info + .find_unit(DebugInfoOffset(offset_of_unit.0 + offset_of_die.0)) + .ok_or(TypeNotFound)?; + + self.type_ref = DieRef::Unit(offset_of_die); + Ok(ContextualDieRef { + debug_info, + unit_idx: unit.idx(), + node: VirtualVariableDie::ANY_NODE, + die: self, + }) + } +} + +impl AsAllocatedData for VirtualVariableDie { + fn name(&self) -> Option<&str> { + None + } + + fn type_ref(&self) -> Option { + Some(self.type_ref) + } + + fn location(&self) -> Option<&Attribute> { + None + } +} diff --git a/src/debugger/watchpoint.rs b/src/debugger/watchpoint.rs index d8737d2..277f5fd 100644 --- a/src/debugger/watchpoint.rs +++ b/src/debugger/watchpoint.rs @@ -1,5 +1,6 @@ use crate::debugger::address::{GlobalAddress, RelocatedAddress}; use crate::debugger::breakpoint::{Breakpoint, BreakpointRegistry}; +use crate::debugger::debugee::dwarf::r#type::TypeIdentity; use crate::debugger::debugee::tracee::TraceeCtl; use crate::debugger::debugee::tracer::WatchpointHitType; use crate::debugger::debugee::{Debugee, Location}; @@ -7,8 +8,9 @@ use crate::debugger::register::debug::{ BreakCondition, BreakSize, DebugRegisterNumber, HardwareDebugState, }; use crate::debugger::unwind::FrameID; -use crate::debugger::variable::select::{DqeResult, SelectExpressionEvaluator, DQE}; -use crate::debugger::variable::{ScalarVariable, SupportedScalar, VariableIR, VariableIdentity}; +use crate::debugger::variable::dqe::Dqe; +use crate::debugger::variable::execute::{DqeExecutor, QueryResult}; +use crate::debugger::variable::value::{ScalarValue, SupportedScalar, Value}; use crate::debugger::Error::Hook; use crate::debugger::{Debugger, Error, ExplorationContext, Tracee}; use crate::{debugger, disable_when_not_stared, weak_error}; @@ -23,9 +25,9 @@ struct ExpressionTarget { /// Original DQE string. source_string: String, /// Address DQE. - dqe: DQE, + dqe: Dqe, /// Last evaluated underlying DQE result. - last_value: Option, + last_value: Option, /// ID of in-focus frame at the time when watchpoint was created. /// Whether `None` when underlying expression has a global or undefined scope. frame_id: Option, @@ -37,8 +39,8 @@ struct ExpressionTarget { } impl ExpressionTarget { - fn underlying_dqe(&self) -> &DQE { - let DQE::Address(ref underlying_dqe) = self.dqe else { + fn underlying_dqe(&self) -> &Dqe { + let Dqe::Address(ref underlying_dqe) = self.dqe else { unreachable!("infallible: watchpoint always contains an address DQE"); }; underlying_dqe @@ -48,12 +50,12 @@ impl ExpressionTarget { #[derive(Debug)] struct AddressTarget { /// Last seen dereferenced value. - /// This is [`VariableIR::Scalar`] with one of u8, u16, u32 or u64 underlying value. - last_value: Option, + /// This is [`Value::Scalar`] with one of u8, u16, u32 or u64 underlying value. + last_value: Option, } impl AddressTarget { - fn refresh_last_value(&mut self, pid: Pid, hw: &HardwareBreakpoint) -> Option { + fn refresh_last_value(&mut self, pid: Pid, hw: &HardwareBreakpoint) -> Option { let read_size = match hw.size { BreakSize::Bytes1 => 1, BreakSize::Bytes2 => 2, @@ -93,10 +95,9 @@ impl AddressTarget { )), ), }; - VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some("data".to_string())), + Value::Scalar(ScalarValue { value: Some(u), - type_name: Some(t.to_string()), + type_ident: TypeIdentity::no_namespace(t), type_id: None, raw_address: None, }) @@ -222,16 +223,16 @@ where } impl Watchpoint { - fn evaluate_dqe(debugger: &Debugger, expr_source: &str, dqe: DQE) -> Result { - let expr_evaluator = SelectExpressionEvaluator::new(debugger, dqe); + fn execute_dqe(debugger: &Debugger, dqe: Dqe) -> Result { + let executor = DqeExecutor::new(debugger); // trying to evaluate at variables first, // if a result is empty, try to evaluate at function arguments - let mut evaluation_on_vars_results = expr_evaluator.evaluate()?; + let mut evaluation_on_vars_results = executor.query(&dqe)?; let mut evaluation_on_args_results; - let mut expr_result = match evaluation_on_vars_results.len() { + let expr_result = match evaluation_on_vars_results.len() { 0 => { - evaluation_on_args_results = expr_evaluator.evaluate_on_arguments()?; + evaluation_on_args_results = executor.query_arguments(&dqe)?; match evaluation_on_args_results.len() { 0 => return Err(Error::WatchSubjectNotFound), 1 => evaluation_on_args_results.pop().expect("infallible"), @@ -241,7 +242,6 @@ impl Watchpoint { 1 => evaluation_on_vars_results.pop().expect("infallible"), _ => return Err(Error::WatchpointCollision), }; - expr_result.variable.identity_mut().name = Some(expr_source.to_string()); Ok(expr_result) } @@ -256,14 +256,14 @@ impl Watchpoint { pub fn from_dqe( debugger: &mut Debugger, expr_source: &str, - dqe: DQE, + dqe: Dqe, condition: BreakCondition, ) -> Result<(HardwareDebugState, Self), Error> { // wrap expression with address operation - let dqe = DQE::Address(dqe.boxed()); + let dqe = Dqe::Address(dqe.boxed()); - let address_dqe_result = Self::evaluate_dqe(debugger, expr_source, dqe.clone())?; - let VariableIR::Pointer(ptr) = &address_dqe_result.variable else { + let address_dqe_result = Self::execute_dqe(debugger, dqe.clone())?; + let Value::Pointer(ptr) = address_dqe_result.value() else { unreachable!("infallible: address DQE always return a pointer") }; @@ -280,7 +280,7 @@ impl Watchpoint { let mut end_of_scope_brkpt = None; let mut frame_id = None; - if let Some(ref scope) = address_dqe_result.scope { + if let Some(scope) = address_dqe_result.scope() { // take a current frame id let expl_ctx = debugger.exploration_ctx(); let frame_num = expl_ctx.frame_num(); @@ -381,8 +381,8 @@ impl Watchpoint { companion: end_of_scope_brkpt, }; let underlying_dqe = target.underlying_dqe().clone(); - let var = Self::evaluate_dqe(debugger, expr_source, underlying_dqe) - .map(|ev| ev.variable) + let var = Self::execute_dqe(debugger, underlying_dqe) + .map(|ev| ev.into_value()) .ok(); target.last_value = var; @@ -442,7 +442,7 @@ impl Watchpoint { self.number } - fn last_value(&self) -> Option<&VariableIR> { + fn last_value(&self) -> Option<&Value> { match &self.subject { Subject::Expression(e) => e.last_value.as_ref(), Subject::Address(_) => None, @@ -634,9 +634,9 @@ impl WatchpointRegistry { &mut self, tracee_ctl: &TraceeCtl, breakpoints: &mut BreakpointRegistry, - dqe: DQE, + dqe: Dqe, ) -> Result, Error> { - let needle = DQE::Address(dqe.boxed()); + let needle = Dqe::Address(dqe.boxed()); let Some(to_remove) = self.watchpoints.iter().position(|wp| { if let Subject::Expression(ExpressionTarget { dqe: wp_dqe, .. }) = &wp.subject { &needle == wp_dqe @@ -725,7 +725,7 @@ impl Debugger { pub fn set_watchpoint_on_expr( &mut self, expr_source: &str, - dqe: DQE, + dqe: Dqe, condition: BreakCondition, ) -> Result { disable_when_not_stared!(self); @@ -785,7 +785,7 @@ impl Debugger { /// # Arguments /// /// * `dqe`: DQE - pub fn remove_watchpoint_by_expr(&mut self, dqe: DQE) -> Result, Error> { + pub fn remove_watchpoint_by_expr(&mut self, dqe: Dqe) -> Result, Error> { let breakpoints = &mut self.breakpoints; self.watchpoints .remove_by_dqe(self.debugee.tracee_ctl(), breakpoints, dqe) @@ -816,15 +816,16 @@ impl Debugger { match &wp.subject { Subject::Expression(target) => { let dqe = target.underlying_dqe().clone(); - let expr_str = target.source_string.clone(); let current_tid = self.exploration_ctx().pid_on_focus(); let new_value = match target.frame_id { - None => Watchpoint::evaluate_dqe(self, &expr_str, dqe), + None => { + Watchpoint::execute_dqe(self, dqe).map(|qr| qr.into_value()) + } // frame_id is actual if current tid and expression tid are equals, // otherwise evaluate as is Some(_) if target.tid != current_tid => { - Watchpoint::evaluate_dqe(self, &expr_str, dqe) + Watchpoint::execute_dqe(self, dqe).map(|qr| qr.into_value()) } Some(frame_id) => { let bt = self.backtrace(current_tid)?; @@ -841,12 +842,13 @@ impl Debugger { ); let ctx = ExplorationContext::new(loc, num as u32); call_with_context(self, ctx, |debugger| { - Watchpoint::evaluate_dqe(debugger, &expr_str, dqe) + Watchpoint::execute_dqe(debugger, dqe) + .map(|qr| qr.into_value()) }) } }; - let new_value = new_value.ok().map(|expr| expr.variable); + let new_value = new_value.ok(); let wp_mut = self .watchpoints @@ -871,6 +873,7 @@ impl Debugger { number, place, wp_mut.hw.condition, + Some(&t.source_string), old.as_ref(), t.last_value.as_ref(), false, @@ -900,6 +903,7 @@ impl Debugger { number, place, wp_mut.hw.condition, + None, old.as_ref(), t.last_value.as_ref(), false, @@ -921,12 +925,22 @@ impl Debugger { weak_error!(dwarf.find_place_from_pc(pc.into_global(&self.debugee)?)).flatten(); for wp in watchpoints { + let dqe_string = + if let Subject::Expression(ExpressionTarget { source_string, .. }) = + &wp.subject + { + Some(source_string.as_str()) + } else { + None + }; + self.hooks .on_watchpoint( pc, wp.number(), place.clone(), wp.hw.condition, + dqe_string, wp.last_value(), None, true, diff --git a/src/oracle/builtin/nop.rs b/src/oracle/builtin/nop.rs index 75bfb3c..5da1f7c 100644 --- a/src/oracle/builtin/nop.rs +++ b/src/oracle/builtin/nop.rs @@ -40,7 +40,7 @@ impl Oracle for NopOracle { true } - fn watch_points(self: Arc) -> Vec { + fn spy_points(self: Arc) -> Vec { vec![] } } diff --git a/src/oracle/builtin/tokio.rs b/src/oracle/builtin/tokio.rs index b3fe51d..12ec3cf 100644 --- a/src/oracle/builtin/tokio.rs +++ b/src/oracle/builtin/tokio.rs @@ -1,6 +1,8 @@ use crate::debugger::unwind::{Backtrace, FrameSpan}; -use crate::debugger::variable::select::{VariableSelector, DQE}; -use crate::debugger::variable::{ScalarVariable, StructVariable, SupportedScalar, VariableIR}; +use crate::debugger::variable::dqe::{Dqe, PointerCast, Selector}; +use crate::debugger::variable::value::{ + Member, PointerValue, ScalarValue, StructValue, SupportedScalar, Value, +}; use crate::debugger::CreateTransparentBreakpointRequest; use crate::debugger::{Debugger, Error}; use crate::oracle::{ConsolePlugin, Oracle, TuiPlugin}; @@ -183,7 +185,7 @@ impl Oracle for TokioOracle { true } - fn watch_points(self: Arc) -> Vec { + fn spy_points(self: Arc) -> Vec { let oracle = self.clone(); let poll_handler = move |dbg: &mut Debugger| { if let Err(e) = oracle.on_poll(dbg) { @@ -282,11 +284,11 @@ impl ConsolePlugin for TokioOracle { impl TokioOracle { /// Return underline value of loom `AtomicUsize` structure. - fn extract_value_from_atomic_usize(&self, val: &StructVariable) -> Option { - if let VariableIR::Struct(inner) = val.members.first()? { - if let VariableIR::Struct(value) = inner.members.first()? { - if let VariableIR::Struct(v) = value.members.first()? { - if let VariableIR::Scalar(value) = v.members.first()? { + fn extract_value_from_atomic_usize(&self, val: &StructValue) -> Option { + if let Value::Struct(ref inner) = val.members.first()?.value { + if let Value::Struct(ref value) = inner.members.first()?.value { + if let Value::Struct(ref v) = value.members.first()?.value { + if let Value::Scalar(ref value) = v.members.first()?.value { if let Some(SupportedScalar::Usize(usize)) = value.value { return Some(usize); } @@ -307,26 +309,34 @@ impl TokioOracle { .filter(|(_, task)| task.dropped_at.is_none()) .for_each(|(_, task)| { if let Some(ptr) = task.ptr { - let var = dbg.read_variable(DQE::Deref( - DQE::PtrCast( + let var = dbg.read_variable(Dqe::Deref( + Dqe::PtrCast(PointerCast::new( ptr as usize, - "*const tokio::runtime::task::core::Header".to_string(), - ) + "*const tokio::runtime::task::core::Header", + )) .boxed(), )); - if let Ok(Some(VariableIR::Struct(header_struct))) = - var.as_ref().map(|v| v.first()) + if let Ok(Some(Value::Struct(header_struct))) = + var.as_ref().map(|v| v.first().map(|v| v.value())) { for member in &header_struct.members { - if let VariableIR::Struct(state_member) = member { - if state_member.identity.name.as_deref() != Some("state") { + if let Member { + field_name, + value: Value::Struct(ref state_member), + } = member + { + if field_name.as_deref() != Some("state") { continue; } let val = state_member.members.first(); - if let Some(VariableIR::Struct(val)) = val { + if let Some(Member { + value: Value::Struct(val), + .. + }) = val + { if let Some(state) = self.extract_value_from_atomic_usize(val) { task.update_state(state) } @@ -340,51 +350,51 @@ impl TokioOracle { /// Read `self` function argument, interpret it as a task and return (task_id, task pointer) pair. fn get_header_from_self(dbg: &mut Debugger) -> Result, Error> { - let header_pointer_expr = DQE::Field( - DQE::Field( - DQE::Variable(VariableSelector::Name { - var_name: "self".to_string(), - only_local: true, - }) - .boxed(), + let header_pointer_expr = Dqe::Field( + Dqe::Field( + Dqe::Variable(Selector::by_name("self", true)).boxed(), "ptr".to_string(), ) .boxed(), "pointer".to_string(), ); - let header_args = dbg.read_argument(header_pointer_expr.clone())?; - let VariableIR::Pointer(header_pointer) = &header_args[0] else { + let mut header_args = dbg.read_argument(header_pointer_expr)?; + let Some(header) = header_args.pop() else { return Ok(None); }; - let id_offset_args = dbg.read_argument(DQE::Field( - DQE::Deref( - DQE::Field( - DQE::Deref(header_pointer_expr.boxed()).boxed(), - "vtable".to_string(), - ) - .boxed(), - ) - .boxed(), - "id_offset".to_string(), - ))?; + let Value::Pointer(PointerValue { + value: Some(header_ptr), + .. + }) = header.value() + else { + return Ok(None); + }; + let header_ptr = *header_ptr; + + let Some(id_offset) = header.modify_value(|ctx, value| { + let deref = value.deref(ctx)?; + let vtable = deref.field("vtable")?; + let deref = vtable.deref(ctx)?; + deref.field("id_offset") + }) else { + return Ok(None); + }; - let Some(VariableIR::Scalar(ScalarVariable { + let Value::Scalar(ScalarValue { value: Some(SupportedScalar::Usize(id_offset)), .. - })) = &id_offset_args.first() + }) = id_offset.value() else { return Ok(None); }; - if let Some(header_ptr) = header_pointer.value { - let id_addr = header_ptr as usize + *id_offset; + let id_addr = header_ptr as usize + *id_offset; - if let Ok(memory) = dbg.read_memory(id_addr, size_of::()) { - let task_id = u64::from_ne_bytes(memory.try_into().unwrap()); - return Ok(Some((task_id, header_ptr))); - } + if let Ok(memory) = dbg.read_memory(id_addr, size_of::()) { + let task_id = u64::from_ne_bytes(memory.try_into().unwrap()); + return Ok(Some((task_id, header_ptr))); } Ok(None) @@ -409,15 +419,12 @@ impl TokioOracle { } fn on_new(&self, debugger: &mut Debugger) -> Result<(), Error> { - let id_args = debugger.read_argument(DQE::Field( - Box::new(DQE::Variable(VariableSelector::Name { - var_name: "id".to_string(), - only_local: true, - })), - "0".to_string(), + let id_args = debugger.read_argument(Dqe::Field( + Box::new(Dqe::Variable(Selector::by_name("id", true))), + "__0".to_string(), ))?; - if let VariableIR::Scalar(scalar) = &id_args[0] { + if let Value::Scalar(scalar) = id_args[0].value() { if let Some(SupportedScalar::U64(id_value)) = scalar.value { let bt = debugger .backtrace(debugger.exploration_ctx().pid_on_focus()) diff --git a/src/oracle/mod.rs b/src/oracle/mod.rs index ff4b4f7..1ef88b4 100644 --- a/src/oracle/mod.rs +++ b/src/oracle/mod.rs @@ -39,8 +39,9 @@ pub trait Oracle: ConsolePlugin + TuiPlugin { /// Return oracle name. fn name(&self) -> &'static str; - /// True if oracle is ready for install on specific debugee. If false then debugger will - /// not use this oracle. Typically, in this method, oracle will check some symbols and + /// True if oracle is ready for install on specific debugee. + /// If false, then the debugger will not use this oracle. + /// Typically, in this method, oracle will check some symbols and /// make a decision about the possibility of further work. /// /// # Arguments @@ -48,7 +49,7 @@ pub trait Oracle: ConsolePlugin + TuiPlugin { /// * `dbg`: debugger instance fn ready_for_install(&self, dbg: &Debugger) -> bool; - /// A list of watch_point using by oracle. - /// In debugger watch point implemented by transparent breakpoints. - fn watch_points(self: Arc) -> Vec; + /// A list of spy-points using by oracle. + /// In debugger spy-point implemented by transparent breakpoints. + fn spy_points(self: Arc) -> Vec; } diff --git a/src/ui/command/arguments.rs b/src/ui/command/arguments.rs index 9858f50..c8f76c7 100644 --- a/src/ui/command/arguments.rs +++ b/src/ui/command/arguments.rs @@ -1,5 +1,5 @@ -use crate::debugger::variable::select::DQE; -use crate::debugger::variable::VariableIR; +use crate::debugger::variable::dqe::Dqe; +use crate::debugger::variable::execute::QueryResult; use crate::debugger::Debugger; use crate::ui::command; @@ -12,7 +12,7 @@ impl<'a> Handler<'a> { Self { dbg: debugger } } - pub fn handle(&self, select_expression: DQE) -> command::CommandResult> { + pub fn handle(&self, select_expression: Dqe) -> command::CommandResult> { Ok(self.dbg.read_argument(select_expression)?) } } diff --git a/src/ui/command/mod.rs b/src/ui/command/mod.rs index 60b2944..eff96c5 100644 --- a/src/ui/command/mod.rs +++ b/src/ui/command/mod.rs @@ -25,7 +25,7 @@ pub mod thread; pub mod variables; pub mod watch; -use crate::debugger::variable::select::DQE; +use crate::debugger::variable::dqe::Dqe; use crate::debugger::Error; #[derive(thiserror::Error, Debug)] @@ -43,8 +43,8 @@ pub type CommandResult = Result; /// External commands that can be processed by the debugger. #[derive(Debug, Clone)] pub enum Command { - PrintVariables(DQE), - PrintArguments(DQE), + PrintVariables(Dqe), + PrintArguments(Dqe), PrintBacktrace(backtrace::Command), Continue, Frame(frame::Command), diff --git a/src/ui/command/parser/expression.rs b/src/ui/command/parser/expression.rs index 890b9dc..dc94243 100644 --- a/src/ui/command/parser/expression.rs +++ b/src/ui/command/parser/expression.rs @@ -1,5 +1,5 @@ //! data query expressions parser. -use crate::debugger::variable::select::{Literal, LiteralOrWildcard, VariableSelector, DQE}; +use crate::debugger::variable::dqe::{Dqe, Literal, LiteralOrWildcard, PointerCast, Selector}; use crate::ui::command::parser::{hex, rust_identifier}; use chumsky::prelude::*; use chumsky::Parser; @@ -7,7 +7,7 @@ use std::collections::HashMap; type Err<'a> = extra::Err>; -fn ptr_cast<'a>() -> impl Parser<'a, &'a str, DQE, Err<'a>> + Clone { +fn ptr_cast<'a>() -> impl Parser<'a, &'a str, Dqe, Err<'a>> + Clone { let op = |c| just(c).padded(); // try to interp any string between brackets as a type @@ -33,7 +33,7 @@ fn ptr_cast<'a>() -> impl Parser<'a, &'a str, DQE, Err<'a>> + Clone { let type_p = any.delimited_by(op('('), op(')')); type_p .then(hex()) - .map(|(r#type, ptr)| DQE::PtrCast(ptr, r#type.trim().to_string())) + .map(|(r#type, ptr)| Dqe::PtrCast(PointerCast::new(ptr, r#type.trim()))) .labelled("pointer cast") } @@ -123,15 +123,10 @@ fn literal<'a>() -> impl Parser<'a, &'a str, Literal, Err<'a>> + Clone { literal } -pub fn parser<'a>() -> impl Parser<'a, &'a str, DQE, Err<'a>> { +pub fn parser<'a>() -> impl Parser<'a, &'a str, Dqe, Err<'a>> { let base_selector = rust_identifier() .padded() - .map(|name: &str| { - DQE::Variable(VariableSelector::Name { - var_name: name.to_string(), - only_local: false, - }) - }) + .map(|name: &str| Dqe::Variable(Selector::by_name(name, false))) .or(ptr_cast()); let expr = recursive(|expr| { @@ -147,8 +142,8 @@ pub fn parser<'a>() -> impl Parser<'a, &'a str, DQE, Err<'a>> { let field_op = op('.') .ignore_then(field) - .map(|field: &str| -> Box DQE> { - Box::new(move |r| DQE::Field(Box::new(r), field.to_string())) + .map(|field: &str| -> Box Dqe> { + Box::new(move |r| Dqe::Field(Box::new(r), field.to_string())) }) .boxed(); @@ -156,8 +151,8 @@ pub fn parser<'a>() -> impl Parser<'a, &'a str, DQE, Err<'a>> { .padded() .labelled("index value") .delimited_by(op('['), op(']')) - .map(|idx| -> Box DQE> { - Box::new(move |r: DQE| DQE::Index(Box::new(r), idx)) + .map(|idx| -> Box Dqe> { + Box::new(move |r: Dqe| Dqe::Index(Box::new(r), idx)) }) .boxed(); @@ -171,8 +166,8 @@ pub fn parser<'a>() -> impl Parser<'a, &'a str, DQE, Err<'a>> { .then(mb_usize) .labelled("slice range (start..end)") .delimited_by(op('['), op(']')) - .map(|(from, to)| -> Box DQE> { - Box::new(move |r: DQE| DQE::Slice(Box::new(r), from, to)) + .map(|(from, to)| -> Box Dqe> { + Box::new(move |r: Dqe| Dqe::Slice(Box::new(r), from, to)) }) .boxed(); @@ -182,9 +177,9 @@ pub fn parser<'a>() -> impl Parser<'a, &'a str, DQE, Err<'a>> { ); op('*') - .to(DQE::Deref as fn(_) -> _) - .or(op('&').to(DQE::Address as fn(_) -> _)) - .or(op('~').to(DQE::Canonic as fn(_) -> _)) + .to(Dqe::Deref as fn(_) -> _) + .or(op('&').to(Dqe::Address as fn(_) -> _)) + .or(op('~').to(Dqe::Canonic as fn(_) -> _)) .repeated() .foldr(expr, |op, rhs| op(Box::new(rhs))) }); @@ -200,23 +195,23 @@ mod test { fn test_ptr_cast_parser() { struct TestCase { string: &'static str, - result: Result, + result: Result, } let cases = vec![ TestCase { string: "(*SomeStruct) 0x12345", - result: Ok(DQE::PtrCast(0x12345, "*SomeStruct".to_string())), + result: Ok(Dqe::PtrCast(PointerCast::new(0x12345, "*SomeStruct"))), }, TestCase { string: " ( &u32 )0x12345", - result: Ok(DQE::PtrCast(0x12345, "&u32".to_string())), + result: Ok(Dqe::PtrCast(PointerCast::new(0x12345, "&u32"))), }, TestCase { string: "(*const abc::def::SomeType) 0x123AABCD", - result: Ok(DQE::PtrCast( + result: Ok(Dqe::PtrCast(PointerCast::new( 0x123AABCD, - "*const abc::def::SomeType".to_string(), - )), + "*const abc::def::SomeType", + ))), }, TestCase { string: " ( &u32 )12345", @@ -224,7 +219,7 @@ mod test { }, TestCase { string: "(*const i32)0x007FFFFFFFDC94", - result: Ok(DQE::PtrCast(0x7FFFFFFFDC94, "*const i32".to_string())), + result: Ok(Dqe::PtrCast(PointerCast::new(0x7FFFFFFFDC94, "*const i32"))), }, ]; @@ -384,213 +379,196 @@ mod test { fn test_expr_parsing() { struct TestCase { string: &'static str, - expr: DQE, + expr: Dqe, } let test_cases = vec![ TestCase { string: "var1", - expr: DQE::Variable(VariableSelector::Name { - var_name: "var1".to_string(), - only_local: false, - }), + expr: Dqe::Variable(Selector::by_name("var1", false)), }, TestCase { string: "*var1", - expr: DQE::Deref(Box::new(DQE::Variable(VariableSelector::Name { - var_name: "var1".to_string(), - only_local: false, - }))), + expr: Dqe::Deref(Dqe::Variable(Selector::by_name("var1", false)).boxed()), }, TestCase { string: "~var1", - expr: DQE::Canonic(Box::new(DQE::Variable(VariableSelector::Name { - var_name: "var1".to_string(), - only_local: false, - }))), + expr: Dqe::Canonic(Dqe::Variable(Selector::by_name("var1", false)).boxed()), }, TestCase { string: "**var1", - expr: DQE::Deref(Box::new(DQE::Deref(Box::new(DQE::Variable( - VariableSelector::Name { - var_name: "var1".to_string(), - only_local: false, - }, - ))))), + expr: Dqe::Deref( + Dqe::Deref(Dqe::Variable(Selector::by_name("var1", false)).boxed()).boxed(), + ), }, TestCase { string: "~*var1", - expr: DQE::Canonic( - DQE::Deref( - DQE::Variable(VariableSelector::Name { - var_name: "var1".to_string(), - only_local: false, - }) - .boxed(), - ) - .boxed(), + expr: Dqe::Canonic( + Dqe::Deref(Dqe::Variable(Selector::by_name("var1", false)).boxed()).boxed(), ), }, TestCase { string: "**var1.field1.field2", - expr: DQE::Deref(Box::new(DQE::Deref(Box::new(DQE::Field( - Box::new(DQE::Field( - Box::new(DQE::Variable(VariableSelector::Name { - var_name: "var1".to_string(), - only_local: false, - })), - "field1".to_string(), - )), - "field2".to_string(), - ))))), + expr: Dqe::Deref( + Dqe::Deref( + Dqe::Field( + Dqe::Field( + Dqe::Variable(Selector::by_name("var1", false)).boxed(), + "field1".to_string(), + ) + .boxed(), + "field2".to_string(), + ) + .boxed(), + ) + .boxed(), + ), }, TestCase { string: "**(var1.field1.field2)", - expr: DQE::Deref(Box::new(DQE::Deref(Box::new(DQE::Field( - Box::new(DQE::Field( - Box::new(DQE::Variable(VariableSelector::Name { - var_name: "var1".to_string(), - only_local: false, - })), - "field1".to_string(), - )), - "field2".to_string(), - ))))), + expr: Dqe::Deref( + Dqe::Deref( + Dqe::Field( + Dqe::Field( + Dqe::Variable(Selector::by_name("var1", false)).boxed(), + "field1".to_string(), + ) + .boxed(), + "field2".to_string(), + ) + .boxed(), + ) + .boxed(), + ), }, TestCase { string: "(**var1).field1.field2", - expr: DQE::Field( - Box::new(DQE::Field( - Box::new(DQE::Deref(Box::new(DQE::Deref(Box::new(DQE::Variable( - VariableSelector::Name { - var_name: "var1".to_string(), - only_local: false, - }, - )))))), + expr: Dqe::Field( + Dqe::Field( + Dqe::Deref( + Dqe::Deref(Dqe::Variable(Selector::by_name("var1", false)).boxed()) + .boxed(), + ) + .boxed(), "field1".to_string(), - )), + ) + .boxed(), "field2".to_string(), ), }, TestCase { string: "*(*(var1.field1)).field2[1][2]", - expr: DQE::Deref(Box::new(DQE::Index( - Box::new(DQE::Index( - Box::new(DQE::Field( - Box::new(DQE::Deref(Box::new(DQE::Field( - Box::new(DQE::Variable(VariableSelector::Name { - var_name: "var1".to_string(), - only_local: false, - })), - "field1".to_string(), - )))), - "field2".to_string(), - )), - Literal::Int(1), - )), - Literal::Int(2), - ))), + expr: Dqe::Deref( + Dqe::Index( + Dqe::Index( + Dqe::Field( + Dqe::Deref( + Dqe::Field( + Dqe::Variable(Selector::by_name("var1", false)).boxed(), + "field1".to_string(), + ) + .boxed(), + ) + .boxed(), + "field2".to_string(), + ) + .boxed(), + Literal::Int(1), + ) + .boxed(), + Literal::Int(2), + ) + .boxed(), + ), }, TestCase { string: "var1.field1[5..]", - expr: DQE::Slice( - Box::new(DQE::Field( - Box::new(DQE::Variable(VariableSelector::Name { - var_name: "var1".to_string(), - only_local: false, - })), + expr: Dqe::Slice( + Dqe::Field( + Dqe::Variable(Selector::by_name("var1", false)).boxed(), "field1".to_string(), - )), + ) + .boxed(), Some(5), None, ), }, TestCase { string: "var1.field1[..5]", - expr: DQE::Slice( - Box::new(DQE::Field( - Box::new(DQE::Variable(VariableSelector::Name { - var_name: "var1".to_string(), - only_local: false, - })), + expr: Dqe::Slice( + Dqe::Field( + Dqe::Variable(Selector::by_name("var1", false)).boxed(), "field1".to_string(), - )), + ) + .boxed(), None, Some(5), ), }, TestCase { string: "var1.field1[5..5]", - expr: DQE::Slice( - Box::new(DQE::Field( - Box::new(DQE::Variable(VariableSelector::Name { - var_name: "var1".to_string(), - only_local: false, - })), + expr: Dqe::Slice( + Dqe::Field( + Dqe::Variable(Selector::by_name("var1", false)).boxed(), "field1".to_string(), - )), + ) + .boxed(), Some(5), Some(5), ), }, TestCase { string: "var1.field1[..]", - expr: DQE::Slice( - Box::new(DQE::Field( - Box::new(DQE::Variable(VariableSelector::Name { - var_name: "var1".to_string(), - only_local: false, - })), + expr: Dqe::Slice( + Dqe::Field( + Dqe::Variable(Selector::by_name("var1", false)).boxed(), "field1".to_string(), - )), + ) + .boxed(), None, None, ), }, TestCase { string: "enum1.0.a", - expr: DQE::Field( - Box::new(DQE::Field( - Box::new(DQE::Variable(VariableSelector::Name { - var_name: "enum1".to_string(), - only_local: false, - })), + expr: Dqe::Field( + Dqe::Field( + Dqe::Variable(Selector::by_name("enum1", false)).boxed(), "0".to_string(), - )), + ) + .boxed(), "a".to_string(), ), }, TestCase { string: "(*mut SomeType)0x123AABCD", - expr: DQE::PtrCast(0x123AABCD, "*mut SomeType".to_string()), + expr: Dqe::PtrCast(PointerCast::new(0x123AABCD, "*mut SomeType")), }, TestCase { string: "(&abc::def::SomeType)0x123AABCD", - expr: DQE::PtrCast(0x123AABCD, "&abc::def::SomeType".to_string()), + expr: Dqe::PtrCast(PointerCast::new(0x123AABCD, "&abc::def::SomeType")), }, TestCase { string: "(*const abc::def::SomeType) 0x123AABCD", - expr: DQE::PtrCast(0x123AABCD, "*const abc::def::SomeType".to_string()), + expr: Dqe::PtrCast(PointerCast::new(0x123AABCD, "*const abc::def::SomeType")), }, TestCase { string: "*((*const abc::def::SomeType) 0x123AABCD)", - expr: DQE::Deref( - DQE::PtrCast(0x123AABCD, "*const abc::def::SomeType".to_string()).boxed(), + expr: Dqe::Deref( + Dqe::PtrCast(PointerCast::new(0x123AABCD, "*const abc::def::SomeType")).boxed(), ), }, TestCase { string: "*(*const i32)0x007FFFFFFFDC94", - expr: DQE::Deref(DQE::PtrCast(0x7FFFFFFFDC94, "*const i32".to_string()).boxed()), + expr: Dqe::Deref( + Dqe::PtrCast(PointerCast::new(0x7FFFFFFFDC94, "*const i32")).boxed(), + ), }, TestCase { string: "var.arr[0].some_val", - expr: DQE::Field( - DQE::Index( - DQE::Field( - DQE::Variable(VariableSelector::Name { - var_name: "var".to_string(), - only_local: false, - }) - .boxed(), + expr: Dqe::Field( + Dqe::Index( + Dqe::Field( + Dqe::Variable(Selector::by_name("var", false)).boxed(), "arr".to_string(), ) .boxed(), @@ -602,16 +580,12 @@ mod test { }, TestCase { string: "arr[0][..][1..][0].some_val", - expr: DQE::Field( - DQE::Index( - DQE::Slice( - DQE::Slice( - DQE::Index( - DQE::Variable(VariableSelector::Name { - var_name: "arr".to_string(), - only_local: false, - }) - .boxed(), + expr: Dqe::Field( + Dqe::Index( + Dqe::Slice( + Dqe::Slice( + Dqe::Index( + Dqe::Variable(Selector::by_name("arr", false)).boxed(), Literal::Int(0), ) .boxed(), @@ -631,16 +605,12 @@ mod test { }, TestCase { string: "map[\"key\"][-5][1.1][false][0x12]", - expr: DQE::Index( - DQE::Index( - DQE::Index( - DQE::Index( - DQE::Index( - DQE::Variable(VariableSelector::Name { - var_name: "map".to_string(), - only_local: false, - }) - .boxed(), + expr: Dqe::Index( + Dqe::Index( + Dqe::Index( + Dqe::Index( + Dqe::Index( + Dqe::Variable(Selector::by_name("map", false)).boxed(), Literal::String("key".to_string()), ) .boxed(), @@ -658,35 +628,21 @@ mod test { }, TestCase { string: "map[Some(true)]", - expr: DQE::Index( - DQE::Variable(VariableSelector::Name { - var_name: "map".to_string(), - only_local: false, - }) - .boxed(), + expr: Dqe::Index( + Dqe::Variable(Selector::by_name("map", false)).boxed(), Literal::EnumVariant("Some".to_string(), Some(Box::new(Literal::Bool(true)))), ), }, TestCase { string: "&a", - expr: DQE::Address( - DQE::Variable(VariableSelector::Name { - var_name: "a".to_string(), - only_local: false, - }) - .boxed(), - ), + expr: Dqe::Address(Dqe::Variable(Selector::by_name("a", false)).boxed()), }, TestCase { string: "&*a.b", - expr: DQE::Address( - DQE::Deref( - DQE::Field( - DQE::Variable(VariableSelector::Name { - var_name: "a".to_string(), - only_local: false, - }) - .boxed(), + expr: Dqe::Address( + Dqe::Deref( + Dqe::Field( + Dqe::Variable(Selector::by_name("a", false)).boxed(), "b".to_string(), ) .boxed(), @@ -696,8 +652,8 @@ mod test { }, TestCase { string: "&&(*i32)0x123", - expr: DQE::Address( - DQE::Address(DQE::PtrCast(0x123, "*i32".to_string()).boxed()).boxed(), + expr: Dqe::Address( + Dqe::Address(Dqe::PtrCast(PointerCast::new(0x123, "*i32")).boxed()).boxed(), ), }, ]; diff --git a/src/ui/command/parser/mod.rs b/src/ui/command/parser/mod.rs index 6feed08..9f387b3 100644 --- a/src/ui/command/parser/mod.rs +++ b/src/ui/command/parser/mod.rs @@ -4,7 +4,8 @@ use super::r#break::BreakpointIdentity; use super::{frame, memory, register, source_code, thread, watch, Command, CommandError}; use super::{r#break, CommandResult}; use crate::debugger::register::debug::BreakCondition; -use crate::debugger::variable::select::{VariableSelector, DQE}; +use crate::debugger::variable::dqe::Dqe; +use crate::debugger::variable::dqe::Selector; use crate::ui::command::watch::WatchpointIdentity; use ariadne::{Color, Fmt, Label, Report, ReportKind, Source}; use chumsky::error::{Rich, RichPattern, RichReason}; @@ -246,7 +247,7 @@ impl Command { let print_local_vars = op_w_arg(VAR_COMMAND) .then(sub_op(VAR_LOCAL_KEY)) - .map(|_| Command::PrintVariables(DQE::Variable(VariableSelector::Any))); + .map(|_| Command::PrintVariables(Dqe::Variable(Selector::Any))); let print_var = op_w_arg(VAR_COMMAND) .ignore_then(expression::parser()) .map(Command::PrintVariables); @@ -255,7 +256,7 @@ impl Command { let print_all_args = op_w_arg(ARG_COMMAND) .then(sub_op(ARG_ALL_KEY)) - .map(|_| Command::PrintArguments(DQE::Variable(VariableSelector::Any))); + .map(|_| Command::PrintArguments(Dqe::Variable(Selector::Any))); let print_arg = op_w_arg(ARG_COMMAND) .ignore_then(expression::parser()) .map(Command::PrintArguments); @@ -542,7 +543,7 @@ fn test_parser() { command_matcher: |result| { assert!(matches!( result.unwrap(), - Command::PrintVariables(DQE::Variable(VariableSelector::Any)) + Command::PrintVariables(Dqe::Variable(Selector::Any)) )); }, }, @@ -551,7 +552,7 @@ fn test_parser() { command_matcher: |result| { assert!(matches!( result.unwrap(), - Command::PrintVariables(DQE::Deref(_)) + Command::PrintVariables(Dqe::Deref(_)) )); }, }, @@ -560,7 +561,7 @@ fn test_parser() { command_matcher: |result| { assert!(matches!( result.unwrap(), - Command::PrintVariables(DQE::Variable(VariableSelector::Name { var_name, .. })) if var_name == "locals_var" + Command::PrintVariables(Dqe::Variable(Selector::Name { var_name, .. })) if var_name == "locals_var" )); }, }, @@ -589,7 +590,7 @@ fn test_parser() { command_matcher: |result| { assert!(matches!( result.unwrap(), - Command::PrintArguments(DQE::Variable(VariableSelector::Any)) + Command::PrintArguments(Dqe::Variable(Selector::Any)) )); }, }, @@ -598,7 +599,7 @@ fn test_parser() { command_matcher: |result| { assert!(matches!( result.unwrap(), - Command::PrintArguments(DQE::Variable(VariableSelector::Name { var_name, .. })) if var_name == "all_arg" + Command::PrintArguments(Dqe::Variable(Selector::Name { var_name, .. })) if var_name == "all_arg" )); }, }, @@ -790,7 +791,7 @@ fn test_parser() { assert!(matches!( result.unwrap(), Command::Watchpoint(watch::Command::Add( - WatchpointIdentity::DQE(source, DQE::Variable(VariableSelector::Name {var_name, ..})), BreakCondition::DataWrites + WatchpointIdentity::DQE(source, Dqe::Variable(Selector::Name {var_name, ..})), BreakCondition::DataWrites )) if var_name == "var1" && source == "var1" )); }, @@ -801,7 +802,7 @@ fn test_parser() { assert!(matches!( result.unwrap(), Command::Watchpoint(watch::Command::Add( - WatchpointIdentity::DQE(source, DQE::Variable(VariableSelector::Name {var_name, ..})), BreakCondition::DataReadsWrites + WatchpointIdentity::DQE(source, Dqe::Variable(Selector::Name {var_name, ..})), BreakCondition::DataReadsWrites )) if var_name == "var1" && source == "var1" )); }, @@ -816,7 +817,7 @@ fn test_parser() { assert!(matches!( result.unwrap(), Command::Watchpoint(watch::Command::Add( - WatchpointIdentity::DQE(source, DQE::Variable(VariableSelector::Name {var_name, ..})), BreakCondition::DataWrites + WatchpointIdentity::DQE(source, Dqe::Variable(Selector::Name {var_name, ..})), BreakCondition::DataWrites )) if var_name == "ns1::ns2::var1" && source == "ns1::ns2::var1" )); }, @@ -844,7 +845,7 @@ fn test_parser() { command_matcher: |result| { assert!(matches!( result.unwrap(), - Command::Watchpoint(watch::Command::Remove(WatchpointIdentity::DQE(source, DQE::Variable(VariableSelector::Name {var_name, ..})))) if var_name == "var1" && source == "var1" + Command::Watchpoint(watch::Command::Remove(WatchpointIdentity::DQE(source, Dqe::Variable(Selector::Name {var_name, ..})))) if var_name == "var1" && source == "var1" )); }, }, diff --git a/src/ui/command/variables.rs b/src/ui/command/variables.rs index c6ee968..8134c12 100644 --- a/src/ui/command/variables.rs +++ b/src/ui/command/variables.rs @@ -1,5 +1,5 @@ -use crate::debugger::variable::select::DQE; -use crate::debugger::variable::VariableIR; +use crate::debugger::variable::dqe::Dqe; +use crate::debugger::variable::execute::QueryResult; use crate::debugger::Debugger; use crate::ui::command; @@ -12,7 +12,7 @@ impl<'a> Handler<'a> { Self { dbg: debugger } } - pub fn handle(self, select_expression: DQE) -> command::CommandResult> { + pub fn handle(self, select_expression: Dqe) -> command::CommandResult>> { Ok(self.dbg.read_variable(select_expression)?) } } diff --git a/src/ui/command/watch.rs b/src/ui/command/watch.rs index fee50b5..3228221 100644 --- a/src/ui/command/watch.rs +++ b/src/ui/command/watch.rs @@ -1,13 +1,13 @@ use crate::debugger::address::RelocatedAddress; use crate::debugger::register::debug::{BreakCondition, BreakSize}; -use crate::debugger::variable::select::DQE; +use crate::debugger::variable::dqe::Dqe; use crate::debugger::Debugger; use crate::debugger::Error; use crate::debugger::WatchpointView; #[derive(Debug, Clone)] pub enum WatchpointIdentity { - DQE(String, DQE), + DQE(String, Dqe), Address(usize, u8), Number(u32), } diff --git a/src/ui/console/editor.rs b/src/ui/console/editor.rs index 335979e..964f8e1 100644 --- a/src/ui/console/editor.rs +++ b/src/ui/console/editor.rs @@ -32,6 +32,10 @@ use std::path::PathBuf; use std::sync::{Arc, Mutex}; use trie_rs::{Trie, TrieBuilder}; +pub const TUI_COMMAND: &str = "tui"; +pub const BINSIDER_COMMAND: &str = "binsider"; +pub const BINSIDER_COMMAND_SHORT: &str = "bn"; + struct CommandHint { short: Option, long: String, @@ -418,6 +422,12 @@ pub fn create_editor( long: ORACLE_COMMAND.to_string(), subcommands: oracles.iter().map(ToString::to_string).collect(), }, + TUI_COMMAND.into(), + CommandHint { + short: Some(BINSIDER_COMMAND_SHORT.to_string()), + long: BINSIDER_COMMAND.to_string(), + subcommands: vec![], + }, ("q", "quit").into(), ]; diff --git a/src/ui/console/help.rs b/src/ui/console/help.rs index 52317fd..d9acc5c 100644 --- a/src/ui/console/help.rs +++ b/src/ui/console/help.rs @@ -26,6 +26,7 @@ source asm|fn| -- show source code or assembly inst oracle <>| -- execute a specific oracle h, help <>| -- show help tui -- change ui mode to tui +binsider -- run binsider (https://binsider.dev) q, quit -- exit the BugStalker "#; @@ -280,6 +281,12 @@ pub const HELP_TUI: &str = "\ Change ui mode to terminal ui. "; +#[cfg(feature = "binsider-integration")] +pub const HELP_BINSIDER: &str = "\ +\x1b[32;1mbn, binsider\x1b[0m +Run binsider (powerful static and dynamic analysis tools, see https://binsider.dev) inside bugstalker. +"; + pub const HELP_ORACLE: &str = "\ \x1b[32;1moracle\x1b[0m Execute a specific oracle. @@ -333,7 +340,15 @@ impl Helper { oracles.for_each(|oracle| help = format!("{help}{}\n", oracle.help())); help }), - Some("tui") => HELP_TUI, + Some(super::editor::TUI_COMMAND) => HELP_TUI, + #[cfg(feature = "binsider-integration")] + Some(super::editor::BINSIDER_COMMAND) | Some(super::editor::BINSIDER_COMMAND_SHORT) => { + HELP_BINSIDER + } + #[cfg(not(feature = "binsider-integration"))] + Some(super::editor::BINSIDER_COMMAND) | Some(super::editor::BINSIDER_COMMAND_SHORT) => { + "disabled, compile with `binsider-integration` feature" + } Some("q") | Some("quit") => HELP_QUIT, _ => "unknown command", } diff --git a/src/ui/console/hook.rs b/src/ui/console/hook.rs index 028c5ad..207a0ff 100644 --- a/src/ui/console/hook.rs +++ b/src/ui/console/hook.rs @@ -1,12 +1,12 @@ use crate::debugger::address::RelocatedAddress; use crate::debugger::register::debug::BreakCondition; -use crate::debugger::variable::VariableIR; +use crate::debugger::variable::value::Value; use crate::debugger::PlaceDescriptor; use crate::debugger::{EventHook, FunctionDie}; use crate::ui::console::file::FileView; use crate::ui::console::print::style::{AddressView, FilePathView, FunctionNameView, KeywordView}; use crate::ui::console::print::ExternalPrinter; -use crate::ui::console::variable::render_variable; +use crate::ui::console::variable::render_value; use crate::version; use log::warn; use nix::sys::signal::Signal; @@ -73,18 +73,22 @@ impl EventHook for TerminalHook { num: u32, mb_place: Option, cond: BreakCondition, - old: Option<&VariableIR>, - new: Option<&VariableIR>, + dqe_string: Option<&str>, + old: Option<&Value>, + new: Option<&Value>, end_of_scope: bool, ) -> anyhow::Result<()> { + let source_dqe = dqe_string + .map(|dqe| format!(" (expr: {dqe})")) + .unwrap_or_default(); let msg = if end_of_scope { format!( - "Watchpoint {num} end of scope (and it will be removed)\n{}:", + "Watchpoint {num}{source_dqe} end of scope (and it will be removed)\n{}:", AddressView::from(pc) ) } else { format!( - "Hit watchpoint {num} ({cond}) at {}:", + "Hit watchpoint {num}{source_dqe} ({cond}) at {}:", AddressView::from(pc) ) }; @@ -101,16 +105,16 @@ impl EventHook for TerminalHook { if cond == BreakCondition::DataReadsWrites && old == new { if let Some(old) = old { - let val = render_variable(old)?; + let val = render_value(old); self.printer.println(format!("value: {val}")); } } else { if let Some(old) = old { - let old = render_variable(old)?; + let old = render_value(old); self.printer.println(format!("old value: {old}")); } if let Some(new) = new { - let new = render_variable(new)?; + let new = render_value(new); self.printer.println(format!("new value: {new}")); } } diff --git a/src/ui/console/mod.rs b/src/ui/console/mod.rs index bbcf1d8..4cac306 100644 --- a/src/ui/console/mod.rs +++ b/src/ui/console/mod.rs @@ -1,6 +1,6 @@ use crate::debugger; use crate::debugger::process::{Child, Installed}; -use crate::debugger::variable::select::{VariableSelector, DQE}; +use crate::debugger::variable::dqe::{Dqe, Selector}; use crate::debugger::{Debugger, DebuggerBuilder}; use crate::ui::command::arguments::Handler as ArgumentsHandler; use crate::ui::command::backtrace::Handler as BacktraceHandler; @@ -23,7 +23,7 @@ use crate::ui::command::{ r#break, source_code, step_instruction, step_into, step_out, step_over, CommandError, }; use crate::ui::command::{run, Command}; -use crate::ui::console::editor::{create_editor, CommandCompleter, RLHelper}; +use crate::ui::console::editor::{create_editor, CommandCompleter, RLHelper, TUI_COMMAND}; use crate::ui::console::file::FileView; use crate::ui::console::help::*; use crate::ui::console::hook::TerminalHook; @@ -151,13 +151,19 @@ impl AppBuilder { } } +enum TuiVariant { + Tui, + #[cfg(feature = "binsider-integration")] + Binsider, +} + enum UserAction { /// New command from user received Cmd(String), /// Terminate application Terminate, /// Switch to TUI mode - ChangeMode, + ChangeMode(TuiVariant), /// Do nothing Nop, } @@ -281,18 +287,26 @@ impl TerminalApplication { let line = editor.readline(promt); match line { - Ok(input) => { - if input == "q" || input == "quit" { + Ok(input) => match input.as_str() { + "q" | "quit" => { _ = control_tx.send(UserAction::Terminate); - break; - } else if input == "tui" { - _ = control_tx.send(UserAction::ChangeMode); - break; - } else { + return; + } + TUI_COMMAND => { + _ = control_tx.send(UserAction::ChangeMode(TuiVariant::Tui)); + return; + } + #[cfg(feature = "binsider-integration")] + crate::ui::console::editor::BINSIDER_COMMAND + | crate::ui::console::editor::BINSIDER_COMMAND_SHORT => { + _ = control_tx.send(UserAction::ChangeMode(TuiVariant::Binsider)); + return; + } + _ => { _ = editor.add_history_entry(&input); _ = control_tx.send(UserAction::Cmd(input)); } - } + }, Err(err) => match err { ReadlineError::Interrupted => { // this branch chosen if SIGINT coming @@ -354,7 +368,7 @@ impl AppLoop { "n" | "no" => false, _ => continue, }, - UserAction::Terminate | UserAction::ChangeMode | UserAction::Nop => false, + UserAction::Terminate | UserAction::ChangeMode(_) | UserAction::Nop => false, }; } } @@ -362,10 +376,10 @@ impl AppLoop { fn update_completer_variables(&self) -> anyhow::Result<()> { let vars = self .debugger - .read_variable_names(DQE::Variable(VariableSelector::Any))?; + .read_variable_names(Dqe::Variable(Selector::Any))?; let args = self .debugger - .read_argument_names(DQE::Variable(VariableSelector::Any))?; + .read_argument_names(Dqe::Variable(Selector::Any))?; let mut completer = self.completer.lock().unwrap(); completer.replace_local_var_hints(vars); @@ -763,7 +777,7 @@ impl AppLoop { UserAction::Terminate => { return Ok(supervisor::ControlFlow::Exit); } - UserAction::ChangeMode => { + UserAction::ChangeMode(TuiVariant::Tui) => { self.cancel_output_flag.store(true, Ordering::SeqCst); let tui_builder = crate::ui::tui::AppBuilder::new(self.debugee_out, self.debugee_err); @@ -772,6 +786,18 @@ impl AppLoop { supervisor::Application::TUI(app), )); } + #[cfg(feature = "binsider-integration")] + UserAction::ChangeMode(TuiVariant::Binsider) => { + self.cancel_output_flag.store(true, Ordering::SeqCst); + let binsider_builder = crate::ui::third_party::binsider::AppBuilder::new( + self.debugee_out, + self.debugee_err, + ); + let app = binsider_builder.extend(self.debugger); + return Ok(supervisor::ControlFlow::Switch( + supervisor::Application::Binsider(app), + )); + } } } } diff --git a/src/ui/console/variable.rs b/src/ui/console/variable.rs index 01446ff..ed5696e 100644 --- a/src/ui/console/variable.rs +++ b/src/ui/console/variable.rs @@ -1,16 +1,23 @@ use crate::debugger::address::RelocatedAddress; -use crate::debugger::variable::render::{RenderRepr, ValueLayout}; -use crate::debugger::variable::VariableIR; +use crate::debugger::variable::execute::{QueryResult, QueryResultKind}; +use crate::debugger::variable::render::{RenderValue, ValueLayout}; +use crate::debugger::variable::value::Value; use crate::ui::syntax; use crate::ui::syntax::StylizedLine; use syntect::util::as_24_bit_terminal_escaped; const TAB: &str = "\t"; -pub fn render_variable(var: &VariableIR) -> anyhow::Result { +pub fn render_variable(var: &QueryResult) -> anyhow::Result { let syntax_renderer = syntax::rust_syntax_renderer(); let mut line_renderer = syntax_renderer.line_renderer(); - let var_as_string = format!("{} = {}", var.name(), render_variable_ir(var, 0)); + let prefix = if var.kind() == QueryResultKind::Root && var.identity().name.is_some() { + format!("{} = ", var.identity()) + } else { + String::default() + }; + + let var_as_string = format!("{prefix}{}", render_value(var.value())); Ok(var_as_string .lines() .map(|l| -> anyhow::Result { @@ -27,41 +34,49 @@ pub fn render_variable(var: &VariableIR) -> anyhow::Result { .join("\n")) } -pub fn render_variable_ir(view: &VariableIR, depth: usize) -> String { - match view.value() { - Some(value) => match value { - ValueLayout::PreRendered(rendered_value) => match view { - VariableIR::CEnum(_) => format!("{}::{}", view.r#type(), rendered_value), - _ => format!("{}({})", view.r#type(), rendered_value), +pub fn render_value(value: &Value) -> String { + render_value_inner(value, 0) +} + +fn render_value_inner(value: &Value, depth: usize) -> String { + match value.value_layout() { + Some(layout) => match layout { + ValueLayout::PreRendered(rendered_value) => match value { + Value::CEnum(_) => format!("{}::{}", value.r#type().name_fmt(), rendered_value), + _ => format!("{}({})", value.r#type().name_fmt(), rendered_value), }, - ValueLayout::Referential { addr } => { + ValueLayout::Referential(addr) => { format!( "{} [{}]", - view.r#type(), + value.r#type().name_fmt(), RelocatedAddress::from(addr as usize) ) } ValueLayout::Wrapped(val) => { - format!("{}::{}", view.r#type(), render_variable_ir(val, depth)) + format!( + "{}::{}", + value.r#type().name_fmt(), + render_value_inner(val, depth) + ) } - ValueLayout::Structure { members } => { - let mut render = format!("{} {{", view.r#type()); + ValueLayout::Structure(members) => { + let mut render = format!("{} {{", value.r#type().name_fmt()); let tabs = TAB.repeat(depth + 1); - for v in members { + for member in members { render = format!("{render}\n"); render = format!( "{render}{tabs}{}: {}", - v.name(), - render_variable_ir(v, depth + 1) + member.field_name.as_deref().unwrap_or_default(), + render_value_inner(&member.value, depth + 1) ); } format!("{render}\n{}}}", TAB.repeat(depth)) } ValueLayout::Map(kv_children) => { - let mut render = format!("{} {{", view.r#type()); + let mut render = format!("{} {{", value.r#type().name_fmt()); let tabs = TAB.repeat(depth + 1); @@ -69,34 +84,42 @@ pub fn render_variable_ir(view: &VariableIR, depth: usize) -> String { render = format!("{render}\n"); render = format!( "{render}{tabs}{}: {}", - render_variable_ir(&kv.0, depth + 1), - render_variable_ir(&kv.1, depth + 1) + render_value_inner(&kv.0, depth + 1), + render_value_inner(&kv.1, depth + 1) + ); + } + + format!("{render}\n{}}}", TAB.repeat(depth)) + } + ValueLayout::IndexedList(items) => { + let mut render = format!("{} {{", value.r#type().name_fmt()); + + let tabs = TAB.repeat(depth + 1); + + for item in items { + render = format!("{render}\n"); + render = format!( + "{render}{tabs}{}: {}", + item.index, + render_value_inner(&item.value, depth + 1) ); } format!("{render}\n{}}}", TAB.repeat(depth)) } - ValueLayout::List { members, indexed } => { - let mut render = format!("{} {{", view.r#type()); + ValueLayout::NonIndexedList(values) => { + let mut render = format!("{} {{", value.r#type().name_fmt()); let tabs = TAB.repeat(depth + 1); - for v in members { + for val in values { render = format!("{render}\n"); - if indexed { - render = format!( - "{render}{tabs}{}: {}", - v.name(), - render_variable_ir(v, depth + 1) - ); - } else { - render = format!("{render}{tabs}{}", render_variable_ir(v, depth + 1)); - } + render = format!("{render}{tabs}{}", render_value_inner(val, depth + 1)); } format!("{render}\n{}}}", TAB.repeat(depth)) } }, - None => format!("{}(unknown)", view.r#type()), + None => format!("{}(unknown)", value.r#type().name_fmt()), } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 355dfc9..38991f3 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -4,6 +4,7 @@ pub mod console; pub mod short; pub mod supervisor; mod syntax; +mod third_party; pub mod tui; use os_pipe::PipeReader; diff --git a/src/ui/supervisor.rs b/src/ui/supervisor.rs index 4247ffe..b5971a5 100644 --- a/src/ui/supervisor.rs +++ b/src/ui/supervisor.rs @@ -28,6 +28,8 @@ pub enum DebugeeSource<'a> { pub enum Application { TUI(TuiApplication), Terminal(TerminalApplication), + #[cfg(feature = "binsider-integration")] + Binsider(crate::ui::third_party::binsider::BinsiderApplication), } impl Application { @@ -35,6 +37,8 @@ impl Application { match self { Application::TUI(tui_app) => tui_app.run(), Application::Terminal(term_app) => term_app.run(), + #[cfg(feature = "binsider-integration")] + Application::Binsider(bs) => bs.run(), } } } diff --git a/src/ui/third_party/binsider.rs b/src/ui/third_party/binsider.rs new file mode 100644 index 0000000..42265c1 --- /dev/null +++ b/src/ui/third_party/binsider.rs @@ -0,0 +1,107 @@ +use crate::debugger::{Debugger, NopHook}; +use crate::ui::{console, supervisor, DebugeeOutReader}; +use binsider::prelude::Event; +use binsider::prelude::*; +use ratatui::{crossterm::event::KeyCode, Frame}; +use std::fs; +use std::path::PathBuf; + +pub struct AppBuilder { + debugee_out: DebugeeOutReader, + debugee_err: DebugeeOutReader, +} + +impl AppBuilder { + pub fn new(debugee_out: DebugeeOutReader, debugee_err: DebugeeOutReader) -> Self { + Self { + debugee_out, + debugee_err, + } + } + + pub fn extend(self, mut debugger: Debugger) -> BinsiderApplication { + debugger.set_hook(NopHook {}); + BinsiderApplication::new(debugger, self.debugee_out, self.debugee_err) + } +} + +/// This is a wrapper over `binsider` application (see https://github.com/orhun/binsider) for running +/// it inside a `BugStalker`. +pub struct BinsiderApplication { + debugger: Debugger, + debugee_out: DebugeeOutReader, + debugee_err: DebugeeOutReader, +} + +impl BinsiderApplication { + fn new( + debugger: Debugger, + debugee_out: DebugeeOutReader, + debugee_err: DebugeeOutReader, + ) -> Self { + Self { + debugger, + debugee_out, + debugee_err, + } + } + + pub fn run(self) -> anyhow::Result { + let path = PathBuf::from(self.debugger.process().program()); + let file_data = fs::read(self.debugger.process().program())?; + let file_info = FileInfo::new( + path.to_str().unwrap_or_default(), + None, + file_data.as_slice(), + )?; + let analyzer = Analyzer::new(file_info, 15, vec![])?; + let mut state = State::new(analyzer)?; + let events = EventHandler::new(250); + state.analyzer.extract_strings(events.sender.clone()); + + let mut terminal = ratatui::init(); + loop { + // Render the UI. + terminal.draw(|frame: &mut Frame| { + render(&mut state, frame); + })?; + + let event = events.next()?; + match event { + Event::Key(key_event) => { + if key_event.code == KeyCode::Char('q') { + break; + } + binsider::handle_event(Event::Key(key_event), &events, &mut state)?; + } + Event::Restart(None) => { + break; + } + Event::Restart(Some(path)) => { + let Some(path) = path.to_str() else { break }; + let file_data = std::fs::read(path)?; + let bytes = file_data.as_slice(); + let file_info = FileInfo::new(path, Some(vec![]), bytes)?; + let analyzer = Analyzer::new(file_info, 15, vec![])?; + + state.change_analyzer(analyzer); + state.handle_tab()?; + state.analyzer.extract_strings(events.sender.clone()); + } + _ => { + binsider::handle_event(event, &events, &mut state)?; + } + } + } + events.stop(); + ratatui::restore(); + + let builder = console::AppBuilder::new(self.debugee_out, self.debugee_err); + let app = builder + .extend(self.debugger) + .expect("build application fail"); + Ok(supervisor::ControlFlow::Switch( + supervisor::Application::Terminal(app), + )) + } +} diff --git a/src/ui/third_party/mod.rs b/src/ui/third_party/mod.rs new file mode 100644 index 0000000..4e1902c --- /dev/null +++ b/src/ui/third_party/mod.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "binsider-integration")] +pub mod binsider; diff --git a/src/ui/tui/app/port.rs b/src/ui/tui/app/port.rs index 20b4bc7..2c10425 100644 --- a/src/ui/tui/app/port.rs +++ b/src/ui/tui/app/port.rs @@ -1,6 +1,6 @@ use crate::debugger::address::RelocatedAddress; use crate::debugger::register::debug::BreakCondition; -use crate::debugger::variable::VariableIR; +use crate::debugger::variable::value::Value; use crate::debugger::{EventHook, FunctionDie, PlaceDescriptor}; use crate::ui::tui::output::OutputLine; use crate::ui::tui::proto::ClientExchanger; @@ -14,7 +14,7 @@ use std::sync::{Arc, Mutex}; use tuirealm::listener::{ListenerResult, Poll}; use tuirealm::Event; -impl PartialOrd for VariableIR { +impl PartialOrd for Value { fn partial_cmp(&self, _: &Self) -> Option { None } @@ -36,8 +36,8 @@ pub enum UserEvent { file: Option, line: Option, cond: BreakCondition, - old_value: Option, - new_value: Option, + old_value: Option, + new_value: Option, end_of_scope: bool, }, Step { @@ -148,8 +148,9 @@ impl EventHook for TuiHook { num: u32, place: Option, cond: BreakCondition, - old: Option<&VariableIR>, - new: Option<&VariableIR>, + _: Option<&str>, + old: Option<&Value>, + new: Option<&Value>, end_of_scope: bool, ) -> anyhow::Result<()> { self.event_queue diff --git a/src/ui/tui/components/variables.rs b/src/ui/tui/components/variables.rs index ec70ad4..b1d0bf8 100644 --- a/src/ui/tui/components/variables.rs +++ b/src/ui/tui/components/variables.rs @@ -1,7 +1,7 @@ use crate::debugger::register::debug::BreakCondition; -use crate::debugger::variable::render::{RenderRepr, ValueLayout}; -use crate::debugger::variable::select::{Literal, VariableSelector, DQE}; -use crate::debugger::variable::{select, VariableIR}; +use crate::debugger::variable::dqe::{Dqe, Selector}; +use crate::debugger::variable::execute::{QueryResult, QueryResultKind}; +use crate::debugger::variable::render::{RenderValue, ValueLayout}; use crate::ui; use crate::ui::syntax::StylizedLine; use crate::ui::tui::app::port::UserEvent; @@ -29,16 +29,24 @@ pub struct Variables { exchanger: Arc, } -fn render_var_inner(name: &str, typ: &str, value: Option<&str>) -> anyhow::Result> { +fn render_var_inner( + name: Option<&str>, + typ: &str, + value: Option<&str>, +) -> anyhow::Result> { let syntax_renderer = syntax::rust_syntax_renderer(); let mut line_renderer = syntax_renderer.line_renderer(); - let line = match value { - None => { + let line = match (value, name) { + (None, None) => typ.to_string(), + (Some(val), None) => { + format!("{typ}({val})") + } + (None, Some(name)) => { format!("{name} {typ}") } - Some(value) => { - format!("{name} {typ}({value})") + (Some(val), Some(name)) => { + format!("{name} {typ}({val})") } }; @@ -54,196 +62,231 @@ fn render_var_inner(name: &str, typ: &str, value: Option<&str>) -> anyhow::Resul Ok(line_spans) } -fn render_var(name: &str, typ: &str, value: &str) -> anyhow::Result> { +fn render_var(name: Option<&str>, typ: &str, value: &str) -> anyhow::Result> { render_var_inner(name, typ, Some(value)) } -fn render_var_def(name: &str, typ: &str) -> anyhow::Result> { +fn render_var_def(name: Option<&str>, typ: &str) -> anyhow::Result> { render_var_inner(name, typ, None) } -impl Variables { - fn node_from_var( - &self, - recursion: u32, - node_name: &str, - var: &VariableIR, - select_path: Option, - ) -> Node> { - let name = var.name(); - let typ = var.r#type(); - - // recursion guard - if recursion >= MAX_RECURSION { - return Node::new( - node_name.to_string(), - render_var(&name, typ, "...").expect("should be rendered"), - ); - } +fn node_from_var2( + recursion: u32, + node_name: &str, + name: Option<&str>, + qr: QueryResult, +) -> Node> { + let ty = qr.value().r#type().name_fmt(); + + // recursion guard + if recursion >= MAX_RECURSION { + return Node::new( + node_name.to_string(), + render_var(None, ty, "...").expect("should be rendered"), + ); + } - match var.value() { - None => Node::new( + match qr.value().value_layout() { + None => Node::new( + node_name.to_string(), + render_var(None, ty, "???").expect("should be rendered"), + ), + Some(layout) => match layout { + ValueLayout::PreRendered(val) => Node::new( node_name.to_string(), - render_var(&name, typ, "unknown").expect("should be rendered"), + render_var(name, ty, &val).expect("should be rendered"), ), - Some(layout) => match layout { - ValueLayout::PreRendered(val) => { - let s = val.as_ref(); - if let Err(e) = String::from_utf8(s.as_bytes().to_vec()) { - println!("err: {e}"); - } + ValueLayout::Referential(addr) => { + let value = format!("{addr:p}"); + let mut node = Node::new( + node_name.to_string(), + render_var(name, ty, &value).expect("should be rendered"), + ); - Node::new( - node_name.to_string(), - render_var(&name, typ, &val).expect("should be rendered"), - ) - } - ValueLayout::Referential { addr, .. } => { - let value = format!("{addr:p}"); - let mut node = Node::new( - node_name.to_string(), - render_var(&name, typ, &value).expect("should be rendered"), + let qr = qr.modify_value(|ctx, val| val.deref(ctx)); + + if let Some(qr) = qr { + let deref_node = node_from_var2( + recursion + 1, + format!("{node_name}_deref").as_str(), + Some("*"), + qr, ); + node.add_child(deref_node); + } - if let Some(path) = select_path { - let deref_expr = DQE::Deref(Box::new(path)); - - let variables = { - let deref_expr = deref_expr.clone(); - self.exchanger - .request_sync(|dbg| { - let handler = command::variables::Handler::new(dbg); - handler.handle(deref_expr) - }) - .expect("messaging enabled") - }; - - if let Ok(variables) = variables { - if let Some(var) = variables.first() { - let deref_node = self.node_from_var( - recursion + 1, - format!("{node_name}_deref").as_str(), - var, - Some(deref_expr), - ); - node.add_child(deref_node); - } - } - } + node + } + ValueLayout::Wrapped(inner) => { + let mut node = Node::new( + node_name.to_string(), + render_var_def(name, ty).expect("should be rendered"), + ); + let qr = qr + .clone() + .modify_value(|_, _| Some(inner.clone())) + .expect("should be `Some`"); - node + node.add_child(node_from_var2( + recursion + 1, + format!("{node_name}_1").as_str(), + None, + qr, + )); + + node + } + ValueLayout::Structure(members) => { + let mut node = Node::new( + node_name.to_string(), + render_var_def(name, ty).expect("should be rendered"), + ); + for (i, member) in members.iter().enumerate() { + let member_var = qr + .clone() + .modify_value(|_, _| Some(member.value.clone())) + .expect("should be `Some`"); + + node.add_child(node_from_var2( + recursion + 1, + format!("{node_name}_{i}").as_str(), + member.field_name.as_deref(), + member_var, + )); } - ValueLayout::Wrapped(other) => { - let mut node = Node::new( - node_name.to_string(), - render_var_def(&name, typ).expect("should be rendered"), - ); - node.add_child(self.node_from_var( + node + } + ValueLayout::IndexedList(items) => { + let mut node = Node::new( + node_name.to_string(), + render_var_def(name, ty).expect("should be rendered"), + ); + for (i, item) in items.iter().enumerate() { + let item_var = qr + .clone() + .modify_value(|_, _| Some(item.value.clone())) + .expect("should be `Some`"); + + node.add_child(node_from_var2( recursion + 1, - format!("{node_name}_1").as_str(), - other, - select_path, + format!("{node_name}_{i}").as_str(), + Some(&format!("{}", item.index)), + item_var, )); - node } - ValueLayout::Structure { members, .. } => { - let mut node = Node::new( - node_name.to_string(), - render_var_def(&name, typ).expect("should be rendered"), - ); - for (i, member) in members.iter().enumerate() { - node.add_child( - self.node_from_var( - recursion + 1, - format!("{node_name}_{i}").as_str(), - member, - select_path - .clone() - .map(|expr| DQE::Field(Box::new(expr), member.name())), - ), - ); - } - node + node + } + ValueLayout::NonIndexedList(items) => { + let mut node = Node::new( + node_name.to_string(), + render_var_def(name, ty).expect("should be rendered"), + ); + for (i, value) in items.iter().enumerate() { + let item_var = qr + .clone() + .modify_value(|_, _| Some(value.clone())) + .expect("should be `Some`"); + + node.add_child(node_from_var2( + recursion + 1, + format!("{node_name}_{i}").as_str(), + None, + item_var, + )); } - ValueLayout::Map(kvs) => { - let mut node = Node::new( - node_name.to_string(), - render_var_def(&name, typ).expect("should be rendered"), + node + } + ValueLayout::Map(kvs) => { + let mut node = Node::new( + node_name.to_string(), + render_var_def(name, ty).expect("should be rendered"), + ); + for (i, (key, _val)) in kvs.iter().enumerate() { + let mut kv_pair = Node::new( + format!("{node_name}_kv_{i}"), + vec![TextSpan::new(format!("kv {i}"))], ); - for (i, (key, val)) in kvs.iter().enumerate() { - let mut kv_pair = Node::new( - format!("{node_name}_kv_{i}"), - vec![TextSpan::new(format!("kv {i}"))], - ); - - kv_pair.add_child(self.node_from_var( - recursion + 1, - format!("{node_name}_kv_{i}_key").as_str(), - key, - // currently no way to use expressions with keys - None, - )); - - kv_pair.add_child( - self.node_from_var( + + let key_var = qr + .clone() + .modify_value(|_, _| Some(key.clone())) + .expect("should be `Some`"); + let key_literal = key_var.value().as_literal(); + + kv_pair.add_child(node_from_var2( + recursion + 1, + format!("{node_name}_kv_{i}_key").as_str(), + Some("key"), + key_var, + )); + + if let Some(ref key_literal) = key_literal { + let value_var = qr.clone(); + let value_var = value_var.modify_value(|_, value| value.index(key_literal)); + + if let Some(value_var) = value_var { + kv_pair.add_child(node_from_var2( recursion + 1, format!("{node_name}_kv_{i}_val").as_str(), - val, - // todo works only if key is a String or &str, need better support of field expr on maps - select_path - .clone() - .map(|expr| DQE::Field(Box::new(expr), key.name())), - ), - ); - node.add_child(kv_pair); - } - node - } - ValueLayout::List { members, indexed } => { - let mut node = Node::new( - node_name.to_string(), - render_var_def(&name, typ).expect("should be rendered"), - ); - for (i, member) in members.iter().enumerate() { - let el_path = if indexed { - select_path.clone().and_then(|expr| { - let mb_idx: Option = member.name().parse().ok(); - mb_idx - .map(|idx| DQE::Index(Box::new(expr), Literal::Int(idx as i64))) - }) - } else { - None - }; - - node.add_child(self.node_from_var( - recursion + 1, - format!("{node_name}_{i}").as_str(), - member, - el_path, - )); + Some("value"), + value_var, + )); + } } - node + node.add_child(kv_pair); } - }, - } + node + } + }, } +} +impl Variables { fn update(&mut self) { - let Ok(variables) = self.exchanger.request_sync(|dbg| { - let expr = select::DQE::Variable(VariableSelector::Any); - let vars = command::variables::Handler::new(dbg) - .handle(expr) - .unwrap_or_default(); - vars + let Ok(vars_node) = self.exchanger.request_sync(|dbg| { + let expr = Dqe::Variable(Selector::Any); + let handler = command::variables::Handler::new(dbg); + let vars = handler.handle(expr).unwrap_or_default(); + + let mut vars_node = + Node::new("variables".to_string(), vec![TextSpan::new("variables")]); + + for (i, var) in vars.into_iter().enumerate() { + let node_name = format!("var_{i}"); + let name = if var.kind() == QueryResultKind::Root && var.identity().name.is_some() { + Some(var.identity().to_string()) + } else { + None + }; + + let var_node = node_from_var2(0, &node_name, name.as_deref(), var); + vars_node.add_child(var_node); + } + + vars_node }) else { return; }; - let Ok(arguments) = self.exchanger.request_sync(|dbg| { - let expr = select::DQE::Variable(VariableSelector::Any); - let args = command::arguments::Handler::new(dbg) - .handle(expr) - .unwrap_or_default(); - args + + let Ok(args_node) = self.exchanger.request_sync(|dbg| { + let expr = Dqe::Variable(Selector::Any); + let handler = command::arguments::Handler::new(dbg); + let args = handler.handle(expr).unwrap_or_default(); + + let mut args_node = + Node::new("arguments".to_string(), vec![TextSpan::new("arguments")]); + for (i, arg) in args.into_iter().enumerate() { + let node_name = format!("arg_{i}"); + let name = if arg.kind() == QueryResultKind::Root && arg.identity().name.is_some() { + Some(arg.identity().to_string()) + } else { + None + }; + + let var_node = node_from_var2(0, &node_name, name.as_deref(), arg); + args_node.add_child(var_node); + } + args_node }) else { return; }; @@ -253,46 +296,20 @@ impl Variables { vec![TextSpan::new("arguments and variables")], ); - let mut args_node = Node::new("arguments".to_string(), vec![TextSpan::new("arguments")]); - for (i, arg) in arguments.iter().enumerate() { - let node_name = format!("arg_{i}"); - let var_node = self.node_from_var( - 0, - node_name.as_str(), - arg, - Some(DQE::Variable(VariableSelector::Name { - var_name: arg.name(), - only_local: false, - })), - ); - args_node.add_child(var_node); - } - root.add_child(args_node); + let vars_count = vars_node.children().len(); + let args_count = args_node.children().len(); - let mut vars_node = Node::new("variables".to_string(), vec![TextSpan::new("variables")]); - for (i, var) in variables.iter().enumerate() { - let node_name = format!("var_{i}"); - let var_node = self.node_from_var( - 0, - node_name.as_str(), - var, - Some(DQE::Variable(VariableSelector::Name { - var_name: var.name(), - only_local: true, - })), - ); - vars_node.add_child(var_node); - } + root.add_child(args_node); root.add_child(vars_node); self.component.set_tree(Tree::new(root)); - if !variables.is_empty() { + if vars_count != 0 { self.component.attr( Attribute::Custom(TREE_INITIAL_NODE), AttrValue::String("var_0".to_string()), ); } - if !arguments.is_empty() { + if args_count != 0 { self.component.attr( Attribute::Custom(TREE_INITIAL_NODE), AttrValue::String("arg_0".to_string()), @@ -355,7 +372,7 @@ impl Variables { .inactive(Style::default().fg(Color::Gray)) .indent_size(3) .scroll_step(6) - .preserve_state(true) + .preserve_state(false) .title("Variables", Alignment::Center) .highlighted_color(Color::LightYellow) .highlight_symbol("â–¶"), diff --git a/tests/debugger/common/mod.rs b/tests/debugger/common/mod.rs index 9991717..35ec8d1 100644 --- a/tests/debugger/common/mod.rs +++ b/tests/debugger/common/mod.rs @@ -1,6 +1,6 @@ use bugstalker::debugger::address::RelocatedAddress; use bugstalker::debugger::register::debug::BreakCondition; -use bugstalker::debugger::variable::VariableIR; +use bugstalker::debugger::variable::value::Value; use bugstalker::debugger::{EventHook, FunctionDie, PlaceDescriptor}; use bugstalker::version::Version; use nix::sys::signal::Signal; @@ -15,8 +15,9 @@ pub struct TestInfo { pub addr: Arc>>, pub line: Arc>>, pub file: Arc>>, - pub old_value: Arc>>, - pub new_value: Arc>>, + pub wp_dqe_string: Arc>>, + pub old_value: Arc>>, + pub new_value: Arc>>, } #[derive(Default)] @@ -51,14 +52,18 @@ impl EventHook for TestHooks { _: u32, place: Option, _: BreakCondition, - old_value: Option<&VariableIR>, - new_value: Option<&VariableIR>, + dqe_string: Option<&str>, + old_value: Option<&Value>, + new_value: Option<&Value>, _: bool, ) -> anyhow::Result<()> { self.info.addr.set(Some(pc)); let file = &self.info.file; file.set(place.as_ref().map(|p| p.file.to_str().unwrap().to_string())); self.info.line.set(place.map(|p| p.line_number)); + self.info + .wp_dqe_string + .replace(dqe_string.map(ToString::to_string)); self.info.old_value.replace(old_value.cloned()); self.info.new_value.replace(new_value.cloned()); Ok(()) diff --git a/tests/debugger/io.rs b/tests/debugger/io.rs index 7c35019..5f5fff0 100644 --- a/tests/debugger/io.rs +++ b/tests/debugger/io.rs @@ -2,7 +2,7 @@ use crate::common::TestHooks; use crate::common::TestInfo; use crate::HW_APP; use crate::{assert_no_proc, prepare_debugee_process, CALC_APP}; -use bugstalker::debugger::variable::render::{RenderRepr, ValueLayout}; +use bugstalker::debugger::variable::render::{RenderValue, ValueLayout}; use bugstalker::debugger::DebuggerBuilder; use serial_test::serial; use std::borrow::Cow; @@ -77,12 +77,13 @@ fn test_read_value_u64() { let vars = debugger.read_local_variables().unwrap(); assert_eq!(vars.len(), 5); - assert_eq!(vars[4].name(), "s"); - assert_eq!(vars[4].r#type(), "i64"); - let _six = "6".to_string(); + + let s = &vars[4]; + assert_eq!(s.identity().to_string(), "s"); + assert_eq!(s.value().r#type().name_fmt(), "i64"); assert!(matches!( - vars[4].value().unwrap(), - ValueLayout::PreRendered(Cow::Owned(_six)) + s.value().value_layout().unwrap(), + ValueLayout::PreRendered(Cow::Owned(str)) if str == "6" )); debugger.continue_debugee().unwrap(); diff --git a/tests/debugger/steps.rs b/tests/debugger/steps.rs index a1d5f84..ec0ef7c 100644 --- a/tests/debugger/steps.rs +++ b/tests/debugger/steps.rs @@ -2,7 +2,7 @@ use crate::common::TestInfo; use crate::common::{rust_version, TestHooks}; use crate::CALC_APP; use crate::{assert_no_proc, prepare_debugee_process, HW_APP, RECURSION_APP, VARS_APP}; -use bugstalker::debugger::variable::{SupportedScalar, VariableIR}; +use bugstalker::debugger::variable::value::{SupportedScalar, Value}; use bugstalker::debugger::{Debugger, DebuggerBuilder}; use bugstalker::ui::command::parser::expression; use bugstalker::version_switch; @@ -64,7 +64,7 @@ fn test_step_into_recursion() { fn assert_arg(debugger: &Debugger, expected: u64) { let get_i_expr = expression::parser().parse("i").unwrap(); let i_arg = debugger.read_argument(get_i_expr).unwrap().pop().unwrap(); - let VariableIR::Scalar(scalar) = i_arg else { + let Value::Scalar(scalar) = i_arg.into_value() else { panic!("not a scalar"); }; assert_eq!(scalar.value, Some(SupportedScalar::U64(expected))); @@ -154,18 +154,19 @@ fn test_step_over() { #[test] #[serial] fn test_step_over_inline_code() { + // TODO this test should be reworked let process = prepare_debugee_process(VARS_APP, &[]); let debugee_pid = process.pid(); let info = TestInfo::default(); let builder = DebuggerBuilder::new().with_hooks(TestHooks::new(info.clone())); let mut debugger = builder.build(process).unwrap(); - debugger.set_breakpoint_at_line("vars.rs", 536).unwrap(); + debugger.set_breakpoint_at_line("vars.rs", 545).unwrap(); debugger.start_debugee().unwrap(); - assert_eq!(info.line.take(), Some(536)); + assert_eq!(info.line.take(), Some(545)); debugger.step_over().unwrap(); - assert_eq!(info.line.take(), Some(537)); + assert_eq!(info.line.take(), Some(546)); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); diff --git a/tests/debugger/variables.rs b/tests/debugger/variables.rs index a942ae4..1556714 100644 --- a/tests/debugger/variables.rs +++ b/tests/debugger/variables.rs @@ -2,336 +2,341 @@ use crate::common::TestHooks; use crate::common::{rust_version, TestInfo}; use crate::VARS_APP; use crate::{assert_no_proc, prepare_debugee_process}; -use bugstalker::debugger::variable::render::RenderRepr; -use bugstalker::debugger::variable::select::{Literal, LiteralOrWildcard, VariableSelector, DQE}; -use bugstalker::debugger::variable::{select, VariableIR}; -use bugstalker::debugger::{variable, Debugger, DebuggerBuilder}; -use bugstalker::ui::command::parser::expression; +use bugstalker::debugger::variable::dqe::{Dqe, Literal, LiteralOrWildcard, PointerCast, Selector}; +use bugstalker::debugger::variable::render::RenderValue; +use bugstalker::debugger::variable::value::{Member, SpecializedValue, SupportedScalar, Value}; +use bugstalker::debugger::DebuggerBuilder; use bugstalker::version::Version; -use bugstalker::{debugger, version_switch}; -use chumsky::Parser; -use debugger::variable::SupportedScalar; +use bugstalker::version_switch; use serial_test::serial; use std::collections::HashMap; -pub fn assert_scalar( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - exp_val: Option, -) { - let VariableIR::Scalar(scalar) = var else { +pub fn assert_scalar(value: &Value, exp_type: &str, exp_val: Option) { + let Value::Scalar(scalar) = value else { panic!("not a scalar"); }; - assert_eq!(var.name(), exp_name); - assert_eq!(var.r#type(), exp_type); + assert_eq!(value.r#type().name_fmt(), exp_type); assert_eq!(scalar.value, exp_val); } -fn assert_struct( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - for_each_member: impl Fn(usize, &VariableIR), -) { - let VariableIR::Struct(structure) = var else { +fn assert_struct(val: &Value, exp_type: &str, for_each_member: impl Fn(usize, &Member)) { + let Value::Struct(structure) = val else { panic!("not a struct"); }; - assert_eq!(var.name(), exp_name); - assert_eq!(var.r#type(), exp_type); + assert_eq!(val.r#type().name_fmt(), exp_type); for (i, member) in structure.members.iter().enumerate() { for_each_member(i, member) } } -fn assert_array( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - for_each_item: impl Fn(usize, &VariableIR), -) { - let VariableIR::Array(array) = var else { - panic!("not a array"); +fn assert_member(member: &Member, expected_field_name: &str, with_value: impl Fn(&Value)) { + assert_eq!(member.field_name.as_deref(), Some(expected_field_name)); + with_value(&member.value); +} + +fn assert_array(val: &Value, exp_type: &str, for_each_item: impl Fn(usize, &Value)) { + let Value::Array(array) = val else { + panic!("not an array"); }; - assert_eq!(array.identity.name.as_ref().unwrap(), exp_name); - assert_eq!(array.type_name.as_ref().unwrap(), exp_type); + assert_eq!(array.type_ident.name_fmt(), exp_type); for (i, item) in array.items.as_ref().unwrap_or(&vec![]).iter().enumerate() { - for_each_item(i, item) + for_each_item(i, &item.value) } } -fn assert_c_enum(var: &VariableIR, exp_name: &str, exp_type: &str, exp_value: Option) { - let VariableIR::CEnum(c_enum) = var else { +fn assert_c_enum(val: &Value, exp_type: &str, exp_value: Option) { + let Value::CEnum(c_enum) = val else { panic!("not a c_enum"); }; - assert_eq!(var.name(), exp_name); - assert_eq!(var.r#type(), exp_type); + assert_eq!(val.r#type().name_fmt(), exp_type); assert_eq!(c_enum.value, exp_value); } -fn assert_rust_enum( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - with_value: impl FnOnce(&VariableIR), -) { - let VariableIR::RustEnum(rust_enum) = var else { +fn assert_rust_enum(val: &Value, exp_type: &str, with_value: impl FnOnce(&Value)) { + let Value::RustEnum(rust_enum) = val else { panic!("not a c_enum"); }; - assert_eq!(var.name(), exp_name); - assert_eq!(var.r#type(), exp_type); - with_value(rust_enum.value.as_ref().unwrap()); + assert_eq!(val.r#type().name_fmt(), exp_type); + with_value(&rust_enum.value.as_ref().unwrap().value); } -fn assert_pointer(var: &VariableIR, exp_name: &str, exp_type: &str) { - let VariableIR::Pointer(ptr) = var else { +fn assert_pointer(val: &Value, exp_type: &str) { + let Value::Pointer(ptr) = val else { panic!("not a pointer"); }; - assert_eq!(var.name(), exp_name); - assert_eq!(ptr.type_name.as_ref().unwrap(), exp_type); -} - -fn assert_vec( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - exp_cap: usize, - with_buf: impl FnOnce(&VariableIR), -) { - let VariableIR::Specialized(variable::SpecializedVariableIR::Vector { - vec: Some(vector), .. - }) = var + assert_eq!(ptr.type_ident.name_fmt(), exp_type); +} + +fn assert_vec(val: &Value, exp_type: &str, exp_cap: usize, with_buf: impl FnOnce(&Value)) { + let Value::Specialized { + value: Some(SpecializedValue::Vector(vector)), + .. + } = val else { panic!("not a vector"); }; - assert_eq!(var.name(), exp_name); - assert_eq!(var.r#type(), exp_type); - let VariableIR::Scalar(capacity) = &vector.structure.members[1] else { + assert_eq!(val.r#type().name_fmt(), exp_type); + let Value::Scalar(capacity) = &vector.structure.members[1].value else { panic!("no capacity"); }; assert_eq!(capacity.value, Some(SupportedScalar::Usize(exp_cap))); - with_buf(&vector.structure.members[0]); + with_buf(&vector.structure.members[0].value); } -fn assert_string(var: &VariableIR, exp_name: &str, exp_value: &str) { - let VariableIR::Specialized(variable::SpecializedVariableIR::String { - string: Some(string), +fn assert_string(val: &Value, exp_value: &str) { + let Value::Specialized { + value: Some(SpecializedValue::String(string)), .. - }) = var + } = val else { panic!("not a string"); }; - assert_eq!(var.name(), exp_name); assert_eq!(string.value, exp_value); } -fn assert_str(var: &VariableIR, exp_name: &str, exp_value: &str) { - let VariableIR::Specialized(variable::SpecializedVariableIR::Str { - string: Some(str), .. - }) = var +fn assert_str(val: &Value, exp_value: &str) { + let Value::Specialized { + value: Some(SpecializedValue::Str(str)), + .. + } = val else { panic!("not a &str"); }; - assert_eq!(var.name(), exp_name); assert_eq!(str.value, exp_value); } -fn assert_init_tls( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - with_var: impl FnOnce(&VariableIR), -) { - let VariableIR::Specialized(variable::SpecializedVariableIR::Tls { - tls_var: Some(tls), .. - }) = var +fn assert_init_tls(val: &Value, exp_type: &str, with_inner: impl FnOnce(&Value)) { + let Value::Specialized { + value: Some(SpecializedValue::Tls(tls)), + .. + } = val else { panic!("not a tls"); }; - assert_eq!(tls.identity.name.as_ref().unwrap(), exp_name); - assert_eq!(tls.inner_type.as_ref().unwrap(), exp_type); - with_var(tls.inner_value.as_ref().unwrap()); + assert_eq!(tls.inner_type.name_fmt(), exp_type); + with_inner(tls.inner_value.as_ref().unwrap()); } -fn assert_uninit_tls(var: &VariableIR, exp_name: &str, exp_type: &str) { - let VariableIR::Specialized(variable::SpecializedVariableIR::Tls { - tls_var: Some(tls), .. - }) = var +fn assert_uninit_tls(val: &Value, exp_type: &str) { + let Value::Specialized { + value: Some(SpecializedValue::Tls(tls)), + .. + } = val else { panic!("not a tls"); }; - assert_eq!(tls.identity.name.as_ref().unwrap(), exp_name); - assert_eq!(tls.inner_type.as_ref().unwrap(), exp_type); + assert_eq!(tls.inner_type.name_fmt(), exp_type); assert!(tls.inner_value.is_none()); } -fn assert_hashmap( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - with_kv_items: impl FnOnce(&Vec<(VariableIR, VariableIR)>), -) { - let VariableIR::Specialized(variable::SpecializedVariableIR::HashMap { - map: Some(map), .. - }) = var +fn assert_hashmap(val: &Value, exp_type: &str, with_kv_items: impl FnOnce(&Vec<(Value, Value)>)) { + let Value::Specialized { + value: Some(SpecializedValue::HashMap(map)), + .. + } = val else { panic!("not a hashmap"); }; - assert_eq!(var.name(), exp_name); - assert_eq!(var.r#type(), exp_type); + assert_eq!(val.r#type().name_fmt(), exp_type); let mut items = map.kv_items.clone(); items.sort_by(|v1, v2| { - let k1_render = format!("{:?}", v1.0.value()); - let k2_render = format!("{:?}", v2.0.value()); + let k1_render = format!("{:?}", v1.0.value_layout()); + let k2_render = format!("{:?}", v2.0.value_layout()); k1_render.cmp(&k2_render) }); with_kv_items(&items); } -fn assert_hashset( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - with_items: impl FnOnce(&Vec), -) { - let VariableIR::Specialized(variable::SpecializedVariableIR::HashSet { - set: Some(set), .. - }) = var +fn assert_hashset(val: &Value, exp_type: &str, with_items: impl FnOnce(&Vec)) { + let Value::Specialized { + value: Some(SpecializedValue::HashSet(set)), + .. + } = val else { panic!("not a hashset"); }; - assert_eq!(set.identity.name.as_ref().unwrap(), exp_name); - assert_eq!(set.type_name.as_ref().unwrap(), exp_type); + assert_eq!(set.type_ident.name_fmt(), exp_type); let mut items = set.items.clone(); items.sort_by(|v1, v2| { - let k1_render = format!("{:?}", v1.value()); - let k2_render = format!("{:?}", v2.value()); + let k1_render = format!("{:?}", v1.value_layout()); + let k2_render = format!("{:?}", v2.value_layout()); k1_render.cmp(&k2_render) }); with_items(&items); } -fn assert_btree_map( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - with_kv_items: impl FnOnce(&Vec<(VariableIR, VariableIR)>), -) { - let VariableIR::Specialized(variable::SpecializedVariableIR::BTreeMap { - map: Some(map), .. - }) = var +fn assert_btree_map(val: &Value, exp_type: &str, with_kv_items: impl FnOnce(&Vec<(Value, Value)>)) { + let Value::Specialized { + value: Some(SpecializedValue::BTreeMap(map)), + .. + } = val else { panic!("not a BTreeMap"); }; - assert_eq!(map.identity.name.as_ref().unwrap(), exp_name); - assert_eq!(map.type_name.as_ref().unwrap(), exp_type); + assert_eq!(map.type_ident.name_fmt(), exp_type); with_kv_items(&map.kv_items); } -fn assert_btree_set( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - with_items: impl FnOnce(&Vec), -) { - let VariableIR::Specialized(variable::SpecializedVariableIR::BTreeSet { - set: Some(set), .. - }) = var +fn assert_btree_set(val: &Value, exp_type: &str, with_items: impl FnOnce(&Vec)) { + let Value::Specialized { + value: Some(SpecializedValue::BTreeSet(set)), + .. + } = val else { panic!("not a BTreeSet"); }; - assert_eq!(set.identity.name.as_ref().unwrap(), exp_name); - assert_eq!(set.type_name.as_ref().unwrap(), exp_type); + assert_eq!(set.type_ident.name_fmt(), exp_type); with_items(&set.items); } -fn assert_vec_deque( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - exp_cap: usize, - with_buf: impl FnOnce(&VariableIR), -) { - let VariableIR::Specialized(variable::SpecializedVariableIR::VecDeque { - vec: Some(vector), +fn assert_vec_deque(val: &Value, exp_type: &str, exp_cap: usize, with_buf: impl FnOnce(&Value)) { + let Value::Specialized { + value: Some(SpecializedValue::VecDeque(vector)), .. - }) = var + } = val else { panic!("not a VecDeque"); }; - assert_eq!(vector.structure.identity.name.as_ref().unwrap(), exp_name); - assert_eq!(vector.structure.type_name.as_ref().unwrap(), exp_type); - let VariableIR::Scalar(capacity) = &vector.structure.members[1] else { + assert_eq!(vector.structure.type_ident.name_fmt(), exp_type); + let Value::Scalar(capacity) = &vector.structure.members[1].value else { panic!("no capacity"); }; assert_eq!(capacity.value, Some(SupportedScalar::Usize(exp_cap))); - with_buf(&vector.structure.members[0]); + with_buf(&vector.structure.members[0].value); } -fn assert_cell( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - with_value: impl FnOnce(&VariableIR), -) { - let VariableIR::Specialized(variable::SpecializedVariableIR::Cell { value, .. }) = var else { +fn assert_cell(val: &Value, exp_type: &str, with_value: impl FnOnce(&Value)) { + let Value::Specialized { + value: Some(SpecializedValue::Cell(value)), + .. + } = val + else { panic!("not a Cell"); }; - assert_eq!(var.name(), exp_name); - assert_eq!(var.r#type(), exp_type); - with_value(value.as_ref().unwrap()); -} - -fn assert_refcell( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - exp_borrow: isize, - with_value: impl FnOnce(&VariableIR), -) { - let VariableIR::Specialized(variable::SpecializedVariableIR::RefCell { value, .. }) = var + assert_eq!(val.r#type().name_fmt(), exp_type); + with_value(value.as_ref()); +} + +fn assert_refcell(val: &Value, exp_type: &str, exp_borrow: isize, with_inner: impl FnOnce(&Value)) { + let Value::Specialized { + value: Some(SpecializedValue::RefCell(value)), + .. + } = val else { panic!("not a Cell"); }; - assert_eq!(var.name(), exp_name); - assert_eq!(var.r#type(), exp_type); - let value = &**value.as_ref().unwrap(); - let VariableIR::Struct(as_struct) = value else { + assert_eq!(val.r#type().name_fmt(), exp_type); + let Value::Struct(as_struct) = value.as_ref() else { panic!("not a struct") }; - let VariableIR::Scalar(borrow) = &as_struct.members[0] else { - panic!("no capacity"); + let Value::Scalar(borrow) = &as_struct.members[0].value else { + panic!("no borrow flag"); }; assert_eq!( borrow.value.as_ref().unwrap(), &SupportedScalar::Isize(exp_borrow) ); - with_value(&as_struct.members[1]); + with_inner(&as_struct.members[1].value); } -fn assert_rc(var: &VariableIR, exp_name: &str, exp_type: &str) { - let VariableIR::Specialized(variable::SpecializedVariableIR::Rc { .. }) = var else { +fn assert_rc(val: &Value, exp_type: &str) { + let Value::Specialized { + value: Some(SpecializedValue::Rc(_)), + .. + } = val + else { panic!("not an rc"); }; - assert_eq!(var.name(), exp_name); - assert_eq!(var.r#type(), exp_type); + assert_eq!(val.r#type().name_fmt(), exp_type); } -fn assert_arc(var: &VariableIR, exp_name: &str, exp_type: &str) { - let VariableIR::Specialized(variable::SpecializedVariableIR::Arc { .. }) = var else { +fn assert_arc(val: &Value, exp_type: &str) { + let Value::Specialized { + value: Some(SpecializedValue::Arc(_)), + .. + } = val + else { panic!("not an arc"); }; - assert_eq!(var.name(), exp_name); - assert_eq!(var.r#type(), exp_type); + assert_eq!(val.r#type().name_fmt(), exp_type); } -fn assert_uuid(var: &VariableIR, exp_name: &str, exp_type: &str) { - let VariableIR::Specialized(variable::SpecializedVariableIR::Uuid { .. }) = var else { +fn assert_uuid(val: &Value, exp_type: &str) { + let Value::Specialized { + value: Some(SpecializedValue::Uuid(_)), + .. + } = val + else { panic!("not an uuid"); }; - assert_eq!(var.name(), exp_name); - assert_eq!(var.r#type(), exp_type); + assert_eq!(val.r#type().name_fmt(), exp_type); +} + +fn assert_system_time(val: &Value, exp_value: (i64, u32)) { + let Value::Specialized { + value: Some(SpecializedValue::SystemTime(value)), + .. + } = val + else { + panic!("not a SystemTime"); + }; + assert_eq!(*value, exp_value); +} + +fn assert_instant(val: &Value) { + let Value::Specialized { + value: Some(SpecializedValue::Instant(_)), + .. + } = val + else { + panic!("not an Instant"); + }; +} + +macro_rules! read_locals { + ($debugger: expr => $($var: ident),*) => { + let vars = $debugger.read_local_variables().unwrap(); + let &[$($var),*] = &vars.as_slice() else { + panic!("Invalid variables count") + }; + }; +} + +macro_rules! read_var_dqe { + ($debugger: expr, $dqe: expr => $($var: ident),*) => { + let vars = $debugger.read_variable($dqe).unwrap(); + let &[$($var),*] = &vars.as_slice() else { + panic!("Invalid variables count") + }; + }; +} + +macro_rules! read_arg_dqe { + ($debugger: expr, $dqe: expr => $($var: ident),*) => { + let args = $debugger.read_argument($dqe).unwrap(); + let &[$($var),*] = &args.as_slice() else { + panic!("Invalid variables count") + }; + }; +} + +macro_rules! read_var_dqe_type_order { + ($debugger: expr, $dqe: expr => $($var: ident),*) => { + let mut vars = $debugger.read_variable($dqe).unwrap(); + vars.sort_by(|v1, v2| v1.value().r#type().cmp(v2.value().r#type())); + let &[$($var),*] = &vars.as_slice() else { + panic!("Invalid variables count") + }; + }; +} + +macro_rules! assert_idents { + ($($var: ident => $name: literal),*) => { + $( + assert_eq!($var.identity().to_string(), $name); + )* + }; } #[test] @@ -348,46 +353,33 @@ fn test_read_scalar_variables() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(30)); - let vars = debugger.read_local_variables().unwrap(); - assert_scalar(&vars[0], "int8", "i8", Some(SupportedScalar::I8(1))); - assert_scalar(&vars[1], "int16", "i16", Some(SupportedScalar::I16(-1))); - assert_scalar(&vars[2], "int32", "i32", Some(SupportedScalar::I32(2))); - assert_scalar(&vars[3], "int64", "i64", Some(SupportedScalar::I64(-2))); - assert_scalar(&vars[4], "int128", "i128", Some(SupportedScalar::I128(3))); - assert_scalar(&vars[5], "isize", "isize", Some(SupportedScalar::Isize(-3))); - assert_scalar(&vars[6], "uint8", "u8", Some(SupportedScalar::U8(1))); - assert_scalar(&vars[7], "uint16", "u16", Some(SupportedScalar::U16(2))); - assert_scalar(&vars[8], "uint32", "u32", Some(SupportedScalar::U32(3))); - assert_scalar(&vars[9], "uint64", "u64", Some(SupportedScalar::U64(4))); - assert_scalar(&vars[10], "uint128", "u128", Some(SupportedScalar::U128(5))); - assert_scalar(&vars[11], "usize", "usize", Some(SupportedScalar::Usize(6))); - assert_scalar(&vars[12], "f32", "f32", Some(SupportedScalar::F32(1.1))); - assert_scalar(&vars[13], "f64", "f64", Some(SupportedScalar::F64(1.2))); - assert_scalar( - &vars[14], - "boolean_true", - "bool", - Some(SupportedScalar::Bool(true)), - ); - assert_scalar( - &vars[15], - "boolean_false", - "bool", - Some(SupportedScalar::Bool(false)), - ); - assert_scalar( - &vars[16], - "char_ascii", - "char", - Some(SupportedScalar::Char('a')), - ); - assert_scalar( - &vars[17], - "char_non_ascii", - "char", - Some(SupportedScalar::Char('😊')), + read_locals!(debugger => int8, int16, int32, int64, int128, isize, uint8, uint16, uint32, uint64, uint128, usize, f32, f64, b_true, b_false, ch_ascii, c_n_ascii); + assert_idents!( + int8 => "int8", int16 => "int16", int32 => "int32", int64 => "int64", int128 => "int128", + isize => "isize", uint8 => "uint8", uint16 => "uint16", uint32 => "uint32", uint64 => "uint64", + uint128 => "uint128", usize => "usize", f32 => "f32", f64 => "f64", b_true => "boolean_true", + b_false => "boolean_false", ch_ascii => "char_ascii", c_n_ascii => "char_non_ascii" ); + assert_scalar(int8.value(), "i8", Some(SupportedScalar::I8(1))); + assert_scalar(int16.value(), "i16", Some(SupportedScalar::I16(-1))); + assert_scalar(int32.value(), "i32", Some(SupportedScalar::I32(2))); + assert_scalar(int64.value(), "i64", Some(SupportedScalar::I64(-2))); + assert_scalar(int128.value(), "i128", Some(SupportedScalar::I128(3))); + assert_scalar(isize.value(), "isize", Some(SupportedScalar::Isize(-3))); + assert_scalar(uint8.value(), "u8", Some(SupportedScalar::U8(1))); + assert_scalar(uint16.value(), "u16", Some(SupportedScalar::U16(2))); + assert_scalar(uint32.value(), "u32", Some(SupportedScalar::U32(3))); + assert_scalar(uint64.value(), "u64", Some(SupportedScalar::U64(4))); + assert_scalar(uint128.value(), "u128", Some(SupportedScalar::U128(5))); + assert_scalar(usize.value(), "usize", Some(SupportedScalar::Usize(6))); + assert_scalar(f32.value(), "f32", Some(SupportedScalar::F32(1.1))); + assert_scalar(f64.value(), "f64", Some(SupportedScalar::F64(1.2))); + assert_scalar(b_true.value(), "bool", Some(SupportedScalar::Bool(true))); + assert_scalar(b_false.value(), "bool", Some(SupportedScalar::Bool(false))); + assert_scalar(ch_ascii.value(), "char", Some(SupportedScalar::Char('a'))); + assert_scalar(c_n_ascii.value(), "char", Some(SupportedScalar::Char('😊'))); + debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); } @@ -430,42 +422,62 @@ fn test_read_struct() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(53)); - let vars = debugger.read_local_variables().unwrap(); - assert_scalar(&vars[0], "tuple_0", "()", Some(SupportedScalar::Empty())); - assert_struct(&vars[1], "tuple_1", "(f64, f64)", |i, member| match i { - 0 => assert_scalar(member, "0", "f64", Some(SupportedScalar::F64(0f64))), - 1 => assert_scalar(member, "1", "f64", Some(SupportedScalar::F64(1.1f64))), + read_locals!(debugger => tuple_0, tuple_1, tuple_2, foo, foo2); + assert_idents!(tuple_0 => "tuple_0", tuple_1 => "tuple_1", tuple_2 => "tuple_2", foo => "foo", foo2 => "foo2"); + + assert_scalar(tuple_0.value(), "()", Some(SupportedScalar::Empty())); + assert_struct(tuple_1.value(), "(f64, f64)", |i, member| match i { + 0 => assert_member(member, "__0", |val| { + assert_scalar(val, "f64", Some(SupportedScalar::F64(0f64))) + }), + 1 => assert_member(member, "__1", |val| { + assert_scalar(val, "f64", Some(SupportedScalar::F64(1.1f64))) + }), _ => panic!("2 members expected"), }); assert_struct( - &vars[2], - "tuple_2", + tuple_2.value(), "(u64, i64, char, bool)", |i, member| match i { - 0 => assert_scalar(member, "0", "u64", Some(SupportedScalar::U64(1))), - 1 => assert_scalar(member, "1", "i64", Some(SupportedScalar::I64(-1))), - 2 => assert_scalar(member, "2", "char", Some(SupportedScalar::Char('a'))), - 3 => assert_scalar(member, "3", "bool", Some(SupportedScalar::Bool(false))), + 0 => assert_member(member, "__0", |val| { + assert_scalar(val, "u64", Some(SupportedScalar::U64(1))) + }), + 1 => assert_member(member, "__1", |val| { + assert_scalar(val, "i64", Some(SupportedScalar::I64(-1))) + }), + 2 => assert_member(member, "__2", |val| { + assert_scalar(val, "char", Some(SupportedScalar::Char('a'))) + }), + 3 => assert_member(member, "__3", |val| { + assert_scalar(val, "bool", Some(SupportedScalar::Bool(false))) + }), _ => panic!("4 members expected"), }, ); - assert_struct(&vars[3], "foo", "Foo", |i, member| match i { - 0 => assert_scalar(member, "bar", "i32", Some(SupportedScalar::I32(100))), - 1 => assert_scalar(member, "baz", "char", Some(SupportedScalar::Char('9'))), + assert_struct(foo.value(), "Foo", |i, member| match i { + 0 => assert_member(member, "bar", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(100))) + }), + 1 => assert_member(member, "baz", |val| { + assert_scalar(val, "char", Some(SupportedScalar::Char('9'))) + }), _ => panic!("2 members expected"), }); - assert_struct(&vars[4], "foo2", "Foo2", |i, member| match i { - 0 => assert_struct(member, "foo", "Foo", |i, member| match i { - 0 => assert_scalar(member, "bar", "i32", Some(SupportedScalar::I32(100))), - 1 => assert_scalar(member, "baz", "char", Some(SupportedScalar::Char('9'))), - _ => panic!("2 members expected"), + assert_struct(foo2.value(), "Foo2", |i, member| match i { + 0 => assert_member(member, "foo", |val| { + assert_struct(val, "Foo", |i, member| match i { + 0 => assert_member(member, "bar", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(100))) + }), + 1 => assert_member(member, "baz", |val| { + assert_scalar(val, "char", Some(SupportedScalar::Char('9'))) + }), + _ => panic!("2 members expected"), + }) + }), + 1 => assert_member(member, "additional", |val| { + assert_scalar(val, "bool", Some(SupportedScalar::Bool(true))) }), - 1 => assert_scalar( - member, - "additional", - "bool", - Some(SupportedScalar::Bool(true)), - ), _ => panic!("2 members expected"), }); @@ -487,38 +499,40 @@ fn test_read_array() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(61)); - let vars = debugger.read_local_variables().unwrap(); - assert_array(&vars[0], "arr_1", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(-1))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(2))), - 3 => assert_scalar(item, "3", "i32", Some(SupportedScalar::I32(-2))), - 4 => assert_scalar(item, "4", "i32", Some(SupportedScalar::I32(3))), + read_locals!(debugger => arr_1, arr_2); + assert_idents!(arr_1 => "arr_1", arr_2 => "arr_2"); + + assert_array(arr_1.value(), "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-1))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 3 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-2))), + 4 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), _ => panic!("5 items expected"), }); - assert_array(&vars[1], "arr_2", "[[i32]]", |i, item| match i { - 0 => assert_array(item, "0", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(-1))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(2))), - 3 => assert_scalar(item, "3", "i32", Some(SupportedScalar::I32(-2))), - 4 => assert_scalar(item, "4", "i32", Some(SupportedScalar::I32(3))), + assert_array(arr_2.value(), "[[i32]]", |i, item| match i { + 0 => assert_array(item, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-1))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 3 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-2))), + 4 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), _ => panic!("5 items expected"), }), - 1 => assert_array(item, "1", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(0))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(1))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(2))), - 3 => assert_scalar(item, "3", "i32", Some(SupportedScalar::I32(3))), - 4 => assert_scalar(item, "4", "i32", Some(SupportedScalar::I32(4))), + 1 => assert_array(item, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(0))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 3 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), + 4 => assert_scalar(item, "i32", Some(SupportedScalar::I32(4))), _ => panic!("5 items expected"), }), - 2 => assert_array(item, "2", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(0))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(-1))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(-2))), - 3 => assert_scalar(item, "3", "i32", Some(SupportedScalar::I32(-3))), - 4 => assert_scalar(item, "4", "i32", Some(SupportedScalar::I32(-4))), + 2 => assert_array(item, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(0))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-1))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-2))), + 3 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-3))), + 4 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-4))), _ => panic!("5 items expected"), }), _ => panic!("3 items expected"), @@ -542,55 +556,78 @@ fn test_read_enum() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(93)); - let vars = debugger.read_local_variables().unwrap(); - assert_c_enum(&vars[0], "enum_1", "EnumA", Some("B".to_string())); - assert_rust_enum(&vars[1], "enum_2", "EnumC", |enum_val| { - assert_struct(enum_val, "C", "C", |_, member| { - assert_scalar(member, "0", "char", Some(SupportedScalar::Char('b'))); + read_locals!(debugger => enum_1, enum_2, enum_3, enum_4, enum_5, enum_6, enum_7); + assert_idents!( + enum_1 => "enum_1", enum_2 => "enum_2", enum_3 => "enum_3", enum_4 => "enum_4", + enum_5 => "enum_5", enum_6 => "enum_6", enum_7 => "enum_7" + ); + + assert_c_enum(enum_1.value(), "EnumA", Some("B".to_string())); + assert_rust_enum(enum_2.value(), "EnumC", |enum_val| { + assert_struct(enum_val, "C", |_, member| { + assert_member(member, "__0", |val| { + assert_scalar(val, "char", Some(SupportedScalar::Char('b'))) + }) }); }); - assert_rust_enum(&vars[2], "enum_3", "EnumC", |enum_val| { - assert_struct(enum_val, "D", "D", |i, member| { + assert_rust_enum(enum_3.value(), "EnumC", |enum_val| { + assert_struct(enum_val, "D", |i, member| { match i { - 0 => assert_scalar(member, "0", "f64", Some(SupportedScalar::F64(1.1))), - 1 => assert_scalar(member, "1", "f32", Some(SupportedScalar::F32(1.2))), + 0 => assert_member(member, "__0", |val| { + assert_scalar(val, "f64", Some(SupportedScalar::F64(1.1))) + }), + 1 => assert_member(member, "__1", |val| { + assert_scalar(val, "f32", Some(SupportedScalar::F32(1.2))) + }), _ => panic!("2 members expected"), }; }); }); - assert_rust_enum(&vars[3], "enum_4", "EnumC", |enum_val| { - assert_struct(enum_val, "E", "E", |_, _| { + assert_rust_enum(enum_4.value(), "EnumC", |enum_val| { + assert_struct(enum_val, "E", |_, _| { panic!("expected empty struct"); }); }); - assert_rust_enum(&vars[4], "enum_5", "EnumF", |enum_val| { - assert_struct(enum_val, "F", "F", |i, member| { + assert_rust_enum(enum_5.value(), "EnumF", |enum_val| { + assert_struct(enum_val, "F", |i, member| { match i { - 0 => assert_rust_enum(member, "0", "EnumC", |enum_val| { - assert_struct(enum_val, "C", "C", |_, member| { - assert_scalar(member, "0", "char", Some(SupportedScalar::Char('f'))); - }); + 0 => assert_member(member, "__0", |val| { + assert_rust_enum(val, "EnumC", |enum_val| { + assert_struct(enum_val, "C", |_, member| { + assert_member(member, "__0", |val| { + assert_scalar(val, "char", Some(SupportedScalar::Char('f'))) + }) + }); + }) }), _ => panic!("1 members expected"), }; }); }); - assert_rust_enum(&vars[5], "enum_6", "EnumF", |enum_val| { - assert_struct(enum_val, "G", "G", |i, member| { + assert_rust_enum(enum_6.value(), "EnumF", |enum_val| { + assert_struct(enum_val, "G", |i, member| { match i { - 0 => assert_struct(member, "0", "Foo", |i, member| match i { - 0 => assert_scalar(member, "a", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(member, "b", "char", Some(SupportedScalar::Char('1'))), - _ => panic!("2 members expected"), + 0 => assert_member(member, "__0", |val| { + assert_struct(val, "Foo", |i, member| match i { + 0 => assert_member(member, "a", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(1))) + }), + 1 => assert_member(member, "b", |val| { + assert_scalar(val, "char", Some(SupportedScalar::Char('1'))) + }), + _ => panic!("2 members expected"), + }) }), _ => panic!("1 members expected"), }; }); }); - assert_rust_enum(&vars[6], "enum_7", "EnumF", |enum_val| { - assert_struct(enum_val, "J", "J", |i, member| { + assert_rust_enum(enum_7.value(), "EnumF", |enum_val| { + assert_struct(enum_val, "J", |i, member| { match i { - 0 => assert_c_enum(member, "0", "EnumA", Some("A".to_string())), + 0 => assert_member(member, "__0", |val| { + assert_c_enum(val, "EnumA", Some("A".to_string())) + }), _ => panic!("1 members expected"), }; }); @@ -600,26 +637,6 @@ fn test_read_enum() { assert_no_proc!(debugee_pid); } -fn make_select_plan(expr: &str) -> DQE { - expression::parser().parse(expr).unwrap() -} - -fn read_single_var(debugger: &Debugger, expr: &str) -> VariableIR { - debugger - .read_variable(make_select_plan(expr)) - .unwrap() - .pop() - .unwrap() -} - -fn read_single_arg(debugger: &Debugger, expr: &str) -> VariableIR { - debugger - .read_argument(make_select_plan(expr)) - .unwrap() - .pop() - .unwrap() -} - #[test] #[serial] fn test_read_pointers() { @@ -634,71 +651,77 @@ fn test_read_pointers() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(119)); - let vars = debugger.read_local_variables().unwrap(); - assert_scalar(&vars[0], "a", "i32", Some(SupportedScalar::I32(2))); - - assert_pointer(&vars[1], "ref_a", "&i32"); - let deref = read_single_var(&debugger, "*ref_a"); - assert_scalar(&deref, "*ref_a", "i32", Some(SupportedScalar::I32(2))); - - assert_pointer(&vars[2], "ptr_a", "*const i32"); - let deref = read_single_var(&debugger, "*ptr_a"); - assert_scalar(&deref, "*ptr_a", "i32", Some(SupportedScalar::I32(2))); - - assert_pointer(&vars[3], "ptr_ptr_a", "*const *const i32"); - let deref = read_single_var(&debugger, "*ptr_ptr_a"); - assert_pointer(&deref, "*ptr_ptr_a", "*const i32"); - let deref = read_single_var(&debugger, "**ptr_ptr_a"); - assert_scalar(&deref, "**ptr_ptr_a", "i32", Some(SupportedScalar::I32(2))); - - assert_scalar(&vars[4], "b", "i32", Some(SupportedScalar::I32(2))); - - assert_pointer(&vars[5], "mut_ref_b", "&mut i32"); - let deref = read_single_var(&debugger, "*mut_ref_b"); - assert_scalar(&deref, "*mut_ref_b", "i32", Some(SupportedScalar::I32(2))); - - assert_scalar(&vars[6], "c", "i32", Some(SupportedScalar::I32(2))); - - assert_pointer(&vars[7], "mut_ptr_c", "*mut i32"); - let deref = read_single_var(&debugger, "*mut_ptr_c"); - assert_scalar(&deref, "*mut_ptr_c", "i32", Some(SupportedScalar::I32(2))); + read_locals!(debugger => a, ref_a, ptr_a, ptr_ptr_a, b, mut_ref_b, c, mut_ptr_c, box_d, f, ref_f); + assert_idents!( + a => "a", ref_a => "ref_a", ptr_a => "ptr_a", ptr_ptr_a => "ptr_ptr_a", b => "b", + mut_ref_b => "mut_ref_b",c => "c", mut_ptr_c => "mut_ptr_c", box_d => "box_d", f => "f", ref_f => "ref_f" + ); + assert_scalar(a.value(), "i32", Some(SupportedScalar::I32(2))); + assert_pointer(ref_a.value(), "&i32"); + let deref = ref_a.clone().modify_value(|ctx, val| val.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); + assert_pointer(ptr_a.value(), "*const i32"); + let deref = ptr_a.clone().modify_value(|ctx, val| val.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); + assert_pointer(ptr_ptr_a.value(), "*const *const i32"); + let deref = ptr_ptr_a.clone().modify_value(|ctx, val| val.deref(ctx)); + assert_pointer(deref.unwrap().value(), "*const i32"); + let deref = ptr_ptr_a + .clone() + .modify_value(|ctx, v| v.deref(ctx).and_then(|v| v.deref(ctx))); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); + assert_scalar(b.value(), "i32", Some(SupportedScalar::I32(2))); + assert_pointer(mut_ref_b.value(), "&mut i32"); + let deref = mut_ref_b.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); + assert_scalar(c.value(), "i32", Some(SupportedScalar::I32(2))); + assert_pointer(mut_ptr_c.value(), "*mut i32"); + let deref = mut_ptr_c.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); assert_pointer( - &vars[8], - "box_d", + box_d.value(), "alloc::boxed::Box", ); - let deref = read_single_var(&debugger, "*box_d"); - assert_scalar(&deref, "*box_d", "i32", Some(SupportedScalar::I32(2))); - - assert_struct(&vars[9], "f", "Foo", |i, member| match i { - 0 => assert_scalar(member, "bar", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_array(member, "baz", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), - _ => panic!("2 items expected"), + let deref = box_d.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); + assert_struct(f.value(), "Foo", |i, member| match i { + 0 => assert_member(member, "bar", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(1))) + }), + 1 => assert_member(member, "baz", |val| { + assert_array(val, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + _ => panic!("2 items expected"), + }) }), 2 => { - assert_pointer(member, "foo", "&i32"); - let deref = read_single_var(&debugger, "*f.foo"); - assert_scalar(&deref, "*foo", "i32", Some(SupportedScalar::I32(2))); + assert_member(member, "foo", |val| assert_pointer(val, "&i32")); + let foo_val = member.value.clone(); + let deref = f.clone().modify_value(|ctx, _| foo_val.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); } _ => panic!("3 members expected"), }); - - assert_pointer(&vars[10], "ref_f", "&vars::references::Foo"); - let deref = read_single_var(&debugger, "*ref_f"); - assert_struct(&deref, "*ref_f", "Foo", |i, member| match i { - 0 => assert_scalar(member, "bar", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_array(member, "baz", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), - _ => panic!("2 items expected"), + assert_pointer(ref_f.value(), "&vars::references::Foo"); + let deref = ref_f.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_struct(deref.unwrap().value(), "Foo", |i, member| match i { + 0 => assert_member(member, "bar", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(1))) + }), + 1 => assert_member(member, "baz", |val| { + assert_array(val, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + _ => panic!("2 items expected"), + }) }), 2 => { - assert_pointer(member, "foo", "&i32"); - let deref = read_single_var(&debugger, "*(*ref_f).foo"); - assert_scalar(&deref, "*foo", "i32", Some(SupportedScalar::I32(2))); + assert_member(member, "foo", |val| assert_pointer(val, "&i32")); + let foo_val = member.value.clone(); + let deref = ref_f.clone().modify_value(|ctx, _| foo_val.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); } _ => panic!("3 members expected"), }); @@ -717,12 +740,12 @@ fn test_read_type_alias() { let mut debugger = builder.build(process).unwrap(); debugger.set_breakpoint_at_line("vars.rs", 126).unwrap(); - debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(126)); - let vars = debugger.read_local_variables().unwrap(); - assert_scalar(&vars[0], "a_alias", "i32", Some(SupportedScalar::I32(1))); + read_locals!(debugger => a_alias); + assert_idents!(a_alias => "a_alias"); + assert_scalar(a_alias.value(), "i32", Some(SupportedScalar::I32(1))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -742,9 +765,12 @@ fn test_type_parameters() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(135)); - let vars = debugger.read_local_variables().unwrap(); - assert_struct(&vars[0], "a", "Foo", |i, member| match i { - 0 => assert_scalar(member, "bar", "i32", Some(SupportedScalar::I32(1))), + read_locals!(debugger => a); + assert_idents!(a => "a"); + assert_struct(a.value(), "Foo", |i, member| match i { + 0 => assert_member(member, "bar", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(1))) + }), _ => panic!("1 members expected"), }); @@ -766,102 +792,99 @@ fn test_read_vec_and_slice() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(151)); - let vars = debugger.read_local_variables().unwrap(); - assert_vec( - &vars[0], - "vec1", - "Vec", - 3, - |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(3))), - _ => panic!("3 items expected"), - }) - }, - ); + read_locals!(debugger => vec1, vec2, vec3, slice1, slice2); + assert_idents!(vec1 => "vec1", vec2 => "vec2", vec3 => "vec3", slice1 => "slice1", slice2 => "slice2"); + + assert_vec(vec1.value(), "Vec", 3, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), + _ => panic!("3 items expected"), + }) + }); + assert_vec( - &vars[1], - "vec2", + vec2.value(), "Vec", 2, |buf| { - assert_array(buf, "buf", "[Foo]", |i, item| match i { - 0 => assert_struct(item, "0", "Foo", |i, member| match i { - 0 => assert_scalar(member, "foo", "i32", Some(SupportedScalar::I32(1))), + assert_array(buf, "[Foo]", |i, item| match i { + 0 => assert_struct(item, "Foo", |i, member| match i { + 0 => assert_member(member, "foo", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(1))) + }), _ => panic!("1 members expected"), }), - 1 => assert_struct(item, "1", "Foo", |i, member| match i { - 0 => assert_scalar(member, "foo", "i32", Some(SupportedScalar::I32(2))), + 1 => assert_struct(item, "Foo", |i, member| match i { + 0 => assert_member(member, "foo", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(2))) + }), _ => panic!("1 members expected"), }), _ => panic!("2 items expected"), }) }, ); + assert_vec( - &vars[2], - "vec3", + vec3.value(), "Vec, alloc::alloc::Global>", 2, |buf| { - assert_array( - buf, - "buf", - "[Vec]", - |i, item| match i { - 0 => assert_vec(item, "0", "Vec", 3, |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(3))), - _ => panic!("3 items expected"), - }) - }), - 1 => assert_vec(item, "1", "Vec", 3, |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(3))), - _ => panic!("3 items expected"), - }) - }), - _ => panic!("2 items expected"), - }, - ) + assert_array(buf, "[Vec]", |i, item| match i { + 0 => assert_vec(item, "Vec", 3, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), + _ => panic!("3 items expected"), + }) + }), + 1 => assert_vec(item, "Vec", 3, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), + _ => panic!("3 items expected"), + }) + }), + _ => panic!("2 items expected"), + }) }, ); - assert_pointer(&vars[3], "slice1", "&[i32; 3]"); - let deref = read_single_var(&debugger, "*slice1"); - assert_array(&deref, "*slice1", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(3))), + assert_pointer(slice1.value(), "&[i32; 3]"); + let deref = slice1.clone().modify_value(|ctx, val| val.deref(ctx)); + assert_array(deref.unwrap().value(), "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), _ => panic!("3 items expected"), }); - assert_pointer(&vars[4], "slice2", "&[&[i32; 3]; 2]"); - let deref = read_single_var(&debugger, "*slice2"); - assert_array(&deref, "*slice2", "[&[i32; 3]]", |i, item| match i { + assert_pointer(slice2.value(), "&[&[i32; 3]; 2]"); + let deref = slice2.clone().modify_value(|ctx, val| val.deref(ctx)); + assert_array(deref.unwrap().value(), "[&[i32; 3]]", |i, item| match i { 0 => { - assert_pointer(item, "0", "&[i32; 3]"); - let deref = read_single_var(&debugger, "*(*slice2)[0]"); - assert_array(&deref, "*0", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(3))), + assert_pointer(item, "&[i32; 3]"); + let item_val = item.clone(); + let deref = slice2.clone().modify_value(|ctx, _| item_val.deref(ctx)); + assert_array(deref.unwrap().value(), "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), _ => panic!("3 items expected"), }); } 1 => { - assert_pointer(item, "1", "&[i32; 3]"); - let deref = read_single_var(&debugger, "*(*slice2)[1]"); - assert_array(&deref, "*1", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(3))), + assert_pointer(item, "&[i32; 3]"); + let item_val = item.clone(); + let deref = slice2.clone().modify_value(|ctx, _| item_val.deref(ctx)); + assert_array(deref.unwrap().value(), "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), _ => panic!("3 items expected"), }); } @@ -886,10 +909,12 @@ fn test_read_strings() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(159)); - let vars = debugger.read_local_variables().unwrap(); - assert_string(&vars[0], "s1", "hello world"); - assert_str(&vars[1], "s2", "hello world"); - assert_str(&vars[2], "s3", "hello world"); + read_locals!(debugger => s1, s2, s3); + assert_idents!(s1 => "s1", s2 => "s2", s3 => "s3"); + + assert_string(s1.value(), "hello world"); + assert_str(s2.value(), "hello world"); + assert_str(s3.value(), "hello world"); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -909,28 +934,13 @@ fn test_read_static_variables() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(168)); - let vars = debugger - .read_variable(DQE::Variable(VariableSelector::Name { - var_name: "GLOB_1".to_string(), - only_local: false, - })) - .unwrap(); - assert_eq!(vars.len(), 1); - assert_str(&vars[0], "vars::GLOB_1", "glob_1"); + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name("GLOB_1", false)) => glob_1); + assert_idents!(glob_1 => "vars::GLOB_1"); + assert_str(glob_1.value(), "glob_1"); - let vars = debugger - .read_variable(DQE::Variable(VariableSelector::Name { - var_name: "GLOB_2".to_string(), - only_local: false, - })) - .unwrap(); - assert_eq!(vars.len(), 1); - assert_scalar( - &vars[0], - "vars::GLOB_2", - "i32", - Some(SupportedScalar::I32(2)), - ); + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name("GLOB_2", false)) => glob_2); + assert_idents!(glob_2 => "vars::GLOB_2"); + assert_scalar(glob_2.value(), "i32", Some(SupportedScalar::I32(2))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -951,10 +961,7 @@ fn test_read_only_local_variables() { assert_eq!(info.line.take(), Some(168)); let vars = debugger - .read_variable(DQE::Variable(VariableSelector::Name { - var_name: "GLOB_1".to_string(), - only_local: true, - })) + .read_variable(Dqe::Variable(Selector::by_name("GLOB_1", true))) .unwrap(); assert_eq!(vars.len(), 0); @@ -976,22 +983,12 @@ fn test_read_static_variables_different_modules() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(179)); - let mut vars = debugger - .read_variable(DQE::Variable(VariableSelector::Name { - var_name: "GLOB_3".to_string(), - only_local: false, - })) - .unwrap(); - assert_eq!(vars.len(), 2); - vars.sort_by(|v1, v2| v1.r#type().cmp(v2.r#type())); - - assert_str(&vars[0], "vars::ns_1::GLOB_3", "glob_3"); - assert_scalar( - &vars[1], - "vars::GLOB_3", - "i32", - Some(SupportedScalar::I32(3)), - ); + read_var_dqe_type_order!(debugger, Dqe::Variable(Selector::by_name("GLOB_3", false)) => glob_3_1, glob_3_2); + assert_idents!(glob_3_1 => "vars::ns_1::GLOB_3"); + assert_str(glob_3_1.value(), "glob_3"); + + assert_idents!(glob_3_2 => "vars::GLOB_3"); + assert_scalar(glob_3_2.value(), "i32", Some(SupportedScalar::I32(3))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -1005,34 +1002,48 @@ fn test_read_tls_variables() { let info = TestInfo::default(); let builder = DebuggerBuilder::new().with_hooks(TestHooks::new(info.clone())); let mut debugger = builder.build(process).unwrap(); + let rust_version = rust_version(VARS_APP).unwrap(); debugger.set_breakpoint_at_line("vars.rs", 194).unwrap(); debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(194)); - let vars = debugger - .read_variable(DQE::Variable(VariableSelector::Name { - var_name: "THREAD_LOCAL_VAR_1".to_string(), - only_local: false, - })) - .unwrap(); - assert_init_tls(&vars[0], "THREAD_LOCAL_VAR_1", "Cell", |inner| { - assert_cell(inner, "0", "Cell", |value| { - assert_scalar(value, "value", "i32", Some(SupportedScalar::I32(2))) + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name( + "THREAD_LOCAL_VAR_1", + false, + )) => tls_var_1); + + version_switch!( + rust_version, + (1, 0, 0) ..= (1, 79, u32::MAX) => { + assert_idents!(tls_var_1 => "vars::THREAD_LOCAL_VAR_1::__getit::__KEY"); + }, + (1, 80, 0) ..= (1, u32::MAX, u32::MAX) => { + assert_idents!(tls_var_1 => "vars::THREAD_LOCAL_VAR_1::{constant#0}::{closure#1}::VAL"); + } + ); + assert_init_tls(tls_var_1.value(), "Cell", |inner| { + assert_cell(inner, "Cell", |value| { + assert_scalar(value, "i32", Some(SupportedScalar::I32(2))) }) }); - let vars = debugger - .read_variable(DQE::Variable(VariableSelector::Name { - var_name: "THREAD_LOCAL_VAR_2".to_string(), - only_local: false, - })) - .unwrap(); - assert_init_tls(&vars[0], "THREAD_LOCAL_VAR_2", "Cell<&str>", |inner| { - assert_cell(inner, "0", "Cell<&str>", |value| { - assert_str(value, "value", "2") - }) + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name( + "THREAD_LOCAL_VAR_2", + false, + )) => tls_var_2); + version_switch!( + rust_version, + (1, 0, 0) ..= (1, 79, u32::MAX) => { + assert_idents!(tls_var_2 => "vars::THREAD_LOCAL_VAR_2::__getit::__KEY"); + }, + (1, 80, 0) ..= (1, u32::MAX, u32::MAX) => { + assert_idents!(tls_var_2 => "vars::THREAD_LOCAL_VAR_2::{constant#0}::{closure#1}::VAL"); + } + ); + assert_init_tls(tls_var_2.value(), "Cell<&str>", |inner| { + assert_cell(inner, "Cell<&str>", |value| assert_str(value, "2")) }); // assert uninit tls variables @@ -1040,19 +1051,21 @@ fn test_read_tls_variables() { debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(199)); - let vars = debugger - .read_variable(DQE::Variable(VariableSelector::Name { - var_name: "THREAD_LOCAL_VAR_1".to_string(), - only_local: false, - })) - .unwrap(); - let rust_version = rust_version(VARS_APP).unwrap(); version_switch!( rust_version, (1, 0, 0) ..= (1, 79, u32::MAX) => { - assert_uninit_tls(&vars[0], "THREAD_LOCAL_VAR_1", "Cell"); + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name( + "THREAD_LOCAL_VAR_1", + false, + )) => tls_var_1); + assert_idents!(tls_var_1 => "vars::THREAD_LOCAL_VAR_1::__getit::__KEY"); + assert_uninit_tls(tls_var_1.value(), "Cell"); }, (1, 80, 0) ..= (1, u32::MAX, u32::MAX) => { + let vars = debugger.read_variable(Dqe::Variable(Selector::by_name( + "THREAD_LOCAL_VAR_1", + false, + ))).unwrap(); assert!(vars.is_empty()); }, ); @@ -1062,15 +1075,13 @@ fn test_read_tls_variables() { debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(203)); - let vars = debugger - .read_variable(DQE::Variable(VariableSelector::Name { - var_name: "THREAD_LOCAL_VAR_1".to_string(), - only_local: false, - })) - .unwrap(); - assert_init_tls(&vars[0], "THREAD_LOCAL_VAR_1", "Cell", |inner| { - assert_cell(inner, "0", "Cell", |value| { - assert_scalar(value, "value", "i32", Some(SupportedScalar::I32(1))) + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name( + "THREAD_LOCAL_VAR_1", + false, + )) => tls_var_1); + assert_init_tls(tls_var_1.value(), "Cell", |inner| { + assert_cell(inner, "Cell", |value| { + assert_scalar(value, "i32", Some(SupportedScalar::I32(1))) }) }); @@ -1078,6 +1089,46 @@ fn test_read_tls_variables() { assert_no_proc!(debugee_pid); } +#[test] +#[serial] +fn test_read_tls_const_variables() { + let rust_version = rust_version(VARS_APP).unwrap(); + if rust_version < Version((1, 79, 0)) { + return; + } + + let process = prepare_debugee_process(VARS_APP, &[]); + let debugee_pid = process.pid(); + let info = TestInfo::default(); + let builder = DebuggerBuilder::new().with_hooks(TestHooks::new(info.clone())); + let mut debugger = builder.build(process).unwrap(); + + debugger.set_breakpoint_at_line("vars.rs", 538).unwrap(); + + debugger.start_debugee().unwrap(); + assert_eq!(info.line.take(), Some(538)); + + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name( + "CONSTANT_THREAD_LOCAL", + false, + )) => const_tls); + version_switch!( + rust_version, + (1, 0, 0) ..= (1, 79, u32::MAX) => { + assert_idents!(const_tls => "vars::thread_local_const_init::CONSTANT_THREAD_LOCAL::__getit::VAL"); + }, + (1, 80, 0) ..= (1, u32::MAX, u32::MAX) => { + assert_idents!(const_tls => "vars::thread_local_const_init::CONSTANT_THREAD_LOCAL::{constant#0}::{closure#0}::VAL"); + } + ); + assert_init_tls(const_tls.value(), "i32", |value| { + assert_scalar(value, "i32", Some(SupportedScalar::I32(1337))) + }); + + debugger.continue_debugee().unwrap(); + assert_no_proc!(debugee_pid); +} + #[test] #[serial] fn test_read_closures() { @@ -1092,33 +1143,36 @@ fn test_read_closures() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(223)); - let vars = debugger.read_local_variables().unwrap(); - assert_struct(&vars[0], "inc", "{closure_env#0}", |_, _| { + read_locals!(debugger => inc, inc_mut, _outer, closure, _a, _b, _c, trait_once, trait_mut, trait_fn, fn_ptr); + assert_idents!( + inc => "inc", inc_mut => "inc_mut", closure => "closure", trait_once => "trait_once", + trait_mut => "trait_mut", trait_fn => "trait_fn", fn_ptr => "fn_ptr" + ); + + assert_struct(inc.value(), "{closure_env#0}", |_, _| { panic!("no members expected") }); - assert_struct(&vars[1], "inc_mut", "{closure_env#1}", |_, _| { + assert_struct(inc_mut.value(), "{closure_env#1}", |_, _| { panic!("no members expected") }); - assert_struct(&vars[3], "closure", "{closure_env#2}", |_, member| { - assert_string(member, "outer", "outer val") + assert_struct(closure.value(), "{closure_env#2}", |_, member| { + assert_member(member, "outer", |val| assert_string(val, "outer val")) }); - let rust_version = rust_version(VARS_APP).unwrap(); assert_struct( - &vars[7], - "trait_once", + trait_once.value(), "alloc::boxed::Box, alloc::alloc::Global>", |i, member| match i { 0 => { - assert_pointer( - member, - "pointer", - "*dyn core::ops::function::FnOnce<(), Output=()>", - ); - let deref = read_single_var(&debugger, "*trait_once.pointer"); + assert_member(member, "pointer", |val| { + assert_pointer(val, "*dyn core::ops::function::FnOnce<(), Output=()>") + }); + let member_val = member.value.clone(); + let deref = trait_once + .clone() + .modify_value(|ctx, _| member_val.deref(ctx)); assert_struct( - &deref, - "*pointer", + deref.unwrap().value(), "dyn core::ops::function::FnOnce<(), Output=()>", |_, _| {}, ); @@ -1129,29 +1183,30 @@ fn test_read_closures() { } else { "&[usize; 3]" }; - - assert_pointer(member, "vtable", exp_type); - let deref = read_single_var(&debugger, "*trait_once.vtable"); - assert_array(&deref, "*vtable", "[usize]", |_, _| {}); + assert_member(member, "vtable", |val| assert_pointer(val, exp_type)); + let member_val = member.value.clone(); + let deref = trait_once + .clone() + .modify_value(|ctx, _| member_val.deref(ctx)); + assert_array(deref.unwrap().value(), "[usize]", |_, _| {}); } _ => panic!("2 members expected"), }, ); assert_struct( - &vars[8], - "trait_mut", + trait_mut.value(), "alloc::boxed::Box, alloc::alloc::Global>", |i, member| match i { 0 => { - assert_pointer( - member, - "pointer", - "*dyn core::ops::function::FnMut<(), Output=()>", - ); - let deref = read_single_var(&debugger, "*trait_mut.pointer"); + assert_member(member, "pointer", |val| { + assert_pointer(val, "*dyn core::ops::function::FnMut<(), Output=()>") + }); + let member_val = member.value.clone(); + let deref = trait_mut + .clone() + .modify_value(|ctx, _| member_val.deref(ctx)); assert_struct( - &deref, - "*pointer", + deref.unwrap().value(), "dyn core::ops::function::FnMut<(), Output=()>", |_, _| {}, ); @@ -1162,28 +1217,30 @@ fn test_read_closures() { } else { "&[usize; 3]" }; - assert_pointer(member, "vtable", exp_type); - let deref = read_single_var(&debugger, "*trait_mut.vtable"); - assert_array(&deref, "*vtable", "[usize]", |_, _| {}); + assert_member(member, "vtable", |val| assert_pointer(val, exp_type)); + let member_val = member.value.clone(); + let deref = trait_mut + .clone() + .modify_value(|ctx, _| member_val.deref(ctx)); + assert_array(deref.unwrap().value(), "[usize]", |_, _| {}); } _ => panic!("2 members expected"), }, ); assert_struct( - &vars[9], - "trait_fn", + trait_fn.value(), "alloc::boxed::Box, alloc::alloc::Global>", |i, member| match i { 0 => { - assert_pointer( - member, - "pointer", - "*dyn core::ops::function::Fn<(), Output=()>", - ); - let deref = read_single_var(&debugger, "*trait_fn.pointer"); + assert_member(member, "pointer", |val| { + assert_pointer(val, "*dyn core::ops::function::Fn<(), Output=()>") + }); + let member_val = member.value.clone(); + let deref = trait_fn + .clone() + .modify_value(|ctx, _| member_val.deref(ctx)); assert_struct( - &deref, - "*pointer", + deref.unwrap().value(), "dyn core::ops::function::Fn<(), Output=()>", |_, _| {}, ); @@ -1194,14 +1251,17 @@ fn test_read_closures() { } else { "&[usize; 3]" }; - assert_pointer(member, "vtable", exp_type); - let deref = read_single_var(&debugger, "*trait_fn.vtable"); - assert_array(&deref, "*vtable", "[usize]", |_, _| {}); + assert_member(member, "vtable", |val| assert_pointer(val, exp_type)); + let member_val = member.value.clone(); + let deref = trait_fn + .clone() + .modify_value(|ctx, _| member_val.deref(ctx)); + assert_array(deref.unwrap().value(), "[usize]", |_, _| {}); } _ => panic!("2 members expected"), }, ); - assert_pointer(&vars[10], "fn_ptr", "fn() -> u8"); + assert_pointer(fn_ptr.value(), "fn() -> u8"); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -1221,33 +1281,39 @@ fn test_arguments() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(232)); - let args = debugger - .read_argument(select::DQE::Variable(VariableSelector::Any)) - .unwrap(); - assert_scalar(&args[0], "by_val", "i32", Some(SupportedScalar::I32(1))); - assert_pointer(&args[1], "by_ref", "&i32"); - let deref = read_single_arg(&debugger, "*by_ref"); - assert_scalar(&deref, "*by_ref", "i32", Some(SupportedScalar::I32(2))); - - assert_vec(&args[2], "vec", "Vec", 3, |buf| { - assert_array(buf, "buf", "[u8]", |i, item| match i { - 0 => assert_scalar(item, "0", "u8", Some(SupportedScalar::U8(3))), - 1 => assert_scalar(item, "1", "u8", Some(SupportedScalar::U8(4))), - 2 => assert_scalar(item, "2", "u8", Some(SupportedScalar::U8(5))), + read_arg_dqe!(debugger, Dqe::Variable(Selector::Any) => by_val, by_ref, vec, box_arr); + assert_idents!(by_val => "by_val", by_ref => "by_ref", vec => "vec", box_arr => "box_arr"); + + assert_scalar(by_val.value(), "i32", Some(SupportedScalar::I32(1))); + + assert_pointer(by_ref.value(), "&i32"); + let deref = by_ref.clone().modify_value(|ctx, value| value.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); + + assert_vec(vec.value(), "Vec", 3, |buf| { + assert_array(buf, "[u8]", |i, item| match i { + 0 => assert_scalar(item, "u8", Some(SupportedScalar::U8(3))), + 1 => assert_scalar(item, "u8", Some(SupportedScalar::U8(4))), + 2 => assert_scalar(item, "u8", Some(SupportedScalar::U8(5))), _ => panic!("3 items expected"), }) }); + assert_struct( - &args[3], - "box_arr", + box_arr.value(), "alloc::boxed::Box<[u8], alloc::alloc::Global>", |i, member| match i { 0 => { - assert_pointer(member, "data_ptr", "*u8"); - let deref = read_single_arg(&debugger, "*box_arr.data_ptr"); - assert_scalar(&deref, "*data_ptr", "u8", Some(SupportedScalar::U8(6))); + assert_member(member, "data_ptr", |val| assert_pointer(val, "*u8")); + let data_ptr_val = member.value.clone(); + let deref = box_arr + .clone() + .modify_value(|ctx, _| data_ptr_val.deref(ctx)); + assert_scalar(deref.unwrap().value(), "u8", Some(SupportedScalar::U8(6))); } - 1 => assert_scalar(member, "length", "usize", Some(SupportedScalar::Usize(3))), + 1 => assert_member(member, "length", |val| { + assert_scalar(val, "usize", Some(SupportedScalar::Usize(3))) + }), _ => panic!("2 members expected"), }, ); @@ -1270,9 +1336,12 @@ fn test_read_union() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(244)); - let vars = debugger.read_local_variables().unwrap(); - assert_struct(&vars[0], "union", "Union1", |i, member| match i { - 0 => assert_scalar(member, "f1", "f32", Some(SupportedScalar::F32(1.1))), + read_locals!(debugger => union); + assert_idents!(union => "union"); + assert_struct(union.value(), "Union1", |i, member| match i { + 0 => assert_member(member, "f1", |val| { + assert_scalar(val, "f32", Some(SupportedScalar::F32(1.1))) + }), 1 => {} 2 => {} _ => panic!("3 members expected"), @@ -1303,13 +1372,15 @@ fn test_read_hashmap() { (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashMap", ).unwrap(); - let vars = debugger.read_local_variables().unwrap(); - assert_hashmap(&vars[0], "hm1", hash_map_type, |items| { + read_locals!(debugger => hm1, hm2, hm3, hm4, _a, b, _hm5, _hm6); + assert_idents!(hm1 => "hm1", hm2 => "hm2", hm3 => "hm3", hm4 => "hm4"); + + assert_hashmap(hm1.value(), hash_map_type, |items| { assert_eq!(items.len(), 2); - assert_scalar(&items[0].0, "0", "bool", Some(SupportedScalar::Bool(false))); - assert_scalar(&items[0].1, "1", "i64", Some(SupportedScalar::I64(5))); - assert_scalar(&items[1].0, "0", "bool", Some(SupportedScalar::Bool(true))); - assert_scalar(&items[1].1, "1", "i64", Some(SupportedScalar::I64(3))); + assert_scalar(&items[0].0, "bool", Some(SupportedScalar::Bool(false))); + assert_scalar(&items[0].1, "i64", Some(SupportedScalar::I64(5))); + assert_scalar(&items[1].0, "bool", Some(SupportedScalar::Bool(true))); + assert_scalar(&items[1].1, "i64", Some(SupportedScalar::I64(3))); }); let hash_map_type = version_switch!( @@ -1317,38 +1388,26 @@ fn test_read_hashmap() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashMap<&str, alloc::vec::Vec, std::collections::hash::map::RandomState>", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashMap<&str, alloc::vec::Vec, std::hash::random::RandomState>", ).unwrap(); - assert_hashmap(&vars[1], "hm2", hash_map_type, |items| { + assert_hashmap(hm2.value(), hash_map_type, |items| { assert_eq!(items.len(), 2); - assert_str(&items[0].0, "0", "abc"); - assert_vec( - &items[0].1, - "1", - "Vec", - 3, - |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(3))), - _ => panic!("3 items expected"), - }) - }, - ); - assert_str(&items[1].0, "0", "efg"); - assert_vec( - &items[1].1, - "1", - "Vec", - 3, - |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(11))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(12))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(13))), - _ => panic!("3 items expected"), - }) - }, - ); + assert_str(&items[0].0, "abc"); + assert_vec(&items[0].1, "Vec", 3, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), + _ => panic!("3 items expected"), + }) + }); + assert_str(&items[1].0, "efg"); + assert_vec(&items[1].1, "Vec", 3, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(11))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(12))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(13))), + _ => panic!("3 items expected"), + }) + }); }); let hash_map_type = version_switch!( @@ -1356,27 +1415,17 @@ fn test_read_hashmap() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashMap", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashMap", ).unwrap(); - assert_hashmap(&vars[2], "hm3", hash_map_type, |items| { + assert_hashmap(hm3.value(), hash_map_type, |items| { assert_eq!(items.len(), 100); let mut exp_items = (0..100).collect::>(); exp_items.sort_by_key(|i1| i1.to_string()); for i in 0..100 { - assert_scalar( - &items[i].0, - "0", - "i32", - Some(SupportedScalar::I32(exp_items[i])), - ); + assert_scalar(&items[i].0, "i32", Some(SupportedScalar::I32(exp_items[i]))); } for i in 0..100 { - assert_scalar( - &items[i].1, - "1", - "i32", - Some(SupportedScalar::I32(exp_items[i])), - ); + assert_scalar(&items[i].1, "i32", Some(SupportedScalar::I32(exp_items[i]))); } }); @@ -1390,85 +1439,62 @@ fn test_read_hashmap() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashMap", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashMap", ).unwrap(); - - assert_hashmap(&vars[3], "hm4", hash_map_type, |items| { + assert_hashmap(hm4.value(), hash_map_type, |items| { assert_eq!(items.len(), 2); - assert_string(&items[0].0, "0", "1"); - assert_hashmap(&items[0].1, "1", inner_hash_map_type, |items| { + assert_string(&items[0].0, "1"); + assert_hashmap(&items[0].1, inner_hash_map_type, |items| { assert_eq!(items.len(), 2); - assert_scalar(&items[0].0, "0", "i32", Some(SupportedScalar::I32(1))); - assert_scalar(&items[0].1, "1", "i32", Some(SupportedScalar::I32(1))); - assert_scalar(&items[1].0, "0", "i32", Some(SupportedScalar::I32(2))); - assert_scalar(&items[1].1, "1", "i32", Some(SupportedScalar::I32(2))); + assert_scalar(&items[0].0, "i32", Some(SupportedScalar::I32(1))); + assert_scalar(&items[0].1, "i32", Some(SupportedScalar::I32(1))); + assert_scalar(&items[1].0, "i32", Some(SupportedScalar::I32(2))); + assert_scalar(&items[1].1, "i32", Some(SupportedScalar::I32(2))); }); - assert_string(&items[1].0, "0", "3"); - assert_hashmap(&items[1].1, "1", inner_hash_map_type, |items| { + assert_string(&items[1].0, "3"); + assert_hashmap(&items[1].1, inner_hash_map_type, |items| { assert_eq!(items.len(), 2); - assert_scalar(&items[0].0, "0", "i32", Some(SupportedScalar::I32(3))); - assert_scalar(&items[0].1, "1", "i32", Some(SupportedScalar::I32(3))); - assert_scalar(&items[1].0, "0", "i32", Some(SupportedScalar::I32(4))); - assert_scalar(&items[1].1, "1", "i32", Some(SupportedScalar::I32(4))); + assert_scalar(&items[0].0, "i32", Some(SupportedScalar::I32(3))); + assert_scalar(&items[0].1, "i32", Some(SupportedScalar::I32(3))); + assert_scalar(&items[1].0, "i32", Some(SupportedScalar::I32(4))); + assert_scalar(&items[1].1, "i32", Some(SupportedScalar::I32(4))); }); }); let make_idx_dqe = |var: &str, literal| { - DQE::Index( - DQE::Variable(VariableSelector::Name { - var_name: var.to_string(), - only_local: true, - }) - .boxed(), - literal, - ) + Dqe::Index(Dqe::Variable(Selector::by_name(var, true)).boxed(), literal) }; // get by bool key let dqe = make_idx_dqe("hm1", Literal::Bool(true)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "value", "i64", Some(SupportedScalar::I64(3))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "i64", Some(SupportedScalar::I64(3))); // get by string key let dqe = make_idx_dqe("hm2", Literal::String("efg".to_string())); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_vec(val, "value", "Vec", 3, |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(11))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(12))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(13))), + read_var_dqe!(debugger, dqe => val); + assert_vec(val.value(), "Vec", 3, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(11))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(12))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(13))), _ => panic!("3 items expected"), }) }); // get by int key let dqe = make_idx_dqe("hm3", Literal::Int(99)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "value", "i32", Some(SupportedScalar::I32(99))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "i32", Some(SupportedScalar::I32(99))); // get by pointer key - let key = debugger - .read_variable(DQE::Variable(VariableSelector::Name { - var_name: "b".to_string(), - only_local: true, - })) - .unwrap(); - assert_eq!(key.len(), 1); - let VariableIR::Pointer(ptr) = &key[0] else { + let Value::Pointer(ptr) = &b.value() else { panic!("not a pointer") }; let ptr_val = ptr.value.unwrap() as usize; let dqe = make_idx_dqe("hm5", Literal::Address(ptr_val)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_str(val, "value", "b"); + read_var_dqe!(debugger, dqe => val); + assert_str(val.value(), "b"); // get by complex object let dqe = make_idx_dqe( @@ -1496,10 +1522,8 @@ fn test_read_hashmap() { ), ])), ); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "value", "i32", Some(SupportedScalar::I32(1))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "i32", Some(SupportedScalar::I32(1))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -1526,26 +1550,23 @@ fn test_read_hashset() { (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashSet", ).unwrap(); - let vars = debugger.read_local_variables().unwrap(); - assert_hashset(&vars[0], "hs1", hashset_type, |items| { + read_locals!(debugger => hs1, hs2, hs3, _a, b, _hs4); + assert_idents!(hs1 => "hs1", hs2 => "hs2", hs3 => "hs3"); + + assert_hashset(hs1.value(), hashset_type, |items| { assert_eq!(items.len(), 4); - assert_scalar(&items[0], "0", "i32", Some(SupportedScalar::I32(1))); - assert_scalar(&items[1], "0", "i32", Some(SupportedScalar::I32(2))); - assert_scalar(&items[2], "0", "i32", Some(SupportedScalar::I32(3))); - assert_scalar(&items[3], "0", "i32", Some(SupportedScalar::I32(4))); + assert_scalar(&items[0], "i32", Some(SupportedScalar::I32(1))); + assert_scalar(&items[1], "i32", Some(SupportedScalar::I32(2))); + assert_scalar(&items[2], "i32", Some(SupportedScalar::I32(3))); + assert_scalar(&items[3], "i32", Some(SupportedScalar::I32(4))); }); - assert_hashset(&vars[1], "hs2", hashset_type, |items| { + assert_hashset(hs2.value(), hashset_type, |items| { assert_eq!(items.len(), 100); let mut exp_items = (0..100).collect::>(); exp_items.sort_by_key(|i1| i1.to_string()); for i in 0..100 { - assert_scalar( - &items[i], - "0", - "i32", - Some(SupportedScalar::I32(exp_items[i])), - ); + assert_scalar(&items[i], "i32", Some(SupportedScalar::I32(exp_items[i]))); } }); @@ -1554,65 +1575,43 @@ fn test_read_hashset() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashSet, std::collections::hash::map::RandomState>", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashSet, std::hash::random::RandomState>", ).unwrap(); - assert_hashset(&vars[2], "hs3", hashset_type, |items| { + assert_hashset(hs3.value(), hashset_type, |items| { assert_eq!(items.len(), 1); - assert_vec(&items[0], "0", "Vec", 2, |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), + assert_vec(&items[0], "Vec", 2, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), _ => panic!("2 items expected"), }) }); }); let make_idx_dqe = |var: &str, literal| { - DQE::Index( - DQE::Variable(VariableSelector::Name { - var_name: var.to_string(), - only_local: true, - }) - .boxed(), - literal, - ) + Dqe::Index(Dqe::Variable(Selector::by_name(var, true)).boxed(), literal) }; // get by int key let dqe = make_idx_dqe("hs1", Literal::Int(2)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "contains", "bool", Some(SupportedScalar::Bool(true))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(true))); let dqe = make_idx_dqe("hs1", Literal::Int(5)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "contains", "bool", Some(SupportedScalar::Bool(false))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(false))); // get by pointer key - let key = debugger - .read_variable(DQE::Variable(VariableSelector::Name { - var_name: "b".to_string(), - only_local: true, - })) - .unwrap(); - assert_eq!(key.len(), 1); - let VariableIR::Pointer(ptr) = &key[0] else { + let Value::Pointer(ptr) = &b.value() else { panic!("not a pointer") }; let ptr_val = ptr.value.unwrap() as usize; let dqe = make_idx_dqe("hs4", Literal::Address(ptr_val)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "contains", "bool", Some(SupportedScalar::Bool(true))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(true))); let dqe = make_idx_dqe("hs4", Literal::Address(0)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "contains", "bool", Some(SupportedScalar::Bool(false))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(false))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -1632,49 +1631,56 @@ fn test_circular_ref_types() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(334)); - let vars = debugger.read_local_variables().unwrap(); + read_locals!(debugger => a_circ, b_circ); + assert_idents!(a_circ => "a_circ", b_circ => "b_circ"); + assert_rc( - &vars[0], - "a_circ", + a_circ.value(), "Rc", ); assert_rc( - &vars[1], - "b_circ", + b_circ.value(), "Rc", ); - let deref = read_single_var(&debugger, "*a_circ"); + let deref = a_circ.clone().modify_value(|ctx, v| v.deref(ctx)); assert_struct( - &deref, - "*a_circ", + deref.unwrap().value(), "RcBox", |i, member| match i { - 0 => assert_cell(member, "strong", "Cell", |inner| { - assert_scalar(inner, "value", "usize", Some(SupportedScalar::Usize(2))) + 0 => assert_member(member, "strong", |val| { + assert_cell(val, "Cell", |inner| { + assert_scalar(inner, "usize", Some(SupportedScalar::Usize(2))) + }) }), - 1 => assert_cell(member, "weak", "Cell", |inner| { - assert_scalar(inner, "value", "usize", Some(SupportedScalar::Usize(1))) + 1 => assert_member(member, "weak", |val| { + assert_cell(val, "Cell", |inner| { + assert_scalar(inner, "usize", Some(SupportedScalar::Usize(1))) + }) }), 2 => { - assert_rust_enum(member, "value", "List", |enum_member| { - assert_struct(enum_member, "Cons", "Cons", |i, cons_member| match i { - 0 => assert_scalar(cons_member, "0", "i32", Some(SupportedScalar::I32(5))), - 1 => assert_refcell( - cons_member, - "1", + assert_member(member, "value", |val| { + assert_rust_enum(val, "List", |enum_member| { + assert_struct(enum_member, "Cons", |i, cons_member| match i { + 0 => assert_member(cons_member, "__0", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(5))) + }), + 1 => assert_member(cons_member, "__1", |val| { + assert_refcell( + val, "RefCell>", 0, |inner| { assert_rc( inner, - "value", "Rc", ) }, - ), - _ => panic!("2 members expected"), - }); + ) + }), + _ => panic!("2 members expected"), + }); + }) }); } _ => panic!("3 members expected"), @@ -1698,40 +1704,32 @@ fn test_lexical_blocks() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(340)); - let vars = debugger.read_local_variables().unwrap(); + read_locals!(debugger => alpha, _beta); // WAITFORFIX: https://github.com/rust-lang/rust/issues/113819 // expected: assert_eq!(vars.len(), 1); // through this bug there is uninitialized variable here - assert_eq!(vars.len(), 2); - assert_eq!(vars[0].name(), "alpha"); + assert_idents!(alpha => "alpha"); debugger.set_breakpoint_at_line("vars.rs", 342).unwrap(); debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(342)); - let vars = debugger.read_local_variables().unwrap(); - assert_eq!(vars.len(), 2); - assert_eq!(vars[0].name(), "alpha"); - assert_eq!(vars[1].name(), "beta"); + read_locals!(debugger => alpha, beta); + assert_idents!(alpha => "alpha", beta => "beta"); debugger.set_breakpoint_at_line("vars.rs", 343).unwrap(); debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(343)); - let vars = debugger.read_local_variables().unwrap(); - assert_eq!(vars.len(), 3); - assert_eq!(vars[0].name(), "alpha"); - assert_eq!(vars[1].name(), "beta"); - assert_eq!(vars[2].name(), "gama"); + read_locals!(debugger => alpha, beta, gama); + assert_idents!(alpha => "alpha", beta => "beta", gama => "gama"); debugger.set_breakpoint_at_line("vars.rs", 349).unwrap(); debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(349)); - let vars = debugger.read_local_variables().unwrap(); - assert_eq!(vars.len(), 2); - assert_eq!(vars[0].name(), "alpha"); - assert_eq!(vars[1].name(), "delta"); + read_locals!(debugger => alpha, delta); + assert_idents!(alpha => "alpha", delta => "delta"); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -1750,60 +1748,49 @@ fn test_btree_map() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(396)); - let vars = debugger.read_local_variables().unwrap(); + read_locals!(debugger => hm1, hm2, hm3, hm4, _a, b, _hm5, _hm6); + assert_idents!(hm1 => "hm1", hm2 => "hm2", hm3 => "hm3", hm4 => "hm4"); + assert_btree_map( - &vars[0], - "hm1", + hm1.value(), "BTreeMap", |items| { assert_eq!(items.len(), 2); - assert_scalar(&items[0].0, "k", "bool", Some(SupportedScalar::Bool(false))); - assert_scalar(&items[0].1, "v", "i64", Some(SupportedScalar::I64(5))); - assert_scalar(&items[1].0, "k", "bool", Some(SupportedScalar::Bool(true))); - assert_scalar(&items[1].1, "v", "i64", Some(SupportedScalar::I64(3))); + assert_scalar(&items[0].0, "bool", Some(SupportedScalar::Bool(false))); + assert_scalar(&items[0].1, "i64", Some(SupportedScalar::I64(5))); + assert_scalar(&items[1].0, "bool", Some(SupportedScalar::Bool(true))); + assert_scalar(&items[1].1, "i64", Some(SupportedScalar::I64(3))); }, ); + assert_btree_map( - &vars[1], - "hm2", + hm2.value(), "BTreeMap<&str, alloc::vec::Vec, alloc::alloc::Global>", |items| { assert_eq!(items.len(), 2); - assert_str(&items[0].0, "k", "abc"); - assert_vec( - &items[0].1, - "v", - "Vec", - 3, - |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(3))), - _ => panic!("3 items expected"), - }) - }, - ); - assert_str(&items[1].0, "k", "efg"); - assert_vec( - &items[1].1, - "v", - "Vec", - 3, - |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(11))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(12))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(13))), - _ => panic!("3 items expected"), - }) - }, - ); + assert_str(&items[0].0, "abc"); + assert_vec(&items[0].1, "Vec", 3, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), + _ => panic!("3 items expected"), + }) + }); + assert_str(&items[1].0, "efg"); + assert_vec(&items[1].1, "Vec", 3, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(11))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(12))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(13))), + _ => panic!("3 items expected"), + }) + }); }, ); + assert_btree_map( - &vars[2], - "hm3", + hm3.value(), "BTreeMap", |items| { assert_eq!(items.len(), 100); @@ -1811,119 +1798,84 @@ fn test_btree_map() { let exp_items = (0..100).collect::>(); for i in 0..100 { - assert_scalar( - &items[i].0, - "k", - "i32", - Some(SupportedScalar::I32(exp_items[i])), - ); + assert_scalar(&items[i].0, "i32", Some(SupportedScalar::I32(exp_items[i]))); } for i in 0..100 { - assert_scalar( - &items[i].1, - "v", - "i32", - Some(SupportedScalar::I32(exp_items[i])), - ); + assert_scalar(&items[i].1, "i32", Some(SupportedScalar::I32(exp_items[i]))); } }, ); + assert_btree_map( - &vars[3], - "hm4", + hm4.value(), "BTreeMap, alloc::alloc::Global>", |items| { assert_eq!(items.len(), 2); - assert_string(&items[0].0, "k", "1"); + assert_string(&items[0].0, "1"); assert_btree_map( &items[0].1, - "v", "BTreeMap", |items| { assert_eq!(items.len(), 2); - assert_scalar(&items[0].0, "k", "i32", Some(SupportedScalar::I32(1))); - assert_scalar(&items[0].1, "v", "i32", Some(SupportedScalar::I32(1))); - assert_scalar(&items[1].0, "k", "i32", Some(SupportedScalar::I32(2))); - assert_scalar(&items[1].1, "v", "i32", Some(SupportedScalar::I32(2))); + assert_scalar(&items[0].0, "i32", Some(SupportedScalar::I32(1))); + assert_scalar(&items[0].1, "i32", Some(SupportedScalar::I32(1))); + assert_scalar(&items[1].0, "i32", Some(SupportedScalar::I32(2))); + assert_scalar(&items[1].1, "i32", Some(SupportedScalar::I32(2))); }, ); assert_string( &items[1].0, - "k", "3", ); assert_btree_map( &items[1].1, - "v", "BTreeMap", |items| { assert_eq!(items.len(), 2); - assert_scalar(&items[0].0, "k", "i32", Some(SupportedScalar::I32(3))); - assert_scalar(&items[0].1, "v", "i32", Some(SupportedScalar::I32(3))); - assert_scalar(&items[1].0, "k", "i32", Some(SupportedScalar::I32(4))); - assert_scalar(&items[1].1, "v", "i32", Some(SupportedScalar::I32(4))); + assert_scalar(&items[0].0, "i32", Some(SupportedScalar::I32(3))); + assert_scalar(&items[0].1, "i32", Some(SupportedScalar::I32(3))); + assert_scalar(&items[1].0, "i32", Some(SupportedScalar::I32(4))); + assert_scalar(&items[1].1, "i32", Some(SupportedScalar::I32(4))); }, ); }); let make_idx_dqe = |var: &str, literal| { - DQE::Index( - DQE::Variable(VariableSelector::Name { - var_name: var.to_string(), - only_local: true, - }) - .boxed(), - literal, - ) + Dqe::Index(Dqe::Variable(Selector::by_name(var, true)).boxed(), literal) }; // get by bool key let dqe = make_idx_dqe("hm1", Literal::Bool(true)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "value", "i64", Some(SupportedScalar::I64(3))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "i64", Some(SupportedScalar::I64(3))); // get by string key let dqe = make_idx_dqe("hm2", Literal::String("efg".to_string())); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_vec(val, "value", "Vec", 3, |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(11))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(12))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(13))), + read_var_dqe!(debugger, dqe => val); + assert_vec(val.value(), "Vec", 3, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(11))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(12))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(13))), _ => panic!("3 items expected"), }) }); // get by int key let dqe = make_idx_dqe("hm3", Literal::Int(99)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "value", "i32", Some(SupportedScalar::I32(99))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "i32", Some(SupportedScalar::I32(99))); // get by pointer key - let key = debugger - .read_variable(DQE::Variable(VariableSelector::Name { - var_name: "b".to_string(), - only_local: true, - })) - .unwrap(); - assert_eq!(key.len(), 1); - let VariableIR::Pointer(ptr) = &key[0] else { + let Value::Pointer(ptr) = b.value() else { panic!("not a pointer") }; let ptr_val = ptr.value.unwrap() as usize; let dqe = make_idx_dqe("hm5", Literal::Address(ptr_val)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_str(val, "value", "b"); + read_var_dqe!(debugger, dqe => val); + assert_str(val.value(), "b"); // get by complex object let dqe = make_idx_dqe( @@ -1944,10 +1896,8 @@ fn test_btree_map() { ), ])), ); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "value", "i32", Some(SupportedScalar::I32(2))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "i32", Some(SupportedScalar::I32(2))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -1967,47 +1917,43 @@ fn test_read_btree_set() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(413)); - let vars = debugger.read_local_variables().unwrap(); + read_locals!(debugger => hs1, hs2, hs3, _a, b, _hs4); + assert_idents!(hs1 => "hs1", hs2 => "hs2", hs3 => "hs3"); + assert_btree_set( - &vars[0], - "hs1", + hs1.value(), "BTreeSet", |items| { assert_eq!(items.len(), 4); - assert_scalar(&items[0], "k", "i32", Some(SupportedScalar::I32(1))); - assert_scalar(&items[1], "k", "i32", Some(SupportedScalar::I32(2))); - assert_scalar(&items[2], "k", "i32", Some(SupportedScalar::I32(3))); - assert_scalar(&items[3], "k", "i32", Some(SupportedScalar::I32(4))); + assert_scalar(&items[0], "i32", Some(SupportedScalar::I32(1))); + assert_scalar(&items[1], "i32", Some(SupportedScalar::I32(2))); + assert_scalar(&items[2], "i32", Some(SupportedScalar::I32(3))); + assert_scalar(&items[3], "i32", Some(SupportedScalar::I32(4))); }, ); + assert_btree_set( - &vars[1], - "hs2", + hs2.value(), "BTreeSet", |items| { assert_eq!(items.len(), 100); let exp_items = (0..100).collect::>(); for i in 0..100 { - assert_scalar( - &items[i], - "k", - "i32", - Some(SupportedScalar::I32(exp_items[i])), - ); + assert_scalar(&items[i], "i32", Some(SupportedScalar::I32(exp_items[i]))); } }, ); + assert_btree_set( - &vars[2], - "hs3", + hs3.value(), "BTreeSet, alloc::alloc::Global>", |items| { assert_eq!(items.len(), 1); - assert_vec(&items[0], "k", "Vec", 2, |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), + assert_vec(&items[0], "Vec", 2, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), _ => panic!("2 items expected"), }) }); @@ -2015,53 +1961,31 @@ fn test_read_btree_set() { ); let make_idx_dqe = |var: &str, literal| { - DQE::Index( - DQE::Variable(VariableSelector::Name { - var_name: var.to_string(), - only_local: true, - }) - .boxed(), - literal, - ) + Dqe::Index(Dqe::Variable(Selector::by_name(var, true)).boxed(), literal) }; // get by int key let dqe = make_idx_dqe("hs1", Literal::Int(2)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "contains", "bool", Some(SupportedScalar::Bool(true))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(true))); let dqe = make_idx_dqe("hs1", Literal::Int(5)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "contains", "bool", Some(SupportedScalar::Bool(false))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(false))); // get by pointer key - let key = debugger - .read_variable(DQE::Variable(VariableSelector::Name { - var_name: "b".to_string(), - only_local: true, - })) - .unwrap(); - assert_eq!(key.len(), 1); - let VariableIR::Pointer(ptr) = &key[0] else { + let Value::Pointer(ptr) = b.value() else { panic!("not a pointer") }; let ptr_val = ptr.value.unwrap() as usize; let dqe = make_idx_dqe("hs4", Literal::Address(ptr_val)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "contains", "bool", Some(SupportedScalar::Bool(true))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(true))); let dqe = make_idx_dqe("hs4", Literal::Address(0)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "contains", "bool", Some(SupportedScalar::Bool(false))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(false))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -2081,52 +2005,52 @@ fn test_read_vec_deque() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(431)); - let vars = debugger.read_local_variables().unwrap(); + read_locals!(debugger => vd1, vd2); + assert_idents!(vd1 => "vd1", vd2 => "vd2"); + assert_vec_deque( - &vars[0], - "vd1", + vd1.value(), "VecDeque", 8, |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(9))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(10))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(0))), - 3 => assert_scalar(item, "3", "i32", Some(SupportedScalar::I32(1))), - 4 => assert_scalar(item, "4", "i32", Some(SupportedScalar::I32(2))), + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(9))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(10))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(0))), + 3 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 4 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), _ => panic!("5 items expected"), }) }, ); assert_vec_deque( - &vars[1], - "vd2", + vd2.value(), "VecDeque, alloc::alloc::Global>", 4, |buf| { - assert_array(buf, "buf", "[VecDeque]", |i, item| match i { - 0 => assert_vec_deque(item, "0", "VecDeque", 3, |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(-2))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(-1))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(0))), + assert_array(buf, "[VecDeque]", |i, item| match i { + 0 => assert_vec_deque(item, "VecDeque", 3, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-2))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-1))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(0))), _ => panic!("3 items expected"), }) }), - 1 => assert_vec_deque(item, "1", "VecDeque", 3, |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(3))), + 1 => assert_vec_deque(item, "VecDeque", 3, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), _ => panic!("3 items expected"), }) }), - 2 => assert_vec_deque(item, "2", "VecDeque", 3, |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(4))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(5))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(6))), + 2 => assert_vec_deque(item, "VecDeque", 3, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(4))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(5))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(6))), _ => panic!("3 items expected"), }) }), @@ -2152,30 +2076,39 @@ fn test_read_atomic() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(441)); - let vars = debugger.read_local_variables().unwrap(); - assert_struct(&vars[0], "int32_atomic", "AtomicI32", |i, member| match i { - 0 => assert_struct(member, "v", "UnsafeCell", |i, member| match i { - 0 => assert_scalar(member, "value", "i32", Some(SupportedScalar::I32(1))), - _ => panic!("1 members expected"), + read_locals!(debugger => int32_atomic, _int32, int32_atomic_ptr); + assert_idents!(int32_atomic => "int32_atomic", int32_atomic_ptr => "int32_atomic_ptr"); + + assert_struct(int32_atomic.value(), "AtomicI32", |i, member| match i { + 0 => assert_member(member, "v", |val| { + assert_struct(val, "UnsafeCell", |i, member| match i { + 0 => assert_member(member, "value", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(1))) + }), + _ => panic!("1 members expected"), + }) }), _ => panic!("1 members expected"), }); assert_struct( - &vars[2], - "int32_atomic_ptr", + int32_atomic_ptr.value(), "AtomicPtr", |i, member| match i { - 0 => assert_struct(member, "p", "UnsafeCell<*mut i32>", |i, member| match i { - 0 => assert_pointer(member, "value", "*mut i32"), - _ => panic!("1 members expected"), + 0 => assert_member(member, "p", |val| { + assert_struct(val, "UnsafeCell<*mut i32>", |i, member| match i { + 0 => assert_member(member, "value", |val| assert_pointer(val, "*mut i32")), + _ => panic!("1 members expected"), + }) }), _ => panic!("1 members expected"), }, ); - let deref = read_single_var(&debugger, "*int32_atomic_ptr.p.value"); - assert_scalar(&deref, "*value", "i32", Some(SupportedScalar::I32(2))); + let deref = int32_atomic_ptr + .clone() + .modify_value(|ctx, v| v.field("p").unwrap().field("value").unwrap().deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -2195,22 +2128,23 @@ fn test_cell() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(453)); - let vars = debugger.read_local_variables().unwrap(); - assert_cell(&vars[0], "a_cell", "Cell", |value| { - assert_scalar(value, "value", "i32", Some(SupportedScalar::I32(1))) + read_locals!(debugger => a_cell, b_refcell, _b_refcell_borrow_1, _b_refcell_borrow_2); + assert_idents!(a_cell => "a_cell", b_refcell => "b_refcell"); + + assert_cell(a_cell.value(), "Cell", |value| { + assert_scalar(value, "i32", Some(SupportedScalar::I32(1))) }); assert_refcell( - &vars[1], - "b_refcell", + b_refcell.value(), "RefCell>", 2, |value| { - assert_vec(value, "value", "Vec", 3, |buf| { - assert_array(buf, "buf", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(3))), + assert_vec(value, "Vec", 3, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), _ => panic!("3 items expected"), }) }) @@ -2235,96 +2169,175 @@ fn test_shared_ptr() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(475)); - let vars = debugger.read_local_variables().unwrap(); - assert_rc(&vars[0], "rc0", "Rc"); - let deref = read_single_var(&debugger, "*rc0"); - assert_struct(&deref, "*rc0", "RcBox", |i, member| match i { - 0 => assert_cell(member, "strong", "Cell", |inner| { - assert_scalar(inner, "value", "usize", Some(SupportedScalar::Usize(2))) + read_locals!(debugger => rc0, rc1, weak_rc2, arc0, arc1, weak_arc2); + assert_idents!( + rc0 => "rc0", rc1 => "rc1", weak_rc2 => "weak_rc2", arc0 => "arc0", arc1 => "arc1", weak_arc2 => "weak_arc2" + ); + + assert_rc(rc0.value(), "Rc"); + let deref = rc0.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_struct(deref.unwrap().value(), "RcBox", |i, member| match i { + 0 => assert_member(member, "strong", |val| { + assert_cell(val, "Cell", |inner| { + assert_scalar(inner, "usize", Some(SupportedScalar::Usize(2))) + }) + }), + 1 => assert_member(member, "weak", |val| { + assert_cell(val, "Cell", |inner| { + assert_scalar(inner, "usize", Some(SupportedScalar::Usize(2))) + }) }), - 1 => assert_cell(member, "weak", "Cell", |inner| { - assert_scalar(inner, "value", "usize", Some(SupportedScalar::Usize(2))) + 2 => assert_member(member, "value", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(1))) }), - 2 => assert_scalar(member, "value", "i32", Some(SupportedScalar::I32(1))), _ => panic!("3 members expected"), }); - assert_rc(&vars[1], "rc1", "Rc"); - let deref = read_single_var(&debugger, "*rc1"); - assert_struct(&deref, "*rc1", "RcBox", |i, member| match i { - 0 => assert_cell(member, "strong", "Cell", |inner| { - assert_scalar(inner, "value", "usize", Some(SupportedScalar::Usize(2))) + + assert_rc(rc1.value(), "Rc"); + let deref = rc1.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_struct(deref.unwrap().value(), "RcBox", |i, member| match i { + 0 => assert_member(member, "strong", |val| { + assert_cell(val, "Cell", |inner| { + assert_scalar(inner, "usize", Some(SupportedScalar::Usize(2))) + }) + }), + 1 => assert_member(member, "weak", |val| { + assert_cell(val, "Cell", |inner| { + assert_scalar(inner, "usize", Some(SupportedScalar::Usize(2))) + }) }), - 1 => assert_cell(member, "weak", "Cell", |inner| { - assert_scalar(inner, "value", "usize", Some(SupportedScalar::Usize(2))) + 2 => assert_member(member, "value", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(1))) }), - 2 => assert_scalar(member, "value", "i32", Some(SupportedScalar::I32(1))), _ => panic!("3 members expected"), }); - assert_rc(&vars[2], "weak_rc2", "Weak"); - let deref = read_single_var(&debugger, "*weak_rc2"); - assert_struct(&deref, "*weak_rc2", "RcBox", |i, member| match i { - 0 => assert_cell(member, "strong", "Cell", |inner| { - assert_scalar(inner, "value", "usize", Some(SupportedScalar::Usize(2))) + + assert_rc(weak_rc2.value(), "Weak"); + let deref = weak_rc2.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_struct(deref.unwrap().value(), "RcBox", |i, member| match i { + 0 => assert_member(member, "strong", |val| { + assert_cell(val, "Cell", |inner| { + assert_scalar(inner, "usize", Some(SupportedScalar::Usize(2))) + }) + }), + 1 => assert_member(member, "weak", |val| { + assert_cell(val, "Cell", |inner| { + assert_scalar(inner, "usize", Some(SupportedScalar::Usize(2))) + }) }), - 1 => assert_cell(member, "weak", "Cell", |inner| { - assert_scalar(inner, "value", "usize", Some(SupportedScalar::Usize(2))) + 2 => assert_member(member, "value", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(1))) }), - 2 => assert_scalar(member, "value", "i32", Some(SupportedScalar::I32(1))), _ => panic!("3 members expected"), }); - assert_arc(&vars[3], "arc0", "Arc"); - let deref = read_single_var(&debugger, "*arc0"); - assert_struct(&deref, "*arc0", "ArcInner", |i, member| match i { - 0 => assert_struct(member, "strong", "AtomicUsize", |i, member| match i { - 0 => assert_struct(member, "v", "UnsafeCell", |_, member| { - assert_scalar(member, "value", "usize", Some(SupportedScalar::Usize(2))) + assert_arc(arc0.value(), "Arc"); + let deref = arc0.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_struct( + deref.unwrap().value(), + "ArcInner", + |i, member| match i { + 0 => assert_member(member, "strong", |val| { + assert_struct(val, "AtomicUsize", |i, member| match i { + 0 => assert_member(member, "v", |val| { + assert_struct(val, "UnsafeCell", |_, member| { + assert_member(member, "value", |val| { + assert_scalar(val, "usize", Some(SupportedScalar::Usize(2))) + }) + }) + }), + _ => panic!("1 member expected"), + }) }), - _ => panic!("1 member expected"), - }), - 1 => assert_struct(member, "weak", "AtomicUsize", |i, member| match i { - 0 => assert_struct(member, "v", "UnsafeCell", |_, member| { - assert_scalar(member, "value", "usize", Some(SupportedScalar::Usize(2))) + 1 => assert_member(member, "weak", |val| { + assert_struct(val, "AtomicUsize", |i, member| match i { + 0 => assert_member(member, "v", |val| { + assert_struct(val, "UnsafeCell", |_, member| { + assert_member(member, "value", |val| { + assert_scalar(val, "usize", Some(SupportedScalar::Usize(2))) + }) + }) + }), + _ => panic!("1 member expected"), + }) }), - _ => panic!("1 member expected"), - }), - 2 => assert_scalar(member, "data", "i32", Some(SupportedScalar::I32(2))), - _ => panic!("3 members expected"), - }); - assert_arc(&vars[4], "arc1", "Arc"); - let deref = read_single_var(&debugger, "*arc1"); - assert_struct(&deref, "*arc1", "ArcInner", |i, member| match i { - 0 => assert_struct(member, "strong", "AtomicUsize", |i, member| match i { - 0 => assert_struct(member, "v", "UnsafeCell", |_, member| { - assert_scalar(member, "value", "usize", Some(SupportedScalar::Usize(2))) + 2 => assert_member(member, "data", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(2))) }), - _ => panic!("1 member expected"), - }), - 1 => assert_struct(member, "weak", "AtomicUsize", |i, member| match i { - 0 => assert_struct(member, "v", "UnsafeCell", |_, member| { - assert_scalar(member, "value", "usize", Some(SupportedScalar::Usize(2))) + _ => panic!("3 members expected"), + }, + ); + + assert_arc(arc1.value(), "Arc"); + let deref = arc1.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_struct( + deref.unwrap().value(), + "ArcInner", + |i, member| match i { + 0 => assert_member(member, "strong", |val| { + assert_struct(val, "AtomicUsize", |i, member| match i { + 0 => assert_member(member, "v", |val| { + assert_struct(val, "UnsafeCell", |_, member| { + assert_member(member, "value", |val| { + assert_scalar(val, "usize", Some(SupportedScalar::Usize(2))) + }) + }) + }), + _ => panic!("1 member expected"), + }) }), - _ => panic!("1 member expected"), - }), - 2 => assert_scalar(member, "data", "i32", Some(SupportedScalar::I32(2))), - _ => panic!("3 members expected"), - }); - assert_arc(&vars[5], "weak_arc2", "Weak"); - let deref = read_single_var(&debugger, "*weak_arc2"); - assert_struct(&deref, "*weak_arc2", "ArcInner", |i, member| match i { - 0 => assert_struct(member, "strong", "AtomicUsize", |i, member| match i { - 0 => assert_struct(member, "v", "UnsafeCell", |_, member| { - assert_scalar(member, "value", "usize", Some(SupportedScalar::Usize(2))) + 1 => assert_member(member, "weak", |val| { + assert_struct(val, "AtomicUsize", |i, member| match i { + 0 => assert_member(member, "v", |val| { + assert_struct(val, "UnsafeCell", |_, member| { + assert_member(member, "value", |val| { + assert_scalar(val, "usize", Some(SupportedScalar::Usize(2))) + }) + }) + }), + _ => panic!("1 member expected"), + }) }), - _ => panic!("1 member expected"), - }), - 1 => assert_struct(member, "weak", "AtomicUsize", |i, member| match i { - 0 => assert_struct(member, "v", "UnsafeCell", |_, member| { - assert_scalar(member, "value", "usize", Some(SupportedScalar::Usize(2))) + 2 => assert_member(member, "data", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(2))) }), - _ => panic!("1 member expected"), + _ => panic!("3 members expected"), + }, + ); + + assert_arc(weak_arc2.value(), "Weak"); + let deref = weak_arc2 + .clone() + .modify_value(|ctx, v| v.deref(ctx)) + .unwrap(); + assert_struct(deref.value(), "ArcInner", |i, member| match i { + 0 => assert_member(member, "strong", |val| { + assert_struct(val, "AtomicUsize", |i, member| match i { + 0 => assert_member(member, "v", |val| { + assert_struct(val, "UnsafeCell", |_, member| { + assert_member(member, "value", |val| { + assert_scalar(val, "usize", Some(SupportedScalar::Usize(2))) + }) + }) + }), + _ => panic!("1 member expected"), + }) + }), + 1 => assert_member(member, "weak", |val| { + assert_struct(val, "AtomicUsize", |i, member| match i { + 0 => assert_member(member, "v", |val| { + assert_struct(val, "UnsafeCell", |_, member| { + assert_member(member, "value", |val| { + assert_scalar(val, "usize", Some(SupportedScalar::Usize(2))) + }) + }) + }), + _ => panic!("1 member expected"), + }) + }), + 2 => assert_member(member, "data", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(2))) }), - 2 => assert_scalar(member, "data", "i32", Some(SupportedScalar::I32(2))), _ => panic!("3 members expected"), }); @@ -2346,82 +2359,77 @@ fn test_zst_types() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(496)); - let vars = debugger.read_local_variables().unwrap(); + read_locals!( + debugger => ptr_zst, array_zst, vec_zst, slice_zst, struct_zst, enum_zst, vecdeque_zst, + hash_map_zst_key, hash_map_zst_val, hash_map_zst, hash_set_zst, btree_map_zst_key, + btree_map_zst_val, btree_map_zst, btree_set_zst + ); + assert_idents!( + ptr_zst => "ptr_zst", array_zst => "array_zst", vec_zst => "vec_zst", + slice_zst => "slice_zst", struct_zst => "struct_zst", enum_zst => "enum_zst", + vecdeque_zst => "vecdeque_zst", hash_map_zst_key => "hash_map_zst_key", + hash_map_zst_val => "hash_map_zst_val", hash_map_zst => "hash_map_zst", + hash_set_zst => "hash_set_zst", btree_map_zst_key => "btree_map_zst_key", + btree_map_zst_val => "btree_map_zst_val", btree_map_zst => "btree_map_zst", + btree_set_zst => "btree_set_zst" + ); - assert_pointer(&vars[0], "ptr_zst", "&()"); - let deref = read_single_var(&debugger, "*ptr_zst"); - assert_scalar(&deref, "*ptr_zst", "()", Some(SupportedScalar::Empty())); + assert_pointer(ptr_zst.value(), "&()"); + let deref = ptr_zst.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_scalar(deref.unwrap().value(), "()", Some(SupportedScalar::Empty())); - assert_array(&vars[1], "array_zst", "[()]", |i, item| match i { - 0 => assert_scalar(item, "0", "()", Some(SupportedScalar::Empty())), - 1 => assert_scalar(item, "1", "()", Some(SupportedScalar::Empty())), + assert_array(array_zst.value(), "[()]", |i, item| match i { + 0 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), + 1 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), _ => panic!("2 members expected"), }); - assert_vec( - &vars[2], - "vec_zst", - "Vec<(), alloc::alloc::Global>", - 0, - |buf| { - assert_array(buf, "buf", "[()]", |i, item| match i { - 0 => assert_scalar(item, "0", "()", Some(SupportedScalar::Empty())), - 1 => assert_scalar(item, "1", "()", Some(SupportedScalar::Empty())), - 2 => assert_scalar(item, "2", "()", Some(SupportedScalar::Empty())), - _ => panic!("3 members expected"), - }) - }, - ); - - assert_vec( - &vars[2], - "vec_zst", - "Vec<(), alloc::alloc::Global>", - 0, - |buf| { - assert_array(buf, "buf", "[()]", |i, item| match i { - 0 => assert_scalar(item, "0", "()", Some(SupportedScalar::Empty())), - 1 => assert_scalar(item, "1", "()", Some(SupportedScalar::Empty())), - 2 => assert_scalar(item, "2", "()", Some(SupportedScalar::Empty())), - _ => panic!("3 members expected"), - }) - }, - ); + assert_vec(vec_zst.value(), "Vec<(), alloc::alloc::Global>", 0, |buf| { + assert_array(buf, "[()]", |i, item| match i { + 0 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), + 1 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), + 2 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), + _ => panic!("3 members expected"), + }) + }); - assert_pointer(&vars[3], "slice_zst", "&[(); 4]"); - let deref = read_single_var(&debugger, "*slice_zst"); - assert_array(&deref, "*slice_zst", "[()]", |i, item| match i { - 0 => assert_scalar(item, "0", "()", Some(SupportedScalar::Empty())), - 1 => assert_scalar(item, "1", "()", Some(SupportedScalar::Empty())), - 2 => assert_scalar(item, "2", "()", Some(SupportedScalar::Empty())), - 3 => assert_scalar(item, "3", "()", Some(SupportedScalar::Empty())), + assert_pointer(slice_zst.value(), "&[(); 4]"); + let deref = slice_zst.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_array(deref.unwrap().value(), "[()]", |i, item| match i { + 0 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), + 1 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), + 2 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), + 3 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), _ => panic!("4 members expected"), }); - assert_struct(&vars[4], "struct_zst", "StructZst", |i, member| match i { - 0 => assert_scalar(member, "0", "()", Some(SupportedScalar::Empty())), + assert_struct(struct_zst.value(), "StructZst", |i, member| match i { + 0 => assert_member(member, "__0", |val| { + assert_scalar(val, "()", Some(SupportedScalar::Empty())) + }), _ => panic!("1 member expected"), }); - assert_rust_enum(&vars[5], "enum_zst", "Option<()>", |member| { - assert_struct(member, "Some", "Some", |i, member| match i { - 0 => assert_scalar(member, "0", "()", Some(SupportedScalar::Empty())), + assert_rust_enum(enum_zst.value(), "Option<()>", |member| { + assert_struct(member, "Some", |i, member| match i { + 0 => assert_member(member, "__0", |val| { + assert_scalar(val, "()", Some(SupportedScalar::Empty())) + }), _ => panic!("1 member expected"), }) }); assert_vec_deque( - &vars[6], - "vecdeque_zst", + vecdeque_zst.value(), "VecDeque<(), alloc::alloc::Global>", 0, |buf| { - assert_array(buf, "buf", "[()]", |i, item| match i { - 0 => assert_scalar(item, "0", "()", Some(SupportedScalar::Empty())), - 1 => assert_scalar(item, "1", "()", Some(SupportedScalar::Empty())), - 2 => assert_scalar(item, "2", "()", Some(SupportedScalar::Empty())), - 3 => assert_scalar(item, "3", "()", Some(SupportedScalar::Empty())), - 4 => assert_scalar(item, "4", "()", Some(SupportedScalar::Empty())), + assert_array(buf, "[()]", |i, item| match i { + 0 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), + 1 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), + 2 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), + 3 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), + 4 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), _ => panic!("5 members expected"), }) }, @@ -2433,10 +2441,10 @@ fn test_zst_types() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashMap<(), i32, std::collections::hash::map::RandomState>", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashMap<(), i32, std::hash::random::RandomState>", ).unwrap(); - assert_hashmap(&vars[7], "hash_map_zst_key", hashmap_type, |items| { + assert_hashmap(hash_map_zst_key.value(), hashmap_type, |items| { assert_eq!(items.len(), 1); - assert_scalar(&items[0].0, "0", "()", Some(SupportedScalar::Empty())); - assert_scalar(&items[0].1, "1", "i32", Some(SupportedScalar::I32(1))); + assert_scalar(&items[0].0, "()", Some(SupportedScalar::Empty())); + assert_scalar(&items[0].1, "i32", Some(SupportedScalar::I32(1))); }); let hashmap_type = version_switch!( @@ -2444,10 +2452,10 @@ fn test_zst_types() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashMap", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashMap", ).unwrap(); - assert_hashmap(&vars[8], "hash_map_zst_val", hashmap_type, |items| { + assert_hashmap(hash_map_zst_val.value(), hashmap_type, |items| { assert_eq!(items.len(), 1); - assert_scalar(&items[0].0, "0", "i32", Some(SupportedScalar::I32(1))); - assert_scalar(&items[0].1, "1", "()", Some(SupportedScalar::Empty())); + assert_scalar(&items[0].0, "i32", Some(SupportedScalar::I32(1))); + assert_scalar(&items[0].1, "()", Some(SupportedScalar::Empty())); }); let hashmap_type = version_switch!( @@ -2455,10 +2463,10 @@ fn test_zst_types() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashMap<(), (), std::collections::hash::map::RandomState>", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashMap<(), (), std::hash::random::RandomState>", ).unwrap(); - assert_hashmap(&vars[9], "hash_map_zst", hashmap_type, |items| { + assert_hashmap(hash_map_zst.value(), hashmap_type, |items| { assert_eq!(items.len(), 1); - assert_scalar(&items[0].0, "0", "()", Some(SupportedScalar::Empty())); - assert_scalar(&items[0].1, "1", "()", Some(SupportedScalar::Empty())); + assert_scalar(&items[0].0, "()", Some(SupportedScalar::Empty())); + assert_scalar(&items[0].1, "()", Some(SupportedScalar::Empty())); }); let hashset_type = version_switch!( @@ -2466,50 +2474,49 @@ fn test_zst_types() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashSet<(), std::collections::hash::map::RandomState>", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashSet<(), std::hash::random::RandomState>", ).unwrap(); - assert_hashset(&vars[10], "hash_set_zst", hashset_type, |items| { + assert_hashset(hash_set_zst.value(), hashset_type, |items| { assert_eq!(items.len(), 1); - assert_scalar(&items[0], "0", "()", Some(SupportedScalar::Empty())); + assert_scalar(&items[0], "()", Some(SupportedScalar::Empty())); }); assert_btree_map( - &vars[11], - "btree_map_zst_key", + btree_map_zst_key.value(), "BTreeMap<(), i32, alloc::alloc::Global>", |items| { assert_eq!(items.len(), 1); - assert_scalar(&items[0].0, "k", "()", Some(SupportedScalar::Empty())); - assert_scalar(&items[0].1, "v", "i32", Some(SupportedScalar::I32(1))); + assert_scalar(&items[0].0, "()", Some(SupportedScalar::Empty())); + assert_scalar(&items[0].1, "i32", Some(SupportedScalar::I32(1))); }, ); + assert_btree_map( - &vars[12], - "btree_map_zst_val", + btree_map_zst_val.value(), "BTreeMap", |items| { assert_eq!(items.len(), 2); - assert_scalar(&items[0].0, "k", "i32", Some(SupportedScalar::I32(1))); - assert_scalar(&items[0].1, "v", "()", Some(SupportedScalar::Empty())); - assert_scalar(&items[1].0, "k", "i32", Some(SupportedScalar::I32(2))); - assert_scalar(&items[1].1, "v", "()", Some(SupportedScalar::Empty())); + assert_scalar(&items[0].0, "i32", Some(SupportedScalar::I32(1))); + assert_scalar(&items[0].1, "()", Some(SupportedScalar::Empty())); + assert_scalar(&items[1].0, "i32", Some(SupportedScalar::I32(2))); + assert_scalar(&items[1].1, "()", Some(SupportedScalar::Empty())); }, ); + assert_btree_map( - &vars[13], - "btree_map_zst", + btree_map_zst.value(), "BTreeMap<(), (), alloc::alloc::Global>", |items| { assert_eq!(items.len(), 1); - assert_scalar(&items[0].0, "k", "()", Some(SupportedScalar::Empty())); - assert_scalar(&items[0].1, "v", "()", Some(SupportedScalar::Empty())); + assert_scalar(&items[0].0, "()", Some(SupportedScalar::Empty())); + assert_scalar(&items[0].1, "()", Some(SupportedScalar::Empty())); }, ); + assert_btree_set( - &vars[14], - "btree_set_zst", + btree_set_zst.value(), "BTreeSet<(), alloc::alloc::Global>", |items| { assert_eq!(items.len(), 1); - assert_scalar(&items[0], "k", "()", Some(SupportedScalar::Empty())); + assert_scalar(&items[0], "()", Some(SupportedScalar::Empty())); }, ); @@ -2529,39 +2536,21 @@ fn test_read_static_in_fn_variable() { // brkpt in function where static is declared debugger.set_breakpoint_at_line("vars.rs", 504).unwrap(); // brkpt outside function where static is declared - debugger.set_breakpoint_at_line("vars.rs", 551).unwrap(); + debugger.set_breakpoint_at_line("vars.rs", 570).unwrap(); debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(504)); - let vars = debugger - .read_variable(DQE::Variable(VariableSelector::Name { - var_name: "INNER_STATIC".to_string(), - only_local: false, - })) - .unwrap(); - assert_scalar( - &vars[0], - "vars::inner_static::INNER_STATIC", - "u32", - Some(SupportedScalar::U32(1)), - ); + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name("INNER_STATIC", false)) => inner_static); + assert_idents!(inner_static => "vars::inner_static::INNER_STATIC"); + assert_scalar(inner_static.value(), "u32", Some(SupportedScalar::U32(1))); debugger.continue_debugee().unwrap(); - assert_eq!(info.line.take(), Some(551)); + assert_eq!(info.line.take(), Some(570)); - let vars = debugger - .read_variable(DQE::Variable(VariableSelector::Name { - var_name: "INNER_STATIC".to_string(), - only_local: false, - })) - .unwrap(); - assert_scalar( - &vars[0], - "vars::inner_static::INNER_STATIC", - "u32", - Some(SupportedScalar::U32(1)), - ); + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name("INNER_STATIC", false)) => inner_static); + assert_idents!(inner_static => "vars::inner_static::INNER_STATIC"); + assert_scalar(inner_static.value(), "u32", Some(SupportedScalar::U32(1))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -2581,75 +2570,55 @@ fn test_slice_operator() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(61)); - let vars = debugger - .read_variable(DQE::Slice( - DQE::Variable(VariableSelector::Name { - var_name: "arr_1".to_string(), - only_local: true, - }) - .boxed(), + read_var_dqe!(debugger, Dqe::Slice( + Dqe::Variable(Selector::by_name("arr_1", true)).boxed(), None, None, - )) - .unwrap(); - assert_array(&vars[0], "arr_1", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(-1))), - 2 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(2))), - 3 => assert_scalar(item, "3", "i32", Some(SupportedScalar::I32(-2))), - 4 => assert_scalar(item, "4", "i32", Some(SupportedScalar::I32(3))), + ) => arr_1); + assert_idents!(arr_1 => "arr_1"); + assert_array(arr_1.value(), "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-1))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 3 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-2))), + 4 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), _ => panic!("5 items expected"), }); - let vars = debugger - .read_variable(DQE::Slice( - DQE::Variable(VariableSelector::Name { - var_name: "arr_1".to_string(), - only_local: true, - }) - .boxed(), + read_var_dqe!(debugger, Dqe::Slice( + Dqe::Variable(Selector::by_name("arr_1", true)).boxed(), Some(3), None, - )) - .unwrap(); - assert_array(&vars[0], "arr_1", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "3", "i32", Some(SupportedScalar::I32(-2))), - 1 => assert_scalar(item, "4", "i32", Some(SupportedScalar::I32(3))), + ) => arr_1); + assert_idents!(arr_1 => "arr_1"); + assert_array(arr_1.value(), "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-2))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), _ => panic!("2 items expected"), }); - let vars = debugger - .read_variable(DQE::Slice( - DQE::Variable(VariableSelector::Name { - var_name: "arr_1".to_string(), - only_local: true, - }) - .boxed(), + read_var_dqe!(debugger, Dqe::Slice( + Dqe::Variable(Selector::by_name("arr_1", true)).boxed(), None, Some(2), - )) - .unwrap(); - assert_array(&vars[0], "arr_1", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(-1))), + ) => arr_1); + assert_idents!(arr_1 => "arr_1"); + assert_array(arr_1.value(), "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-1))), _ => panic!("2 items expected"), }); - let vars = debugger - .read_variable(DQE::Slice( - DQE::Variable(VariableSelector::Name { - var_name: "arr_1".to_string(), - only_local: true, - }) - .boxed(), + read_var_dqe!(debugger, Dqe::Slice( + Dqe::Variable(Selector::by_name("arr_1", true)).boxed(), Some(1), Some(4), - )) - .unwrap(); - assert_array(&vars[0], "arr_1", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(-1))), - 1 => assert_scalar(item, "2", "i32", Some(SupportedScalar::I32(2))), - 2 => assert_scalar(item, "3", "i32", Some(SupportedScalar::I32(-2))), + ) => arr_1); + assert_idents!(arr_1 => "arr_1"); + assert_array(arr_1.value(), "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-2))), _ => panic!("3 items expected"), }); @@ -2671,21 +2640,19 @@ fn test_cast_pointers() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(119)); - let vars = debugger.read_local_variables().unwrap(); - assert_scalar(&vars[0], "a", "i32", Some(SupportedScalar::I32(2))); + read_locals!(debugger => a, ref_a, _ptr_a, _ptr_ptr_a, _b, _mut_ref_b, _c, _mut_ptr_c, _box_d, _f, _ref_f); - let VariableIR::Pointer(pointer) = &vars[1] else { + assert_scalar(a.value(), "i32", Some(SupportedScalar::I32(2))); + let Value::Pointer(pointer) = ref_a.value() else { panic!("expect a pointer"); }; let raw_ptr = pointer.value.unwrap(); - let var = debugger - .read_variable(DQE::Deref( - DQE::PtrCast(raw_ptr as usize, "*const i32".to_string()).boxed(), - )) - .unwrap(); - assert_scalar(&var[0], "{unknown}", "i32", Some(SupportedScalar::I32(2))); + read_var_dqe!(debugger, Dqe::Deref( + Dqe::PtrCast(PointerCast::new(raw_ptr as usize, "*const i32")).boxed(), + ) => val); + assert_scalar(val.value(), "i32", Some(SupportedScalar::I32(2))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -2705,9 +2672,10 @@ fn test_read_uuid() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(519)); - let vars = debugger.read_local_variables().unwrap(); - assert_uuid(&vars[0], "uuid_v4", "Uuid"); - assert_uuid(&vars[1], "uuid_v7", "Uuid"); + read_locals!(debugger => uuid_v4, uuid_v7); + assert_idents!(uuid_v4 => "uuid_v4", uuid_v7 => "uuid_v7"); + assert_uuid(uuid_v4.value(), "Uuid"); + assert_uuid(uuid_v7.value(), "Uuid"); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -2726,36 +2694,22 @@ fn test_address_operator() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(119)); - fn addr_of(name: &str, loc: bool) -> DQE { - DQE::Address( - DQE::Variable(VariableSelector::Name { - var_name: name.to_string(), - only_local: loc, - }) - .boxed(), - ) + fn addr_of(name: &str, loc: bool) -> Dqe { + Dqe::Address(Dqe::Variable(Selector::by_name(name, loc)).boxed()) } - fn addr_of_index(name: &str, index: i32) -> DQE { - DQE::Address( - DQE::Index( - DQE::Variable(VariableSelector::Name { - var_name: name.to_string(), - only_local: true, - }) - .boxed(), + fn addr_of_index(name: &str, index: i32) -> Dqe { + Dqe::Address( + Dqe::Index( + Dqe::Variable(Selector::by_name(name, true)).boxed(), Literal::Int(index as i64), ) .boxed(), ) } - fn addr_of_field(name: &str, field: &str) -> DQE { - DQE::Address( - DQE::Field( - DQE::Variable(VariableSelector::Name { - var_name: name.to_string(), - only_local: true, - }) - .boxed(), + fn addr_of_field(name: &str, field: &str) -> Dqe { + Dqe::Address( + Dqe::Field( + Dqe::Variable(Selector::by_name(name, true)).boxed(), field.to_string(), ) .boxed(), @@ -2764,100 +2718,105 @@ fn test_address_operator() { // get address of scalar variable and deref it let addr_a_dqe = addr_of("a", true); - let addr_a = debugger.read_variable(addr_a_dqe.clone()).unwrap(); - assert_pointer(&addr_a[0], "{unknown}", "&i32"); - let a = debugger - .read_variable(DQE::Deref(addr_a_dqe.boxed())) - .unwrap(); - assert_scalar(&a[0], "{unknown}", "i32", Some(SupportedScalar::I32(2))); - - let addr_ptr_a = debugger.read_variable(addr_of("ref_a", true)).unwrap(); - assert_pointer(&addr_ptr_a[0], "{unknown}", "&&i32"); - let a = debugger - .read_variable(DQE::Deref( - DQE::Deref(addr_of("ref_a", true).boxed()).boxed(), - )) - .unwrap(); - assert_scalar(&a[0], "{unknown}", "i32", Some(SupportedScalar::I32(2))); + read_var_dqe!(debugger, addr_a_dqe.clone() => a); + assert_pointer(a.value(), "&i32"); + read_var_dqe!(debugger, Dqe::Deref(addr_a_dqe.boxed()) => a); + assert_scalar(a.value(), "i32", Some(SupportedScalar::I32(2))); + + read_var_dqe!(debugger, addr_of("ref_a", true) => addr_ptr_a); + assert_pointer(addr_ptr_a.value(), "&&i32"); + read_var_dqe!(debugger, Dqe::Deref( + Dqe::Deref(addr_of("ref_a", true).boxed()).boxed(), + ) => a); + assert_scalar(a.value(), "i32", Some(SupportedScalar::I32(2))); // get address of structure field and deref it - let addr_f = debugger.read_variable(addr_of("f", true)).unwrap(); - assert_pointer(&addr_f[0], "{unknown}", "&Foo"); - let f = debugger - .read_variable(DQE::Deref(addr_of("f", true).boxed())) - .unwrap(); - assert_struct(&f[0], "{unknown}", "Foo", |i, member| match i { - 0 => assert_scalar(member, "bar", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_array(member, "baz", "[i32]", |i, item| match i { - 0 => assert_scalar(item, "0", "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "1", "i32", Some(SupportedScalar::I32(2))), - _ => panic!("2 items expected"), + read_var_dqe!(debugger, addr_of("f", true) => addr_f); + assert_pointer(addr_f.value(), "&Foo"); + read_var_dqe!(debugger, Dqe::Deref(addr_of("f", true).boxed()) => f); + assert_struct(f.value(), "Foo", |i, member| match i { + 0 => assert_member(member, "bar", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(1))) + }), + 1 => assert_member(member, "baz", |val| { + assert_array(val, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + _ => panic!("2 items expected"), + }) }), 2 => { - assert_pointer(member, "foo", "&i32"); - let deref = read_single_var(&debugger, "*f.foo"); - assert_scalar(&deref, "*foo", "i32", Some(SupportedScalar::I32(2))); + assert_member(member, "foo", |val| assert_pointer(val, "&i32")); + let member_val = member.value.clone(); + let deref = f.clone().modify_value(|ctx, _| member_val.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); } _ => panic!("3 members expected"), }); - let addr_f_bar = debugger.read_variable(addr_of_field("f", "bar")).unwrap(); - assert_pointer(&addr_f_bar[0], "{unknown}", "&i32"); - let f_bar = debugger - .read_variable(DQE::Deref(addr_of_field("f", "bar").boxed())) - .unwrap(); - assert_scalar(&f_bar[0], "{unknown}", "i32", Some(SupportedScalar::I32(1))); + + read_var_dqe!(debugger, addr_of_field("f", "bar") => addr_f_bar); + assert_pointer(addr_f_bar.value(), "&i32"); + read_var_dqe!(debugger, Dqe::Deref(addr_of_field("f", "bar").boxed()) => f_bar); + assert_scalar(f_bar.value(), "i32", Some(SupportedScalar::I32(1))); // get address of an array element and deref it debugger.set_breakpoint_at_line("vars.rs", 151).unwrap(); debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(151)); - let addr_vec1 = debugger.read_variable(addr_of("vec1", true)).unwrap(); - assert_pointer( - &addr_vec1[0], - "{unknown}", - "&Vec", - ); - - let addr_el_1 = debugger.read_variable(addr_of_index("vec1", 1)).unwrap(); - assert_pointer(&addr_el_1[0], "{unknown}", "&i32"); - let el_1 = debugger - .read_variable(DQE::Deref(addr_of_index("vec1", 1).boxed())) - .unwrap(); - assert_scalar(&el_1[0], "{unknown}", "i32", Some(SupportedScalar::I32(2))); + read_var_dqe!(debugger, addr_of("vec1", true) => addr_vec1); + assert_pointer(addr_vec1.value(), "&Vec"); + read_var_dqe!(debugger, addr_of_index("vec1", 1) => addr_el_1); + assert_pointer(addr_el_1.value(), "&i32"); + read_var_dqe!(debugger, Dqe::Deref(addr_of_index("vec1", 1).boxed()) => el_1); + assert_scalar(el_1.value(), "i32", Some(SupportedScalar::I32(2))); // get an address of a hashmap element and deref it debugger.set_breakpoint_at_line("vars.rs", 290).unwrap(); debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(290)); - let addr_hm3 = debugger.read_variable(addr_of("hm3", true)).unwrap(); + read_var_dqe!(debugger, addr_of("hm3", true) => addr_hm3); let inner_hash_map_type = version_switch!( rust_version(VARS_APP).unwrap(), (1, 0, 0) ..= (1, 75, u32::MAX) => "&HashMap", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "&HashMap", ).unwrap(); - assert_pointer(&addr_hm3[0], "{unknown}", inner_hash_map_type); + assert_pointer(addr_hm3.value(), inner_hash_map_type); - let addr_el_11 = debugger.read_variable(addr_of_index("hm3", 11)).unwrap(); - assert_pointer(&addr_el_11[0], "{unknown}", "&i32"); - let el_11 = debugger - .read_variable(DQE::Deref(addr_of_index("hm3", 11).boxed())) - .unwrap(); - assert_scalar( - &el_11[0], - "{unknown}", - "i32", - Some(SupportedScalar::I32(11)), - ); + read_var_dqe!(debugger, addr_of_index("hm3", 11) => addr_el_11); + assert_pointer(addr_el_11.value(), "&i32"); + read_var_dqe!(debugger, Dqe::Deref(addr_of_index("hm3", 11).boxed()) => el_11); + assert_scalar(el_11.value(), "i32", Some(SupportedScalar::I32(11))); // get address of global variable and deref it - let addr_glob_1 = debugger.read_variable(addr_of("GLOB_1", false)).unwrap(); - assert_pointer(&addr_glob_1[0], "{unknown}", "&&str"); - let glob_1 = debugger - .read_variable(DQE::Deref(addr_of("GLOB_1", false).boxed())) - .unwrap(); - assert_str(&glob_1[0], "{unknown}", "glob_1"); + read_var_dqe!(debugger, addr_of("GLOB_1", false) => addr_glob_1); + assert_pointer(addr_glob_1.value(), "&&str"); + read_var_dqe!(debugger, Dqe::Deref(addr_of("GLOB_1", false).boxed()) => glob_1); + assert_str(glob_1.value(), "glob_1"); + + debugger.continue_debugee().unwrap(); + assert_no_proc!(debugee_pid); +} + +#[test] +#[serial] +fn test_read_time() { + let process = prepare_debugee_process(VARS_APP, &[]); + let debugee_pid = process.pid(); + let info = TestInfo::default(); + let builder = DebuggerBuilder::new().with_hooks(TestHooks::new(info.clone())); + let mut debugger = builder.build(process).unwrap(); + + debugger.set_breakpoint_at_line("vars.rs", 529).unwrap(); + + debugger.start_debugee().unwrap(); + assert_eq!(info.line.take(), Some(529)); + + read_locals!(debugger => system_time, instant); + assert_idents!(system_time => "system_time", instant => "instant"); + assert_system_time(system_time.value(), (0, 0)); + assert_instant(instant.value()); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); diff --git a/tests/debugger/watchpoint.rs b/tests/debugger/watchpoint.rs index f3d8066..3556eba 100644 --- a/tests/debugger/watchpoint.rs +++ b/tests/debugger/watchpoint.rs @@ -4,33 +4,53 @@ use crate::{assert_no_proc, VARS_APP}; use crate::{prepare_debugee_process, CALCULATIONS_APP}; use bugstalker::debugger::address::RelocatedAddress; use bugstalker::debugger::register::debug::{BreakCondition, BreakSize}; -use bugstalker::debugger::variable::select::{VariableSelector, DQE}; -use bugstalker::debugger::variable::{PointerVariable, SupportedScalar, VariableIR}; +use bugstalker::debugger::variable::dqe::{Dqe, Selector}; +use bugstalker::debugger::variable::value::{PointerValue, SupportedScalar, Value}; use bugstalker::debugger::{Debugger, DebuggerBuilder}; use serial_test::serial; use BreakCondition::DataWrites; use BreakSize::Bytes8; -fn assert_old_new( +fn assert_old_new_inner( info: &TestInfo, - name: &str, + source_dqe: Option<&str>, r#type: &str, old: SupportedScalar, mb_new: Option, ) { let old_val = &info.old_value.take().unwrap(); - assert_scalar(old_val, name, r#type, Some(old)); + assert_eq!(info.wp_dqe_string.take().as_deref(), source_dqe); + assert_scalar(old_val, r#type, Some(old)); match mb_new { None => { assert!(&info.new_value.take().is_none()) } Some(new) => { let new_val = &info.new_value.take().unwrap(); - assert_scalar(new_val, name, r#type, Some(new)); + assert_scalar(new_val, r#type, Some(new)); } } } +fn assert_old_new( + info: &TestInfo, + source_dqe: &str, + r#type: &str, + old: SupportedScalar, + mb_new: Option, +) { + assert_old_new_inner(info, Some(source_dqe), r#type, old, mb_new) +} + +fn assert_old_new_from_addr( + info: &TestInfo, + r#type: &str, + old: SupportedScalar, + mb_new: Option, +) { + assert_old_new_inner(info, None, r#type, old, mb_new) +} + #[test] #[serial] fn test_watchpoint_works() { @@ -43,7 +63,7 @@ fn test_watchpoint_works() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(7)); - let wp_dqe = DQE::Variable(VariableSelector::by_name("int8", true)); + let wp_dqe = Dqe::Variable(Selector::by_name("int8", true)); debugger .set_watchpoint_on_expr("int8", wp_dqe, DataWrites) .unwrap(); @@ -51,7 +71,7 @@ fn test_watchpoint_works() { debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(8)); let var_val = &info.new_value.take().unwrap(); - assert_scalar(var_val, "int8", "i8", Some(SupportedScalar::I8(1))); + assert_scalar(var_val, "i8", Some(SupportedScalar::I8(1))); assert!(info.file.take().unwrap().contains("vars.rs")); debugger.continue_debugee().unwrap(); @@ -76,7 +96,7 @@ fn test_watchpoint_works_2() { dbg.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(8)); - let wp_dqe = DQE::Variable(VariableSelector::by_name("int8", true)); + let wp_dqe = Dqe::Variable(Selector::by_name("int8", true)); dbg.set_watchpoint_on_expr("int8", wp_dqe, DataWrites) .unwrap(); @@ -113,19 +133,19 @@ fn test_watchpoint_global_var() { dbg.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(108)); - let wp_dqe = DQE::Variable(VariableSelector::by_name("GLOBAL_1", false)); + let wp_dqe = Dqe::Variable(Selector::by_name("GLOBAL_1", false)); dbg.set_watchpoint_on_expr("GLOBAL_1", wp_dqe, DataWrites) .unwrap(); dbg.continue_debugee().unwrap(); let (old, new) = (SupportedScalar::I64(1), Some(SupportedScalar::I64(0))); - assert_old_new(&info, "calculations::GLOBAL_1", "i64", old, new); + assert_old_new(&info, "GLOBAL_1", "i64", old, new); dbg.continue_debugee().unwrap(); let (old, new) = (SupportedScalar::I64(0), Some(SupportedScalar::I64(3))); - assert_old_new(&info, "calculations::GLOBAL_1", "i64", old, new); + assert_old_new(&info, "GLOBAL_1", "i64", old, new); dbg.continue_debugee().unwrap(); let (old, new) = (SupportedScalar::I64(3), Some(SupportedScalar::I64(1))); - assert_old_new(&info, "calculations::GLOBAL_1", "i64", old, new); + assert_old_new(&info, "GLOBAL_1", "i64", old, new); dbg.continue_debugee().unwrap(); // watchpoint at global variables never removed automatically @@ -146,13 +166,13 @@ fn test_max_watchpoint_count() { dbg.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(22)); - let wp_dqe = DQE::Variable(VariableSelector::by_name("a", false)); + let wp_dqe = Dqe::Variable(Selector::by_name("a", false)); dbg.set_watchpoint_on_expr("a", wp_dqe, DataWrites).unwrap(); - let wp_dqe = DQE::Variable(VariableSelector::by_name("b", false)); + let wp_dqe = Dqe::Variable(Selector::by_name("b", false)); dbg.set_watchpoint_on_expr("b", wp_dqe, DataWrites).unwrap(); - let wp_dqe = DQE::Variable(VariableSelector::by_name("c", false)); + let wp_dqe = Dqe::Variable(Selector::by_name("c", false)); dbg.set_watchpoint_on_expr("c", wp_dqe, DataWrites).unwrap(); - let wp_dqe = DQE::Variable(VariableSelector::by_name("d", false)); + let wp_dqe = Dqe::Variable(Selector::by_name("d", false)); dbg.set_watchpoint_on_expr("d", wp_dqe, DataWrites).unwrap(); dbg.continue_debugee().unwrap(); @@ -193,10 +213,10 @@ fn test_watchpoint_remove_and_continue() { dbg.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(22)); - let a_wp_dqe = DQE::Variable(VariableSelector::by_name("a", false)); + let a_wp_dqe = Dqe::Variable(Selector::by_name("a", false)); dbg.set_watchpoint_on_expr("a", a_wp_dqe.clone(), DataWrites) .unwrap(); - let d_wp_dqe = DQE::Variable(VariableSelector::by_name("d", false)); + let d_wp_dqe = Dqe::Variable(Selector::by_name("d", false)); dbg.set_watchpoint_on_expr("d", d_wp_dqe, DataWrites) .unwrap(); @@ -229,9 +249,9 @@ fn test_watchpoint_global_var_multithread() { dbg.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(56)); - let wp_dqe = DQE::Field( - DQE::Field( - DQE::Variable(VariableSelector::by_name("GLOBAL_2", false)).boxed(), + let wp_dqe = Dqe::Field( + Dqe::Field( + Dqe::Variable(Selector::by_name("GLOBAL_2", false)).boxed(), "data".to_string(), ) .boxed(), @@ -272,7 +292,7 @@ fn test_watchpoint_local_var_multithread() { dbg.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(67)); - let wp_dqe = DQE::Variable(VariableSelector::by_name("a", false)); + let wp_dqe = Dqe::Variable(Selector::by_name("a", false)); dbg.set_watchpoint_on_expr("a", wp_dqe, DataWrites).unwrap(); dbg.continue_debugee().unwrap(); @@ -304,18 +324,12 @@ fn test_max_watchpoint_count_at_address() { assert_eq!(info.line.take(), Some(22)); fn get_ptr_of(dbg: &Debugger, var: &str) -> RelocatedAddress { - let addr_dqe = DQE::Address( - DQE::Variable(VariableSelector::Name { - var_name: var.to_string(), - only_local: true, - }) - .boxed(), - ); + let addr_dqe = Dqe::Address(Dqe::Variable(Selector::by_name(var, true)).boxed()); let var = dbg.read_variable(addr_dqe).unwrap(); - let VariableIR::Pointer(PointerVariable { value: Some(p), .. }) = var[0] else { + let Value::Pointer(PointerValue { value: Some(p), .. }) = var[0].value() else { panic!("not a pointer") }; - RelocatedAddress::from(p as usize) + RelocatedAddress::from(*p as usize) } let ptr_a = get_ptr_of(&dbg, "a"); @@ -331,16 +345,16 @@ fn test_max_watchpoint_count_at_address() { dbg.continue_debugee().unwrap(); let (old, new) = (SupportedScalar::U64(1), Some(SupportedScalar::U64(6))); - assert_old_new(&info, "data", "u64", old, new); + assert_old_new_from_addr(&info, "u64", old, new); dbg.continue_debugee().unwrap(); let (old, new) = (SupportedScalar::U64(2), Some(SupportedScalar::U64(3))); - assert_old_new(&info, "data", "u64", old, new); + assert_old_new_from_addr(&info, "u64", old, new); dbg.continue_debugee().unwrap(); let (old, new) = (SupportedScalar::U64(3), Some(SupportedScalar::U64(1))); - assert_old_new(&info, "data", "u64", old, new); + assert_old_new_from_addr(&info, "u64", old, new); dbg.continue_debugee().unwrap(); let (old, new) = (SupportedScalar::U64(4), Some(SupportedScalar::U64(3))); - assert_old_new(&info, "data", "u64", old, new); + assert_old_new_from_addr(&info, "u64", old, new); dbg.remove_watchpoint_by_addr(ptr_a).unwrap(); dbg.remove_watchpoint_by_addr(ptr_b).unwrap(); @@ -364,7 +378,7 @@ fn test_watchpoint_argument() { dbg.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(101)); - let wp_dqe = DQE::Variable(VariableSelector::by_name("arg", false)); + let wp_dqe = Dqe::Variable(Selector::by_name("arg", false)); dbg.set_watchpoint_on_expr("arg", wp_dqe, DataWrites) .unwrap(); diff --git a/tests/integration/test_variables.py b/tests/integration/test_variables.py index 1aaaa9e..92a76ed 100644 --- a/tests/integration/test_variables.py +++ b/tests/integration/test_variables.py @@ -190,16 +190,16 @@ def test_deref_pointers(self): self.debugger.cmd('break vars.rs:119', 'New breakpoint') self.debugger.cmd('run', '119 let nop: Option = None;') - self.debugger.cmd('var *ref_a', '*ref_a = i32(2)') - self.debugger.cmd('var *ptr_a', '*ptr_a = i32(2)') - self.debugger.cmd_re('var *ptr_ptr_a', r'\*ptr_ptr_a = \*const i32 \[0x[0-9A-F]{14}\]') - self.debugger.cmd('var **ptr_ptr_a', '**ptr_ptr_a = i32(2)') - self.debugger.cmd('var *mut_ref_b', '*mut_ref_b = i32(2)') - self.debugger.cmd('var *mut_ptr_c', '*mut_ptr_c = i32(2)') - self.debugger.cmd('var *box_d', '*box_d = i32(2)') + self.debugger.cmd('var *ref_a', 'i32(2)') + self.debugger.cmd('var *ptr_a', 'i32(2)') + self.debugger.cmd_re('var *ptr_ptr_a', r'\*const i32 \[0x[0-9A-F]{14}\]') + self.debugger.cmd('var **ptr_ptr_a', 'i32(2)') + self.debugger.cmd('var *mut_ref_b', 'i32(2)') + self.debugger.cmd('var *mut_ptr_c', 'i32(2)') + self.debugger.cmd('var *box_d', 'i32(2)') self.debugger.cmd_re( 'var *ref_f', - r'\*ref_f = Foo {', + r'Foo {', r'baz: \[i32\] {', r'0: i32\(1\)', r'1: i32\(2\)', @@ -269,7 +269,7 @@ def test_read_vec_and_slice(self): self.debugger.cmd( 'var *slice1', - '*slice1 = [i32]', + '[i32]', '0: i32(1)', '1: i32(2)', '2: i32(3)', @@ -278,7 +278,7 @@ def test_read_vec_and_slice(self): self.debugger.cmd_re( 'var *slice2', - r'\*slice2 = \[&\[i32; 3\]\] {', + r'\[&\[i32; 3\]\] {', r'0: &\[i32; 3\] \[0x[0-9A-F]{14}\]', r'1: &\[i32; 3\] \[0x[0-9A-F]{14}\]', '}', @@ -286,7 +286,7 @@ def test_read_vec_and_slice(self): self.debugger.cmd( 'var *(*slice2)[0]', - '*0 = [i32] {', + '[i32] {', '0: i32(1)', '1: i32(2)', '2: i32(3)', @@ -295,7 +295,7 @@ def test_read_vec_and_slice(self): self.debugger.cmd( 'var *(*slice2)[1]', - '*1 = [i32] {', + '[i32] {', '0: i32(1)', '1: i32(2)', '2: i32(3)', @@ -350,17 +350,17 @@ def test_custom_select(self): """Reading memory by select expressions""" self.debugger.cmd('break vars.rs:61', 'New breakpoint') self.debugger.cmd('run', '61 let nop: Option = None;') - self.debugger.cmd('var arr_2[0][2]', '2 = i32(2)') + self.debugger.cmd('var arr_2[0][2]', 'i32(2)') self.debugger.cmd( 'var arr_1[2..4]', - 'arr_1 = [i32] {', + '[i32] {', '2: i32(2)', '3: i32(-2)', '}', ) self.debugger.cmd( 'var arr_1[..]', - 'arr_1 = [i32] {', + '[i32] {', '0: i32(1)', '1: i32(-1)', '2: i32(2)', @@ -370,44 +370,44 @@ def test_custom_select(self): ) self.debugger.cmd( 'var arr_1[..2]', - 'arr_1 = [i32] {', + '[i32] {', '0: i32(1)', '1: i32(-1)', '}', ) self.debugger.cmd( 'var arr_1[3..]', - 'arr_1 = [i32] {', + '[i32] {', '3: i32(-2)', '4: i32(3)', '}', ) self.debugger.cmd( 'var arr_1[4..6]', - 'arr_1 = [i32] {', + '[i32] {', '4: i32(3)', '}', ) self.debugger.cmd( 'var arr_1[2..4][1..]', - 'arr_1 = [i32] {', + '[i32] {', '3: i32(-2)', '}', ) self.debugger.cmd('break vars.rs:93', 'New breakpoint') self.debugger.cmd('continue', '93 let nop: Option = None;') - self.debugger.cmd('var enum_6.0.a', 'a = i32(1)') + self.debugger.cmd('var enum_6.__0.a', 'i32(1)') self.debugger.cmd('break vars.rs:119', 'New breakpoint') self.debugger.cmd('continue', '119 let nop: Option = None;') - self.debugger.cmd('var *((*ref_f).foo)', '*foo = i32(2)') + self.debugger.cmd('var *((*ref_f).foo)', 'i32(2)') self.debugger.cmd('break vars.rs:290', 'New breakpoint') self.debugger.cmd('continue', '290 let nop: Option = None;') self.debugger.cmd( 'var hm2.abc', - 'abc = Vec {', + 'Vec {', 'buf: [i32] {', '1: i32(2)', '2: i32(3)', @@ -415,31 +415,31 @@ def test_custom_select(self): 'cap: usize(3)', '}', ) - self.debugger.cmd('var hm1[false]', 'value = i64(5)') - self.debugger.cmd('var hm2["abc"]', 'value = Vec {') - self.debugger.cmd('var hm3[55]', 'value = i32(55)') - self.debugger.cmd('var hm4["1"][1]', 'value = i32(1)') + self.debugger.cmd('var hm1[false]', 'i64(5)') + self.debugger.cmd('var hm2["abc"]', 'Vec {') + self.debugger.cmd('var hm3[55]', 'i32(55)') + self.debugger.cmd('var hm4["1"][1]', 'i32(1)') self.debugger.cmd('var a') addr = self.debugger.search_in_output(r'a = &i32 \[(.*)\]') - self.debugger.cmd(f'var hm5[{addr}]', 'value = &str(a)') + self.debugger.cmd(f'var hm5[{addr}]', '&str(a)') self.debugger.cmd('break vars.rs:307', 'New breakpoint') self.debugger.cmd('continue', '307 let nop: Option = None;') - self.debugger.cmd('var hs1[1]', 'contains = bool(true)') - self.debugger.cmd('var hs2[22]', 'contains = bool(true)') - self.debugger.cmd('var hs2[222]', 'contains = bool(false)') + self.debugger.cmd('var hs1[1]', 'bool(true)') + self.debugger.cmd('var hs2[22]', 'bool(true)') + self.debugger.cmd('var hs2[222]', 'bool(false)') self.debugger.cmd('var b') addr = self.debugger.search_in_output(r'b = &i32 \[(.*)\]') - self.debugger.cmd(f'var hs4[{addr}]', 'contains = bool(true)') - self.debugger.cmd('var hs4[0x000]', 'contains = bool(false)') + self.debugger.cmd(f'var hs4[{addr}]', 'bool(true)') + self.debugger.cmd('var hs4[0x000]', 'bool(false)') self.debugger.cmd('break vars.rs:460', 'New breakpoint') self.debugger.cmd('continue', '460 let nop: Option = None;') self.debugger.cmd( 'var ptr[..4]', - '[*ptr] = [i32] {', + '[i32] {', '0: i32(1)', '1: i32(2)', '2: i32(3)', @@ -548,7 +548,7 @@ def test_ptr_cast(self): self.debugger.cmd('var ref_a') addr = self.debugger.search_in_output(r'ref_a = &i32 \[0x(.*)\]') addr = "0x" + addr[:14] - self.debugger.cmd(f'var *(*const i32){addr}', '{unknown} = i32(2)') + self.debugger.cmd(f'var *(*const i32){addr}', 'i32(2)') def test_address(self): """Test address operator""" @@ -563,12 +563,12 @@ def test_address(self): self.debugger.cmd( 'var *&tuple_1', '(f64, f64) {', - '0: f64(0)', - '1: f64(1.1)', + '__0: f64(0)', + '__1: f64(1.1)', '}', ) - self.debugger.cmd_re('var &tuple_1.0', r'&f64 \[0x[0-9A-F]{14}\]') - self.debugger.cmd('var *&tuple_1.0', 'f64(0)') + self.debugger.cmd_re('var &tuple_1.__0', r'&f64 \[0x[0-9A-F]{14}\]') + self.debugger.cmd('var *&tuple_1.__0', 'f64(0)') self.debugger.cmd_re('var &foo2.foo.bar', r'&i32 \[0x[0-9A-F]{14}\]') self.debugger.cmd('var *&foo2.foo.bar', 'i32(100)') @@ -599,3 +599,10 @@ def test_address(self): self.debugger.cmd('continue', 'let nop: Option = None;') self.debugger.cmd_re('var &hm6[{field_1: 1, field_2: *, field_3: *}]', r'&i32 \[0x[0-9A-F]{14}\]') self.debugger.cmd('var *&hm6[{field_1: 1, field_2: *, field_3: *}]', 'i32(1)') + + def test_read_time(self): + """Read Instant and System type std types""" + self.debugger.cmd('break vars.rs:529', 'New breakpoint') + self.debugger.cmd('run', 'let nop: Option = None;') + self.debugger.cmd('var system_time', 'system_time = SystemTime(1970-01-01 00:00:00)') + self.debugger.cmd_re('var instant', r'instant = Instant\(\d+ seconds from now\)') diff --git a/tests/integration/test_watchpoint.py b/tests/integration/test_watchpoint.py index 8e31086..163ff07 100644 --- a/tests/integration/test_watchpoint.py +++ b/tests/integration/test_watchpoint.py @@ -15,12 +15,12 @@ def test_watchpoint(self): self.debugger.cmd('watch c', 'New watchpoint') self.debugger.cmd( 'continue', - 'Hit watchpoint', - 'old value: c = u64(3)', - 'new value: c = u64(1)', + 'Hit watchpoint 1 (expr: c) (w)', + 'old value: u64(3)', + 'new value: u64(1)', ) - self.debugger.cmd('continue', 'Watchpoint 1 end of scope', 'old value: c = u64(1)') + self.debugger.cmd('continue', 'Watchpoint 1 (expr: c) end of scope', 'old value: u64(1)') def test_watchpoint_at_field(self): """Add a new watchpoint for structure field or value in vector and check it works""" @@ -30,19 +30,19 @@ def test_watchpoint_at_field(self): self.debugger.cmd('watch vector[2]', 'New watchpoint') self.debugger.cmd( 'continue', - 'Hit watchpoint', - 'old value: vector[2] = i32(3)', - 'new value: vector[2] = i32(4)', + 'Hit watchpoint 1 (expr: vector[2]) (w)', + 'old value: i32(3)', + 'new value: i32(4)', ) self.debugger.cmd('continue', 'Hit breakpoint 2') self.debugger.cmd('watch s.b', 'New watchpoint') - self.debugger.cmd('continue', 'old value: s.b = f64(1)', 'new value: s.b = f64(2)') + self.debugger.cmd('continue', 'old value: f64(1)', 'new value: f64(2)') self.debugger.cmd( 'continue', - 'Watchpoint 1 end of scope', - 'old value: vector[2] = i32(4)', - 'Watchpoint 2 end of scope', - 'old value: s.b = f64(2)', + 'Watchpoint 1 (expr: vector[2]) end of scope', + 'old value: i32(4)', + 'Watchpoint 2 (expr: s.b) end of scope', + 'old value: f64(2)', ) def test_watchpoint_at_address(self): @@ -51,14 +51,14 @@ def test_watchpoint_at_address(self): self.debugger.cmd('run', 'Hit breakpoint 1') self.debugger.cmd('var &a') - addr = self.debugger.search_in_output(r'= &u64 \[0x(.*)\]') + addr = self.debugger.search_in_output(r'&u64 \[0x(.*)\]') addr = "0x" + addr[:14] self.debugger.cmd(f'watch {addr}:8', 'New watchpoint') self.debugger.cmd( 'continue', 'Hit watchpoint', - 'old value: data = u64(1)', - 'new value: data = u64(6)', + 'old value: u64(1)', + 'new value: u64(6)', ) self.debugger.cmd('q') @@ -67,11 +67,11 @@ def test_watchpoint_with_stepping(self): self.debugger.cmd('break calculations.rs:12', 'New breakpoint') self.debugger.cmd('run', 'Hit breakpoint 1') self.debugger.cmd('watch int8', 'New watchpoint') - self.debugger.cmd('next', 'Hit watchpoint') + self.debugger.cmd('next', 'Hit watchpoint 1 (expr: int8)') self.debugger.cmd('next', '13 println!("{int8}");') self.debugger.cmd('next') self.debugger.cmd('next') - self.debugger.cmd('next', 'Watchpoint 1 end of scope') + self.debugger.cmd('next', 'Watchpoint 1 (expr: int8) end of scope') def test_watchpoint_at_undefined_value(self): """Trying to set watchpoint for undefined value""" @@ -127,32 +127,32 @@ def test_watchpoint_rw(self): self.debugger.cmd('break calculations.rs:20', 'New breakpoint') self.debugger.cmd('run', 'Hit breakpoint 1') self.debugger.cmd('watch +rw a', 'New watchpoint') - self.debugger.cmd('continue', 'Hit watchpoint 1 (rw)', 'value: a = u64(1)') + self.debugger.cmd('continue', 'Hit watchpoint 1 (expr: a) (rw)', 'value: u64(1)') self.debugger.cmd( 'continue', - 'Hit watchpoint 1 (rw)', - 'old value: a = u64(1)', - 'new value: a = u64(6)', + 'Hit watchpoint 1 (expr: a) (rw)', + 'old value: u64(1)', + 'new value: u64(6)', ) - self.debugger.cmd('continue', 'Hit watchpoint 1 (rw)', 'value: a = u64(6)') - self.debugger.cmd('continue', 'Watchpoint 1 end of scope', 'old value: a = u64(6)') + self.debugger.cmd('continue', 'Hit watchpoint 1 (expr: a) (rw)', 'value: u64(6)') + self.debugger.cmd('continue', 'Watchpoint 1 (expr: a) end of scope', 'old value: u64(6)') def test_watchpoint_at_addr_rw(self): """Add a new watchpoint with read-write condition at address and check it works""" self.debugger.cmd('break calculations.rs:20', 'New breakpoint') self.debugger.cmd('run', 'Hit breakpoint 1') self.debugger.cmd('var &a') - addr = self.debugger.search_in_output(r'= &u64 \[0x(.*)\]') + addr = self.debugger.search_in_output(r'&u64 \[0x(.*)\]') addr = "0x" + addr[:14] self.debugger.cmd(f'watch +rw {addr}:8', 'New watchpoint') - self.debugger.cmd('continue', 'Hit watchpoint 1 (rw)', 'value: data = u64(1)') + self.debugger.cmd('continue', 'Hit watchpoint 1 (rw)', 'value: u64(1)') self.debugger.cmd( 'continue', 'Hit watchpoint 1 (rw)', - 'old value: data = u64(1)', - 'new value: data = u64(6)', + 'old value: u64(1)', + 'new value: u64(6)', ) - self.debugger.cmd('continue', 'Hit watchpoint 1 (rw)', 'value: data = u64(6)') + self.debugger.cmd('continue', 'Hit watchpoint 1 (rw)', 'value: u64(6)') def test_watchpoint_at_complex_data_types(self): """Add watchpoints for vector attribute""" @@ -161,20 +161,20 @@ def test_watchpoint_at_complex_data_types(self): self.debugger.cmd('watch (~vector2).len', 'New watchpoint') self.debugger.cmd( 'continue', - 'Hit watchpoint 1 (w)', - 'old value: (~vector2).len = usize(2)', - 'new value: (~vector2).len = usize(3)', + 'Hit watchpoint 1 (expr: (~vector2).len) (w)', + 'old value: usize(2)', + 'new value: usize(3)', ) self.debugger.cmd( 'continue', - 'Hit watchpoint 1 (w)', - 'old value: (~vector2).len = usize(3)', - 'new value: (~vector2).len = usize(4)', + 'Hit watchpoint 1 (expr: (~vector2).len) (w)', + 'old value: usize(3)', + 'new value: usize(4)', ) self.debugger.cmd( 'continue', - 'Watchpoint 1 end of scope', - 'old value: (~vector2).len = usize(4)', + 'Watchpoint 1 (expr: (~vector2).len) end of scope', + 'old value: usize(4)', ) def test_watchpoint_at_complex_data_types2(self): @@ -184,12 +184,12 @@ def test_watchpoint_at_complex_data_types2(self): self.debugger.cmd('watch (~(~string).vec).len', 'New watchpoint') self.debugger.cmd( 'continue', - 'Hit watchpoint 1 (w)', - 'old value: (~(~string).vec).len = usize(3)', - 'new value: (~(~string).vec).len = usize(7)', + 'Hit watchpoint 1 (expr: (~(~string).vec).len) (w)', + 'old value: usize(3)', + 'new value: usize(7)', ) self.debugger.cmd( 'continue', - 'Watchpoint 1 end of scope', - 'old value: (~(~string).vec).len = usize(7)', + 'Watchpoint 1 (expr: (~(~string).vec).len) end of scope', + 'old value: usize(7)', )