Skip to content

Commit 490f218

Browse files
authored
Merge pull request #783 from ridwanabdillahi/natvis
Add Natvis visualizations for the `Url` type
2 parents d6b4cda + 435c0ca commit 490f218

File tree

6 files changed

+283
-4
lines changed

6 files changed

+283
-4
lines changed

.github/workflows/main.yml

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ jobs:
2525
rust: beta
2626
- os: macos-latest
2727
rust: nightly
28-
- os: windows-latest
29-
rust: nightly
3028

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

@@ -41,10 +39,27 @@ jobs:
4139
with:
4240
command: build
4341
args: --all-targets
44-
- uses: actions-rs/cargo@v1
42+
# Run tests
43+
- name: Run tests
44+
uses: actions-rs/cargo@v1
45+
with:
46+
command: test
47+
# Run tests enabling the serde feature
48+
- name: Run tests with the serde feature
49+
uses: actions-rs/cargo@v1
50+
with:
51+
command: test
52+
args: --features "url/serde"
53+
# The #[debugger_visualizer] attribute is currently gated behind an unstable feature flag.
54+
# In order to test the visualizers for the url crate, they have to be tested on a nightly build.
55+
- name: Run debugger_visualizer tests
56+
if: |
57+
matrix.os == 'windows-latest' &&
58+
matrix.rust == 'nightly'
59+
uses: actions-rs/cargo@v1
4560
with:
4661
command: test
47-
args: --all-features
62+
args: --test debugger_visualizer --features "url/serde,url/debugger_visualizer" -- --test-threads=1
4863

4964
WASM:
5065
runs-on: ubuntu-latest

