Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Natvis definitions and tests for SmallVec type #286

Merged
merged 1 commit into from Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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();
}