From 0de12fbd9d7a38ae89e403f736f0f2b8d7b4948a Mon Sep 17 00:00:00 2001 From: ridwanabdillahi <91507758+ridwanabdillahi@users.noreply.github.com> Date: Mon, 14 Feb 2022 19:04:42 -0800 Subject: [PATCH 1/2] Add Natvis definitions for url types and create tests to ensure visualizations do not become stale or broken. Add a README for documenting how the debugger visualizers and how to embed/ test them. Update the github actions CI workflow to manually enable features, in addition to the default features, to allow for testing unstable features separately. Cleanup running tests with the serde feature enabled. Update documentation noting the debugger_visualizer crate feature is an unstable feature. Respond to PR comments. Fix unused variables warnings. Fix lint errors Enable features via `member:feature` syntax. --- .github/workflows/main.yml | 23 +++++-- debug_metadata/README.md | 111 +++++++++++++++++++++++++++++++ debug_metadata/url.natvis | 34 ++++++++++ url/Cargo.toml | 12 ++++ url/src/lib.rs | 5 ++ url/tests/debugger_visualizer.rs | 102 ++++++++++++++++++++++++++++ 6 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 debug_metadata/README.md create mode 100644 debug_metadata/url.natvis create mode 100644 url/tests/debugger_visualizer.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2d202663d..a114a272a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,8 +25,6 @@ jobs: rust: beta - os: macos-latest rust: nightly - - os: windows-latest - rust: nightly runs-on: ${{ matrix.os }} @@ -41,10 +39,27 @@ jobs: with: command: build args: --all-targets - - uses: actions-rs/cargo@v1 + # Run tests + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + # Run tests enabling the serde feature + - name: Run tests with the serde feature + uses: actions-rs/cargo@v1 + with: + command: test + args: --features "url/serde" + # The #[debugger_visualizer] attribute is currently gated behind an unstable feature flag. + # In order to test the visualizers for the url crate, they have to be tested on a nightly build. + - name: Run debugger_visualizer tests + if: | + matrix.os == 'windows-latest' && + matrix.rust == 'nightly' + uses: actions-rs/cargo@v1 with: command: test - args: --all-features + args: --test debugger_visualizer --features "url/serde,url/debugger_visualizer" -- --test-threads=1 WASM: runs-on: ubuntu-latest diff --git a/debug_metadata/README.md b/debug_metadata/README.md new file mode 100644 index 000000000..785a45467 --- /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 `url` +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 `url` 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 `url` 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 `url` crate via `debug_metadata/url.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/url.natvis b/debug_metadata/url.natvis new file mode 100644 index 000000000..9a8a97b72 --- /dev/null +++ b/debug_metadata/url.natvis @@ -0,0 +1,34 @@ + + + + + {serialization} + + + {(char*)(ptr()),[scheme_end]s8} + + + + {(char*)(ptr()+(scheme_end + 3)),[((username_end)-(scheme_end + 3))]s8} + + + {(char*)(ptr()+host_start),[host_end-host_start]s8} + + + {port.variant1.__0,d} + + + {(char*)(ptr()+path_start),[(serialization.vec.len-path_start)]s8} + {(char*)(ptr()+path_start),[(query_start.variant1.__0-path_start)]s8} + {(char*)(ptr()+path_start),[(fragment_start.variant1.__0-path_start)]s8} + + + {(char*)(ptr()+query_start.variant1.__0+1),[((serialization.vec.len)-(query_start.variant1.__0+1))]s8} + {(char*)(ptr()+query_start.variant1.__0+1),[((fragment_start.variant1.__0)-(query_start.variant1.__0+1))]s8} + + + {(char*)(ptr()+fragment_start.variant1.__0+1),[(serialization.vec.len-fragment_start.variant1.__0-1)]s8} + + + + diff --git a/url/Cargo.toml b/url/Cargo.toml index 2231d7550..1747e4a97 100644 --- a/url/Cargo.toml +++ b/url/Cargo.toml @@ -23,6 +23,9 @@ appveyor = { repository = "Manishearth/rust-url" } [dev-dependencies] serde_json = "1.0" bencher = "0.1" +# To test debugger visualizers defined for the url crate such as url.natvis +debugger_test = "0.1" +debugger_test_parser = "0.1" [dependencies] form_urlencoded = { version = "1.0.0", path = "../form_urlencoded" } @@ -32,8 +35,17 @@ serde = {version = "1.0", optional = true, features = ["derive"]} [features] default = ["idna"] +# UNSTABLE FEATURES (requires Rust nightly) +# Enable to use the #[debugger_visualizer] attribute. +debugger_visualizer = [] [[bench]] name = "parse_url" path = "benches/parse_url.rs" harness = false + +[[test]] +name = "debugger_visualizer" +path = "tests/debugger_visualizer.rs" +required-features = ["debugger_visualizer"] +test = false diff --git a/url/src/lib.rs b/url/src/lib.rs index 163cc9522..436590cf1 100644 --- a/url/src/lib.rs +++ b/url/src/lib.rs @@ -131,6 +131,11 @@ url = { version = "2", default-features = false } */ #![doc(html_root_url = "https://docs.rs/url/2.2.2")] +#![cfg_attr( + feature = "debugger_visualizer", + feature(debugger_visualizer), + debugger_visualizer(natvis_file = "../../debug_metadata/url.natvis") +)] pub use form_urlencoded; diff --git a/url/tests/debugger_visualizer.rs b/url/tests/debugger_visualizer.rs new file mode 100644 index 000000000..4558e0701 --- /dev/null +++ b/url/tests/debugger_visualizer.rs @@ -0,0 +1,102 @@ +use debugger_test::debugger_test; +use url::Url; + +#[inline(never)] +fn __break() {} + +#[debugger_test( + debugger = "cdb", + commands = " + .nvlist + + dx base_url + + dx url_with_non_special_scheme + + dx url_with_user_pass_port_query_fragments + + dx url_blob + + dx url_with_base + + dx url_with_base_replaced + + dx url_with_comma", + expected_statements = r#" + pattern:debugger_visualizer-.*\.exe \(embedded NatVis ".*-[0-9]+\.natvis"\) + + base_url : "http://example.org/foo/bar" [Type: url::Url] + [] [Type: url::Url] + [scheme] : "http" + [host] : "example.org" + [path] : "/foo/bar" + + url_with_non_special_scheme : "non-special://test/x" [Type: url::Url] + [] [Type: url::Url] + [scheme] : "non-special" + [host] : "test" + [path] : "/x" + + url_with_user_pass_port_query_fragments : "http://user:pass@foo:21/bar;par?b#c" [Type: url::Url] + [] [Type: url::Url] + [scheme] : "http" + [username] : "user" + [host] : "foo" + [port] : 21 + [path] : "/bar;par" + [query] : "b" + [fragment] : "c" + + url_blob : "blob:https://example.com:443/" [Type: url::Url] + [] [Type: url::Url] + [scheme] : "blob" + [path] : "https://example.com:443/" + + url_with_base : "http://example.org/a%2fc" [Type: url::Url] + [] [Type: url::Url] + [scheme] : "http" + [host] : "example.org" + [path] : "/a%2fc" + + url_with_base_replaced : "http://[::7f00:1]/" [Type: url::Url] + [] [Type: url::Url] + [scheme] : "http" + [host] : "[::7f00:1]" + [path] : "/" + + url_with_comma : "data:text/html,test#test" [Type: url::Url] + [] [Type: url::Url] + [scheme] : "data" + [path] : "text/html,test" + [fragment] : "test" + "# +)] +fn test_url_visualizer() { + // Copied from https://github.com/web-platform-tests/wpt/blob/master/url/ + let base_url = Url::parse("http://example.org/foo/bar").unwrap(); + assert_eq!(base_url.as_str(), "http://example.org/foo/bar"); + + let url_with_non_special_scheme = Url::parse("non-special://:@test/x").unwrap(); + assert_eq!(url_with_non_special_scheme.as_str(), "non-special://test/x"); + + let url_with_user_pass_port_query_fragments = + Url::parse("http://user:pass@foo:21/bar;par?b#c").unwrap(); + assert_eq!( + url_with_user_pass_port_query_fragments.as_str(), + "http://user:pass@foo:21/bar;par?b#c" + ); + + let url_blob = Url::parse("blob:https://example.com:443/").unwrap(); + assert_eq!(url_blob.as_str(), "blob:https://example.com:443/"); + + let url_with_base = base_url.join("/a%2fc").unwrap(); + assert_eq!(url_with_base.as_str(), "http://example.org/a%2fc"); + + let url_with_base_replaced = base_url.join("http://[::127.0.0.1]").unwrap(); + assert_eq!(url_with_base_replaced.as_str(), "http://[::7f00:1]/"); + + let url_with_comma = base_url.join("data:text/html,test#test").unwrap(); + assert_eq!(url_with_comma.as_str(), "data:text/html,test#test"); + + __break(); +} From 435c0ca2ee75e48782e2a3182c1e93b1b9385cf1 Mon Sep 17 00:00:00 2001 From: Ridwan Abdilahi Date: Thu, 18 Aug 2022 11:17:48 -0700 Subject: [PATCH 2/2] Update natvis type definitions after latest changes to debuginfo type names in the Rust compiler. --- debug_metadata/url.natvis | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/debug_metadata/url.natvis b/debug_metadata/url.natvis index 9a8a97b72..371247046 100644 --- a/debug_metadata/url.natvis +++ b/debug_metadata/url.natvis @@ -11,23 +11,23 @@ {(char*)(ptr()+(scheme_end + 3)),[((username_end)-(scheme_end + 3))]s8} - + {(char*)(ptr()+host_start),[host_end-host_start]s8} - - {port.variant1.__0,d} + + {port.variant1.value.__0,d} - {(char*)(ptr()+path_start),[(serialization.vec.len-path_start)]s8} - {(char*)(ptr()+path_start),[(query_start.variant1.__0-path_start)]s8} - {(char*)(ptr()+path_start),[(fragment_start.variant1.__0-path_start)]s8} + {(char*)(ptr()+path_start),[(serialization.vec.len-path_start)]s8} + {(char*)(ptr()+path_start),[(query_start.variant1.value.__0-path_start)]s8} + {(char*)(ptr()+path_start),[(fragment_start.variant1.value.__0-path_start)]s8} - - {(char*)(ptr()+query_start.variant1.__0+1),[((serialization.vec.len)-(query_start.variant1.__0+1))]s8} - {(char*)(ptr()+query_start.variant1.__0+1),[((fragment_start.variant1.__0)-(query_start.variant1.__0+1))]s8} + + {(char*)(ptr()+query_start.variant1.value.__0+1),[((serialization.vec.len)-(query_start.variant1.value.__0+1))]s8} + {(char*)(ptr()+query_start.variant1.value.__0+1),[((fragment_start.variant1.value.__0)-(query_start.variant1.value.__0+1))]s8} - - {(char*)(ptr()+fragment_start.variant1.__0+1),[(serialization.vec.len-fragment_start.variant1.__0-1)]s8} + + {(char*)(ptr()+fragment_start.variant1.value.__0+1),[(serialization.vec.len-fragment_start.variant1.value.__0-1)]s8}