debug_metadata/README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
## Debugger Visualizers
2+
3+
Many languages and debuggers enable developers to control how a type is
4+
displayed in a debugger. These are called "debugger visualizations" or "debugger
5+
views".
6+
7+
The Windows debuggers (WinDbg\CDB) support defining custom debugger visualizations using
8+
the `Natvis` framework. To use Natvis, developers write XML documents using the natvis
9+
schema that describe how debugger types should be displayed with the `.natvis` extension.
10+
(See: https://docs.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects?view=vs-2019)
11+
The Natvis files provide patterns which match type names a description of how to display
12+
those types.
13+
14+
The Natvis schema can be found either online (See: https://code.visualstudio.com/docs/cpp/natvis#_schema)
15+
or locally at `<VS Installation Folder>\Xml\Schemas\1033\natvis.xsd`.
16+
17+
The GNU debugger (GDB) supports defining custom debugger views using Pretty Printers.
18+
Pretty printers are written as python scripts that describe how a type should be displayed
19+
when loaded up in GDB/LLDB. (See: https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing.html#Pretty-Printing)
20+
The pretty printers provide patterns, which match type names, and for matching
21+
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).
22+
23+
### Embedding Visualizers
24+
25+
Through the use of the currently unstable `#[debugger_visualizer]` attribute, the `url`
26+
crate can embed debugger visualizers into the crate metadata.
27+
28+
Currently the two types of visualizers supported are Natvis and Pretty printers.
29+
30+
For Natvis files, when linking an executable with a crate that includes Natvis files,
31+
the MSVC linker will embed the contents of all Natvis files into the generated `PDB`.
32+
33+
For pretty printers, the compiler will encode the contents of the pretty printer
34+
in the `.debug_gdb_scripts` section of the `ELF` generated.
35+
36+
### Testing Visualizers
37+
38+
The `url` crate supports testing debugger visualizers defined for this crate. The entry point for
39+
these tests are `tests/debugger_visualizer.rs`. These tests are defined using the `debugger_test` and
40+
`debugger_test_parser` crates. The `debugger_test` crate is a proc macro crate which defines a
41+
single proc macro attribute, `#[debugger_test]`. For more detailed information about this crate,
42+
see https://crates.io/crates/debugger_test. The CI pipeline for the `url` crate has been updated
43+
to run the debugger visualizer tests to ensure debugger visualizers do not become broken/stale.
44+
45+
The `#[debugger_test]` proc macro attribute may only be used on test functions and will run the
46+
function under the debugger specified by the `debugger` meta item.
47+
48+
This proc macro attribute has 3 required values:
49+
50+
1. The first required meta item, `debugger`, takes a string value which specifies the debugger to launch.
51+
2. The second required meta item, `commands`, takes a string of new line (`\n`) separated list of debugger
52+
commands to run.
53+
3. The third required meta item, `expected_statements`, takes a string of new line (`\n`) separated list of
54+
statements that must exist in the debugger output. Pattern matching through regular expressions is also
55+
supported by using the `pattern:` prefix for each expected statement.
56+
57+
#### Example:
58+
59+
```rust
60+
#[debugger_test(
61+
debugger = "cdb",
62+
commands = "command1\ncommand2\ncommand3",
63+
expected_statements = "statement1\nstatement2\nstatement3")]
64+
fn test() {
65+
66+
}
67+
```
68+
69+
Using a multiline string is also supported, with a single debugger command/expected statement per line:
70+
71+
```rust
72+
#[debugger_test(
73+
debugger = "cdb",
74+
commands = "
75+
command1
76+
command2
77+
command3",
78+
expected_statements = "
79+
statement1
80+
pattern:statement[0-9]+
81+
statement3")]
82+
fn test() {
83+
84+
}
85+
```
86+
87+
In the example above, the second expected statement uses pattern matching through a regular expression
88+
by using the `pattern:` prefix.
89+
90+
#### Testing Locally
91+
92+
Currently, only Natvis visualizations have been defined for the `url` crate via `debug_metadata/url.natvis`,
93+
which means the `tests/debugger_visualizer.rs` tests need to be run on Windows using the `*-pc-windows-msvc` targets.
94+
To run these tests locally, first ensure the debugging tools for Windows are installed or install them following
95+
the steps listed here, [Debugging Tools for Windows](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/).
96+
Once the debugging tools have been installed, the tests can be run in the same manner as they are in the CI
97+
pipeline.
98+
99+
#### Note
100+
101+
When running the debugger visualizer tests, `tests/debugger_visualizer.rs`, they need to be run consecutively
102+
and not in parallel. This can be achieved by passing the flag `--test-threads=1` to rustc. This is due to
103+
how the debugger tests are run. Each test marked with the `#[debugger_test]` attribute launches a debugger
104+
and attaches it to the current test process. If tests are running in parallel, the test will try to attach
105+
a debugger to the current process which may already have a debugger attached causing the test to fail.
106+
107+
For example:
108+
109+
```
110+
cargo test --test debugger_visualizer --features debugger_visualizer -- --test-threads=1
111+
```

debug_metadata/url.natvis

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
3+
<Type Name="url::Url">
4+
<Intrinsic Name="ptr" Expression="serialization.vec.buf.ptr.pointer.pointer" />
5+
<DisplayString>{serialization}</DisplayString>
6+
<Expand>
7+
<Synthetic Name="[scheme]">
8+
<DisplayString>{(char*)(ptr()),[scheme_end]s8}</DisplayString>
9+
</Synthetic>
10+
<Synthetic Name="[username]" Condition="username_end > (scheme_end + 3)">
11+
<!-- Add 3 to the scheme end to account for the scheme separator which is '://' -->
12+
<DisplayString>{(char*)(ptr()+(scheme_end + 3)),[((username_end)-(scheme_end + 3))]s8}</DisplayString>
13+
</Synthetic>
14+
<Synthetic Name="[host]" Condition="host.tag != 0">
15+
<DisplayString>{(char*)(ptr()+host_start),[host_end-host_start]s8}</DisplayString>
16+
</Synthetic>
17+
<Synthetic Name="[port]" Condition="port.tag == 1">
18+
<DisplayString>{port.variant1.value.__0,d}</DisplayString>
19+
</Synthetic>
20+
<Synthetic Name="[path]">
21+
<DisplayString Condition="query_start.tag == 0 &amp;&amp; fragment_start.tag == 0">{(char*)(ptr()+path_start),[(serialization.vec.len-path_start)]s8}</DisplayString>
22+
<DisplayString Condition="query_start.tag == 1">{(char*)(ptr()+path_start),[(query_start.variant1.value.__0-path_start)]s8}</DisplayString>
23+
<DisplayString Condition="fragment_start.tag == 1">{(char*)(ptr()+path_start),[(fragment_start.variant1.value.__0-path_start)]s8}</DisplayString>
24+
</Synthetic>
25+
<Synthetic Name="[query]" Condition="query_start.tag == 1">
26+
<DisplayString Condition="fragment_start.tag == 0">{(char*)(ptr()+query_start.variant1.value.__0+1),[((serialization.vec.len)-(query_start.variant1.value.__0+1))]s8}</DisplayString>
27+
<DisplayString Condition="fragment_start.tag == 1">{(char*)(ptr()+query_start.variant1.value.__0+1),[((fragment_start.variant1.value.__0)-(query_start.variant1.value.__0+1))]s8}</DisplayString>
28+
</Synthetic>
29+
<Synthetic Name="[fragment]" Condition="fragment_start.tag == 1">
30+
<DisplayString>{(char*)(ptr()+fragment_start.variant1.value.__0+1),[(serialization.vec.len-fragment_start.variant1.value.__0-1)]s8}</DisplayString>
31+
</Synthetic>
32+
</Expand>
33+
</Type>
34+
</AutoVisualizer>

url/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ appveyor = { repository = "Manishearth/rust-url" }
2323
[dev-dependencies]
2424
serde_json = "1.0"
2525
bencher = "0.1"
26+
# To test debugger visualizers defined for the url crate such as url.natvis
27+
debugger_test = "0.1"
28+
debugger_test_parser = "0.1"
2629

2730
[dependencies]
2831
form_urlencoded = { version = "1.0.0", path = "../form_urlencoded" }
@@ -32,8 +35,17 @@ serde = {version = "1.0", optional = true, features = ["derive"]}
3235

3336
[features]
3437
default = ["idna"]
38+
# UNSTABLE FEATURES (requires Rust nightly)
39+
# Enable to use the #[debugger_visualizer] attribute.
40+
debugger_visualizer = []
3541

3642
[[bench]]
3743
name = "parse_url"
3844
path = "benches/parse_url.rs"
3945
harness = false
46+
47+
[[test]]
48+
name = "debugger_visualizer"
49+
path = "tests/debugger_visualizer.rs"
50+
required-features = ["debugger_visualizer"]
51+
test = false

url/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ url = { version = "2", default-features = false }
131131
*/
132132

133133
#![doc(html_root_url = "https://docs.rs/url/2.2.2")]
134+
#![cfg_attr(
135+
feature = "debugger_visualizer",
136+
feature(debugger_visualizer),
137+
debugger_visualizer(natvis_file = "../../debug_metadata/url.natvis")
138+
)]
134139

