Skip to content

Commit

Permalink
Add Natvis definitions and tests for SmallVec type with and without t…
Browse files Browse the repository at this point in the history
…he `union` crate feature enabled.

Unify Natvis definitions into a single Natvis file.
  • Loading branch information
Ridwan Abdilahi authored and mbrubeck committed Sep 12, 2022
1 parent 7fa951f commit 09b4988
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 5 deletions.
23 changes: 18 additions & 5 deletions .github/workflows/main.yml
Expand Up @@ -7,9 +7,8 @@ on:
workflow_dispatch:

jobs:
linux-ci:
name: Linux
runs-on: ubuntu-latest
ci:
name: Build/Test
strategy:
matrix:
toolchain: ["stable", "beta", "nightly", "1.36.0"]
Expand All @@ -20,10 +19,16 @@ jobs:
- toolchain: beta
env:
DO_FUZZ: 1
- os: windows-latest
toolchain: nightly

runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v2

- name: Install packages
if: matrix.os == 'ubuntu-latest'
run: sudo apt-get install -y binutils-dev libunwind8-dev libcurl4-openssl-dev libelf-dev libdw-dev cmake gcc libiberty-dev

- name: Install toolchain
Expand All @@ -50,6 +55,14 @@ jobs:
if: matrix.toolchain == 'beta'
run: cargo test --verbose --features union

- name: Cargo test w/ debugger_visualizer
if: matrix.toolchain == 'nightly'
run: cargo test --test debugger_visualizer --verbose --features debugger_visualizer -- --test-threads=1

- name: Cargo test w/ debugger_visualizer and union
if: matrix.toolchain == 'nightly'
run: cargo test --test debugger_visualizer --verbose --features 'debugger_visualizer,union' -- --test-threads=1

- name: Cargo test all features
if: matrix.toolchain == 'nightly'
run: cargo test --verbose --all-features
Expand All @@ -59,7 +72,7 @@ jobs:
run: cargo bench --verbose bench

- name: miri
if: matrix.toolchain == 'nightly'
if: matrix.toolchain == 'nightly' && matrix.os == 'ubuntu-latest'
run: bash ./scripts/run_miri.sh
env:
MIRIFLAGS: '-Zmiri-tag-raw-pointers'
Expand All @@ -73,7 +86,7 @@ jobs:
name: homu build finished
runs-on: ubuntu-latest
needs:
- "linux-ci"
- "ci"

steps:
- name: Mark the job as successful
Expand Down
17 changes: 17 additions & 0 deletions Cargo.toml
Expand Up @@ -19,13 +19,30 @@ union = []
specialization = []
may_dangle = []

# UNSTABLE FEATURES (requires Rust nightly)
# Enable to use the #[debugger_visualizer] attribute.
debugger_visualizer = []

[dependencies]
serde = { version = "1", optional = true, default-features = false }
arbitrary = { version = "1", optional = true }

