From ef1ad904474a445f3e3ff6e688e71d473667cd91 Mon Sep 17 00:00:00 2001 From: Ridwan Abdilahi Date: Fri, 2 Sep 2022 12:54:27 -0700 Subject: [PATCH 1/3] Add Natvis visualizations and tests for `ArrayVec` and `SliceVec`. --- .github/workflows/rust.yml | 18 +++++- Cargo.toml | 13 ++++ debug_metadata/README.md | 111 ++++++++++++++++++++++++++++++++++ debug_metadata/tinyvec.natvis | 19 ++++++ src/lib.rs | 5 ++ tests/debugger_visualizer.rs | 84 +++++++++++++++++++++++++ 6 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 debug_metadata/README.md create mode 100644 debug_metadata/tinyvec.natvis create mode 100644 tests/debugger_visualizer.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b05d26e..480c438 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -6,15 +6,21 @@ on: jobs: build_test: - runs-on: ubuntu-latest strategy: matrix: + os: [ubuntu-latest] rust: - 1.34.0 - 1.36.0 - stable - beta - nightly + include: + -rust: nightly + -os: windows-latest + + runs-on: ${{ matrix.os }} + steps: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 @@ -45,3 +51,13 @@ jobs: with: command: test args: --all-features + # The #[debugger_visualizer] attribute is currently gated behind an unstable feature flag. + # In order to test the visualizers for the tinyvec crate, they have to be tested on a nightly build. + - name: Test debugger_visualizer feature on Nightly + if: | + matrix.os == 'windows-latest' && + matrix.rust == 'nightly' + uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features --test debugger_visualizer -- --test-threads=1 diff --git a/Cargo.toml b/Cargo.toml index b18ea9e..79d6eba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,11 @@ rustc_1_57 = ["rustc_1_55"] # https://github.com/rust-lang/rust/issues/54279 nightly_slice_partition_dedup = [] +# allow use of nightly feature `debugger_visualizer`, +# will become useless once that is stabilized: +# https://github.com/rust-lang/rust/issues/95939 +debugger_visualizer = [] + # EXPERIMENTAL: Not part of SemVer. It adds `core::fmt::Write` to `ArrayVec` # and `SliceVec`. It works on Stable Rust, but Vec normally supports the # `std::io::Write` trait instead of `core::fmt::Write`, so we're keeping it as @@ -78,6 +83,8 @@ members = ["fuzz"] criterion = "0.3.0" serde_test = "1.0" smallvec = "1" +debugger_test = "0.1" +debugger_test_parser = "0.1" [[test]] name = "tinyvec" @@ -92,3 +99,9 @@ required-features = ["alloc"] name = "smallvec" harness = false required-features = ["alloc", "real_blackbox"] + +[[test]] +path = "tests/debugger_visualizer.rs" +name = "debugger_visualizer" +required-features = ["debugger_visualizer"] +test = false diff --git a/debug_metadata/README.md b/debug_metadata/README.md new file mode 100644 index 0000000..dda209a --- /dev/null +++ b/debug_metadata/README.md @@ -0,0 +1,111 @@ +## Debugger Visualizers + +Many languages and debuggers enable developers to control how a type is +displayed in a debugger. These are called "debugger visualizations" or "debugger +views". + +The Windows debuggers (WinDbg\CDB) support defining custom debugger visualizations using +the `Natvis` framework. To use Natvis, developers write XML documents using the natvis +schema that describe how debugger types should be displayed with the `.natvis` extension. +(See: https://docs.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects?view=vs-2019) +The Natvis files provide patterns which match type names a description of how to display +those types. + +The Natvis schema can be found either online (See: https://code.visualstudio.com/docs/cpp/natvis#_schema) +or locally at `\Xml\Schemas\1033\natvis.xsd`. + +The GNU debugger (GDB) supports defining custom debugger views using Pretty Printers. +Pretty printers are written as python scripts that describe how a type should be displayed +when loaded up in GDB/LLDB. (See: https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing.html#Pretty-Printing) +The pretty printers provide patterns, which match type names, and for matching +types, descibe how to display those types. (For writing a pretty printer, see: https://sourceware.org/gdb/onlinedocs/gdb/Writing-a-Pretty_002dPrinter.html#Writing-a-Pretty_002dPrinter). + +### Embedding Visualizers + +Through the use of the currently unstable `#[debugger_visualizer]` attribute, the `tinyvec` +crate can embed debugger visualizers into the crate metadata. + +Currently the two types of visualizers supported are Natvis and Pretty printers. + +For Natvis files, when linking an executable with a crate that includes Natvis files, +the MSVC linker will embed the contents of all Natvis files into the generated `PDB`. + +For pretty printers, the compiler will encode the contents of the pretty printer +in the `.debug_gdb_scripts` section of the `ELF` generated. + +### Testing Visualizers + +The `tinyvec` crate supports testing debugger visualizers defined for this crate. The entry point for +these tests are `tests/debugger_visualizer.rs`. These tests are defined using the `debugger_test` and +`debugger_test_parser` crates. The `debugger_test` crate is a proc macro crate which defines a +single proc macro attribute, `#[debugger_test]`. For more detailed information about this crate, +see https://crates.io/crates/debugger_test. The CI pipeline for the `tinyvec` crate has been updated +to run the debugger visualizer tests to ensure debugger visualizers do not become broken/stale. + +The `#[debugger_test]` proc macro attribute may only be used on test functions and will run the +function under the debugger specified by the `debugger` meta item. + +This proc macro attribute has 3 required values: + +1. The first required meta item, `debugger`, takes a string value which specifies the debugger to launch. +2. The second required meta item, `commands`, takes a string of new line (`\n`) separated list of debugger +commands to run. +3. The third required meta item, `expected_statements`, takes a string of new line (`\n`) separated list of +statements that must exist in the debugger output. Pattern matching through regular expressions is also +supported by using the `pattern:` prefix for each expected statement. + +#### Example: + +```rust +#[debugger_test( + debugger = "cdb", + commands = "command1\ncommand2\ncommand3", + expected_statements = "statement1\nstatement2\nstatement3")] +fn test() { + +} +``` + +Using a multiline string is also supported, with a single debugger command/expected statement per line: + +```rust +#[debugger_test( + debugger = "cdb", + commands = " +command1 +command2 +command3", + expected_statements = " +statement1 +pattern:statement[0-9]+ +statement3")] +fn test() { + +} +``` + +In the example above, the second expected statement uses pattern matching through a regular expression +by using the `pattern:` prefix. + +#### Testing Locally + +Currently, only Natvis visualizations have been defined for the `tinyvec` crate via `debug_metadata/tinyvec.natvis`, +which means the `tests/debugger_visualizer.rs` tests need to be run on Windows using the `*-pc-windows-msvc` targets. +To run these tests locally, first ensure the debugging tools for Windows are installed or install them following +the steps listed here, [Debugging Tools for Windows](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/). +Once the debugging tools have been installed, the tests can be run in the same manner as they are in the CI +pipeline. + +#### Note + +When running the debugger visualizer tests, `tests/debugger_visualizer.rs`, they need to be run consecutively +and not in parallel. This can be achieved by passing the flag `--test-threads=1` to rustc. This is due to +how the debugger tests are run. Each test marked with the `#[debugger_test]` attribute launches a debugger +and attaches it to the current test process. If tests are running in parallel, the test will try to attach +a debugger to the current process which may already have a debugger attached causing the test to fail. + +For example: + +``` +cargo test --test debugger_visualizer --features debugger_visualizer -- --test-threads=1 +``` diff --git a/debug_metadata/tinyvec.natvis b/debug_metadata/tinyvec.natvis new file mode 100644 index 0000000..61d338a --- /dev/null +++ b/debug_metadata/tinyvec.natvis @@ -0,0 +1,19 @@ + + + {{ len={len} }} + + len + $T2 + + len + ($T1*)data + + + + + + + data + + + \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 68a7036..a7ee9d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,11 @@ feature = "nightly_slice_partition_dedup", feature(slice_partition_dedup) )] +#![cfg_attr( + feature = "debugger_visualizer", + feature(debugger_visualizer), + debugger_visualizer(natvis_file = "../debug_metadata/tinyvec.natvis") +)] #![cfg_attr(docs_rs, feature(doc_cfg))] #![warn(clippy::missing_inline_in_public_items)] #![warn(clippy::must_use_candidate)] diff --git a/tests/debugger_visualizer.rs b/tests/debugger_visualizer.rs new file mode 100644 index 0000000..abb38d2 --- /dev/null +++ b/tests/debugger_visualizer.rs @@ -0,0 +1,84 @@ +use debugger_test::debugger_test; +use tinyvec::*; + +#[inline(never)] +fn __break() { + println!("breakpoint hit"); +} + +#[debugger_test( + debugger = "cdb", + commands = r#" +dx strings +dx inline_tv +dx inline_tv.__0 +g +dx slice_vec +g +dx strings +"#, + expected_statements = r#" +strings : { len=0x3 } [Type: tinyvec::arrayvec::ArrayVec >] + [] [Type: tinyvec::arrayvec::ArrayVec >] + [len] : 0x3 [Type: unsigned short] + [capacity] : 7 + [0] : "a" [Type: str] + [1] : "b" [Type: str] + [2] : "c" [Type: str] + +inline_tv : Inline [Type: enum2$ > >] + [] [Type: enum2$ > >] + [+0x004] __0 : { len=0x4 } [Type: tinyvec::arrayvec::ArrayVec >] + +inline_tv.__0 : { len=0x4 } [Type: tinyvec::arrayvec::ArrayVec >] + [] [Type: tinyvec::arrayvec::ArrayVec >] + [len] : 0x4 [Type: unsigned short] + [capacity] : 4 + [0] : 1 [Type: i32] + [1] : 2 [Type: i32] + [2] : 3 [Type: i32] + [3] : 4 [Type: i32] + +strings : { len=0x6 } [Type: tinyvec::arrayvec::ArrayVec >] + [] [Type: tinyvec::arrayvec::ArrayVec >] + [len] : 0x6 [Type: unsigned short] + [capacity] : 7 + [0] : "a" [Type: str] + [1] : "b" [Type: str] + [2] : "d" [Type: str] + [3] : "e" [Type: str] + [4] : "f" [Type: str] + [5] : "g" [Type: str] +"# +)] +#[inline(never)] +fn test_debugger_visualizer() { + let mut strings = ArrayVec::<[&str; 7]>::default(); + strings.push("a"); + strings.push("b"); + strings.push("c"); + assert_eq!(["a", "b", "c"], &strings[..]); + + let mut inline_tv = tiny_vec!([i32; 4] => 1, 2, 3); + assert!(inline_tv.is_inline()); + + inline_tv.push(4); + __break(); + + { + let mut slice_vec = SliceVec::from(strings.as_mut_slice()); + assert_eq!(3, slice_vec.capacity()); + assert_eq!("c", slice_vec.remove(2)); + slice_vec.push("d"); + println!("{:?}", slice_vec); + __break(); + + assert_eq!(["a", "b", "d"], &slice_vec[..]); + } + + strings.push("e"); + strings.push("f"); + strings.push("g"); + assert_eq!(["a", "b", "d", "e", "f", "g"], &strings[..]); + __break(); +} From 0476fd743858d1a9b8037f791683beef74b338f9 Mon Sep 17 00:00:00 2001 From: Ridwan Abdilahi Date: Fri, 2 Sep 2022 13:40:59 -0700 Subject: [PATCH 2/3] CI yaml cleanups. --- .github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 480c438..f8bcda5 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,8 +16,8 @@ jobs: - beta - nightly include: - -rust: nightly - -os: windows-latest + - rust: nightly + os: windows-latest runs-on: ${{ matrix.os }} From 0d012f9aec2a9c2569d8a7b81d53d6a55bf2a772 Mon Sep 17 00:00:00 2001 From: Ridwan Abdilahi Date: Tue, 6 Sep 2022 13:58:08 -0700 Subject: [PATCH 3/3] Respond to PR comments, cleanup Natvis definitions. --- debug_metadata/tinyvec.natvis | 7 ++++++- tests/debugger_visualizer.rs | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/debug_metadata/tinyvec.natvis b/debug_metadata/tinyvec.natvis index 61d338a..873581d 100644 --- a/debug_metadata/tinyvec.natvis +++ b/debug_metadata/tinyvec.natvis @@ -12,8 +12,13 @@ + {{ len={len} }} - data + len + + len + data.data_ptr + \ No newline at end of file diff --git a/tests/debugger_visualizer.rs b/tests/debugger_visualizer.rs index abb38d2..d41e255 100644 --- a/tests/debugger_visualizer.rs +++ b/tests/debugger_visualizer.rs @@ -39,6 +39,13 @@ inline_tv.__0 : { len=0x4 } [Type: tinyvec::arrayvec::ArrayVec [2] : 3 [Type: i32] [3] : 4 [Type: i32] +slice_vec : { len=0x3 } [Type: tinyvec::slicevec::SliceVec] + [] [Type: tinyvec::slicevec::SliceVec] + [len] : 0x3 [Type: unsigned __int64] + [0] : "a" [Type: str] + [1] : "b" [Type: str] + [2] : "d" [Type: str] + strings : { len=0x6 } [Type: tinyvec::arrayvec::ArrayVec >] [] [Type: tinyvec::arrayvec::ArrayVec >] [len] : 0x6 [Type: unsigned short]