135140
pub use form_urlencoded;
136141

url/tests/debugger_visualizer.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use debugger_test::debugger_test;
2+
use url::Url;
3+
4+
#[inline(never)]
5+
fn __break() {}
6+
7+
#[debugger_test(
8+
debugger = "cdb",
9+
commands = "
10+
.nvlist
11+
12+
dx base_url
13+
14+
dx url_with_non_special_scheme
15+
16+
dx url_with_user_pass_port_query_fragments
17+
18+
dx url_blob
19+
20+
dx url_with_base
21+
22+
dx url_with_base_replaced
23+
24+
dx url_with_comma",
25+
expected_statements = r#"
26+
pattern:debugger_visualizer-.*\.exe \(embedded NatVis ".*-[0-9]+\.natvis"\)
27+
28+
base_url : "http://example.org/foo/bar" [Type: url::Url]
29+
[<Raw View>] [Type: url::Url]
30+
[scheme] : "http"
31+
[host] : "example.org"
32+
[path] : "/foo/bar"
33+
34+
url_with_non_special_scheme : "non-special://test/x" [Type: url::Url]
35+
[<Raw View>] [Type: url::Url]
36+
[scheme] : "non-special"
37+
[host] : "test"
38+
[path] : "/x"
39+
40+
url_with_user_pass_port_query_fragments : "http://user:pass@foo:21/bar;par?b#c" [Type: url::Url]
41+
[<Raw View>] [Type: url::Url]
42+
[scheme] : "http"
43+
[username] : "user"
44+
[host] : "foo"
45+
[port] : 21
46+
[path] : "/bar;par"
47+
[query] : "b"
48+
[fragment] : "c"
49+
50+
url_blob : "blob:https://example.com:443/" [Type: url::Url]
51+
[<Raw View>] [Type: url::Url]
52+
[scheme] : "blob"
53+
[path] : "https://example.com:443/"
54+
55+
url_with_base : "http://example.org/a%2fc" [Type: url::Url]
56+
[<Raw View>] [Type: url::Url]
57+
[scheme] : "http"
58+
[host] : "example.org"
59+
[path] : "/a%2fc"
60+
61+
url_with_base_replaced : "http://[::7f00:1]/" [Type: url::Url]
62+
[<Raw View>] [Type: url::Url]
63+
[scheme] : "http"
64+
[host] : "[::7f00:1]"
65+
[path] : "/"
66+
67+
url_with_comma : "data:text/html,test#test" [Type: url::Url]
68+
[<Raw View>] [Type: url::Url]
69+
[scheme] : "data"
70+
[path] : "text/html,test"
71+
[fragment] : "test"
72+
"#
73+
)]
74+
fn test_url_visualizer() {
75+
// Copied from https://github.com/web-platform-tests/wpt/blob/master/url/
76+
let base_url = Url::parse("http://example.org/foo/bar").unwrap();
77+
assert_eq!(base_url.as_str(), "http://example.org/foo/bar");
78+
79+
let url_with_non_special_scheme = Url::parse("non-special://:@test/x").unwrap();
80+
assert_eq!(url_with_non_special_scheme.as_str(), "non-special://test/x");
81+
82+
let url_with_user_pass_port_query_fragments =
83+
Url::parse("http://user:pass@foo:21/bar;par?b#c").unwrap();
84+
assert_eq!(
85+
url_with_user_pass_port_query_fragments.as_str(),
86+
"http://user:pass@foo:21/bar;par?b#c"
87+
);
88+
89+
let url_blob = Url::parse("blob:https://example.com:443/").unwrap();
90+
assert_eq!(url_blob.as_str(), "blob:https://example.com:443/");
91+
92+
let url_with_base = base_url.join("/a%2fc").unwrap();
93+
assert_eq!(url_with_base.as_str(), "http://example.org/a%2fc");
94+
95+
let url_with_base_replaced = base_url.join("http://[::127.0.0.1]").unwrap();
96+
assert_eq!(url_with_base_replaced.as_str(), "http://[::7f00:1]/");
97+
98+
let url_with_comma = base_url.join("data:text/html,test#test").unwrap();
99+
assert_eq!(url_with_comma.as_str(), "data:text/html,test#test");
100+
101+
__break();
102+
}

0 commit comments

Comments
 (0)