[dev_dependencies]
bincode = "1.0.1"
debugger_test = "0.1.0"
debugger_test_parser = "0.1.0"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[[test]]
name = "debugger_visualizer"
path = "tests/debugger_visualizer.rs"
required-features = ["debugger_visualizer"]
# Do not run these tests by default. These tests need to
# be run with the additional rustc flag `--test-threads=1`
# since each test causes a debugger to attach to the current
# test process. If multiple debuggers try to attach at the same
# time, the test will fail.
test = false
111 changes: 111 additions & 0 deletions 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 `<VS Installation Folder>\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 `smallvec`
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 `smallvec` 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 `smallvec` 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 `smallvec` crate via `debug_metadata/smallvec.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
```
35 changes: 35 additions & 0 deletions debug_metadata/smallvec.natvis
@@ -0,0 +1,35 @@
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="smallvec::SmallVec&lt;array$&lt;*,*&gt;&gt;" Priority="Medium">
<Intrinsic Name="is_inline" Expression="$T2 &gt; capacity" />
<Intrinsic Name="len" Expression="is_inline() ? capacity : data.variant1.value.__0.__1" />
<Intrinsic Name="data_ptr" Expression="is_inline() ? data.variant0.value.__0.value.value : data.variant1.value.__0.__0" />

<DisplayString>{{ len={len()} }}</DisplayString>
<Expand>
<Item Name="[capacity]">is_inline() ? $T2 : capacity</Item>
<Item Name="[len]">len()</Item>

<ArrayItems>
<Size>len()</Size>
<ValuePointer>data_ptr()</ValuePointer>
</ArrayItems>
</Expand>
</Type>

<Type Name="smallvec::SmallVec&lt;array$&lt;*,*&gt;&gt;" Priority="MediumLow">
<Intrinsic Name="is_inline" Expression="$T2 &gt; capacity" />
<Intrinsic Name="len" Expression="is_inline() ? capacity : data.heap.__1" />
<Intrinsic Name="data_ptr" Expression="is_inline() ? data.inline.value.value.value : data.heap.__0" />

<DisplayString>{{ len={len()} }}</DisplayString>
<Expand>
<Item Name="[capacity]">is_inline() ? $T2 : capacity</Item>
<Item Name="[len]">len()</Item>

<ArrayItems>
<Size>len()</Size>
<ValuePointer>data_ptr()</ValuePointer>
</ArrayItems>
</Expand>
</Type>
</AutoVisualizer>
3 changes: 3 additions & 0 deletions scripts/run_miri.sh
Expand Up @@ -11,6 +11,7 @@ cargo clean

MIRI_NIGHTLY=nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri)
echo "Installing latest nightly with Miri: $MIRI_NIGHTLY"
rustup override unset
rustup default "$MIRI_NIGHTLY"

rustup component add miri
Expand All @@ -19,3 +20,5 @@ cargo miri setup
cargo miri test --verbose
cargo miri test --verbose --features union
cargo miri test --verbose --all-features

rustup override set nightly
5 changes: 5 additions & 0 deletions src/lib.rs
Expand Up @@ -81,6 +81,11 @@
#![cfg_attr(feature = "specialization", allow(incomplete_features))]
#![cfg_attr(feature = "specialization", feature(specialization))]
#![cfg_attr(feature = "may_dangle", feature(dropck_eyepatch))]
#![cfg_attr(
feature = "debugger_visualizer",
feature(debugger_visualizer),
debugger_visualizer(natvis_file = "../debug_metadata/smallvec.natvis")
)]
#![deny(missing_docs)]

#[doc(hidden)]
Expand Down
68 changes: 68 additions & 0 deletions tests/debugger_visualizer.rs
@@ -0,0 +1,68 @@
use debugger_test::debugger_test;
use smallvec::{smallvec, SmallVec};

#[inline(never)]
fn __break() {}

#[debugger_test(
debugger = "cdb",
commands = r#"
.nvlist
dx sv
g
dx sv
g
dx sv
"#,
expected_statements = r#"
sv : { len=0x2 } [Type: smallvec::SmallVec<array$<i32,4> >]
[<Raw View>] [Type: smallvec::SmallVec<array$<i32,4> >]
[capacity] : 4
[len] : 0x2 [Type: unsigned __int64]
[0] : 1 [Type: int]
[1] : 2 [Type: int]
sv : { len=0x5 } [Type: smallvec::SmallVec<array$<i32,4> >]
[<Raw View>] [Type: smallvec::SmallVec<array$<i32,4> >]
[capacity] : 0x8 [Type: unsigned __int64]
[len] : 0x5 [Type: unsigned __int64]
[0] : 5 [Type: int]
[1] : 2 [Type: int]
[2] : 3 [Type: int]
[3] : 4 [Type: int]
[4] : 5 [Type: int]
sv : { len=0x5 } [Type: smallvec::SmallVec<array$<i32,4> >]
[<Raw View>] [Type: smallvec::SmallVec<array$<i32,4> >]
[capacity] : 0x8 [Type: unsigned __int64]
[len] : 0x5 [Type: unsigned __int64]
[0] : 2 [Type: int]
[1] : 3 [Type: int]
[2] : 4 [Type: int]
[3] : 5 [Type: int]
[4] : 5 [Type: int]
"#
)]
#[inline(never)]
fn test_debugger_visualizer() {
// This SmallVec can hold up to 4 items on the stack:
let mut sv: SmallVec<[i32; 4]> = smallvec![1, 2];
__break();

// Overfill the SmallVec to move its contents to the heap
for i in 3..6 {
sv.push(i);
}

// Update the contents of the first value of the SmallVec.
sv[0] = sv[1] + sv[2];
__break();

// Sort the SmallVec in place.
sv.sort();
__break();
}

0 comments on commit 09b4988

Please sign in to comment.