Auto merge of #91779 - ridwanabdillahi:natvis, r=michaelwoerister

Add a new Rust attribute to support embedding debugger visualizers

Implemented [this RFC](https://github.com/rust-lang/rfcs/pull/3191) to add support for embedding debugger visualizers into a PDB.

Added a new attribute `#[debugger_visualizer]` and updated the `CrateMetadata` to store debugger visualizers for crate dependencies.

RFC: https://github.com/rust-lang/rfcs/pull/3191
This commit is contained in:
bors 2022-05-05 12:26:38 +00:00
commit a7d6768e3b
29 changed files with 554 additions and 76 deletions

View File

@ -4184,6 +4184,7 @@ dependencies = [
"rustc_attr",
"rustc_data_structures",
"rustc_errors",
"rustc_expand",
"rustc_feature",
"rustc_hir",
"rustc_index",

View File

@ -3,17 +3,15 @@ use rustc_ast::ptr::P;
use rustc_ast::token;
use rustc_ast::tokenstream::TokenStream;
use rustc_ast_pretty::pprust;
use rustc_errors::PResult;
use rustc_expand::base::{self, *};
use rustc_expand::module::DirOwnership;
use rustc_parse::parser::{ForceCollect, Parser};
use rustc_parse::{self, new_parser_from_file};
use rustc_session::lint::builtin::INCOMPLETE_INCLUDE;
use rustc_span::symbol::Symbol;
use rustc_span::{self, FileName, Pos, Span};
use rustc_span::{self, Pos, Span};
use smallvec::SmallVec;
use std::path::PathBuf;
use std::rc::Rc;
// These macros all relate to the file system; they either return
@ -104,7 +102,7 @@ pub fn expand_include<'cx>(
return DummyResult::any(sp);
};
// The file will be added to the code map by the parser
let file = match resolve_path(cx, file.as_str(), sp) {
let file = match resolve_path(&cx.sess.parse_sess, file.as_str(), sp) {
Ok(f) => f,
Err(mut err) => {
err.emit();
@ -176,7 +174,7 @@ pub fn expand_include_str(
let Some(file) = get_single_str_from_tts(cx, sp, tts, "include_str!") else {
return DummyResult::any(sp);
};
let file = match resolve_path(cx, file.as_str(), sp) {
let file = match resolve_path(&cx.sess.parse_sess, file.as_str(), sp) {
Ok(f) => f,
Err(mut err) => {
err.emit();
@ -210,7 +208,7 @@ pub fn expand_include_bytes(
let Some(file) = get_single_str_from_tts(cx, sp, tts, "include_bytes!") else {
return DummyResult::any(sp);
};
let file = match resolve_path(cx, file.as_str(), sp) {
let file = match resolve_path(&cx.sess.parse_sess, file.as_str(), sp) {
Ok(f) => f,
Err(mut err) => {
err.emit();
@ -225,40 +223,3 @@ pub fn expand_include_bytes(
}
}
}
/// Resolves a `path` mentioned inside Rust code, returning an absolute path.
///
/// This unifies the logic used for resolving `include_X!`.
fn resolve_path<'a>(
cx: &mut ExtCtxt<'a>,
path: impl Into<PathBuf>,
span: Span,
) -> PResult<'a, PathBuf> {
let path = path.into();
// Relative paths are resolved relative to the file in which they are found
// after macro expansion (that is, they are unhygienic).
if !path.is_absolute() {
let callsite = span.source_callsite();
let mut result = match cx.source_map().span_to_filename(callsite) {
FileName::Real(name) => name
.into_local_path()
.expect("attempting to resolve a file path in an external file"),
FileName::DocTest(path, _) => path,
other => {
return Err(cx.struct_span_err(
span,
&format!(
"cannot resolve relative path in non-file source `{}`",
cx.source_map().filename_for_diagnostics(&other)
),
));
}
};
result.pop();
result.push(path);
Ok(result)
} else {
Ok(path)
}
}

View File

@ -5,7 +5,7 @@ use rustc_data_structures::memmap::Mmap;
use rustc_data_structures::temp_dir::MaybeTempDir;
use rustc_errors::{ErrorGuaranteed, Handler};
use rustc_fs_util::fix_windows_verbatim_for_gcc;
use rustc_hir::def_id::CrateNum;
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
use rustc_middle::middle::dependency_format::Linkage;
use rustc_middle::middle::exported_symbols::SymbolExportKind;
use rustc_session::config::{self, CFGuard, CrateType, DebugInfo, LdImpl, Strip};
@ -2099,8 +2099,14 @@ fn add_order_independent_options(
// Pass optimization flags down to the linker.
cmd.optimize();
let debugger_visualizer_paths = if sess.target.is_like_msvc {
collect_debugger_visualizers(tmpdir, sess, &codegen_results.crate_info)
} else {
Vec::new()
};
// Pass debuginfo and strip flags down to the linker.
cmd.debuginfo(strip_value(sess));
cmd.debuginfo(strip_value(sess), &debugger_visualizer_paths);
// We want to prevent the compiler from accidentally leaking in any system libraries,
// so by default we tell linkers not to link to any default libraries.
@ -2119,6 +2125,47 @@ fn add_order_independent_options(
add_rpath_args(cmd, sess, codegen_results, out_filename);
}
// Write the debugger visualizer files for each crate to the temp directory and gather the file paths.
fn collect_debugger_visualizers(
tmpdir: &Path,
sess: &Session,
crate_info: &CrateInfo,
) -> Vec<PathBuf> {
let mut visualizer_paths = Vec::new();
let debugger_visualizers = &crate_info.debugger_visualizers;
let mut index = 0;
for (&cnum, visualizers) in debugger_visualizers {
let crate_name = if cnum == LOCAL_CRATE {
crate_info.local_crate_name.as_str()
} else {
crate_info.crate_name[&cnum].as_str()
};
for visualizer in visualizers {
let visualizer_out_file = tmpdir.join(format!("{}-{}.natvis", crate_name, index));
match fs::write(&visualizer_out_file, &visualizer.src) {
Ok(()) => {
visualizer_paths.push(visualizer_out_file.clone());
index += 1;
}
Err(error) => {
sess.warn(
format!(
"Unable to write debugger visualizer file `{}`: {} ",
visualizer_out_file.display(),
error
)
.as_str(),
);
}
};
}
}
visualizer_paths
}
/// # Native library linking
///
/// User-supplied library search paths (-L on the command line). These are the same paths used to

View File

@ -183,7 +183,7 @@ pub trait Linker {
fn optimize(&mut self);
fn pgo_gen(&mut self);
fn control_flow_guard(&mut self);
fn debuginfo(&mut self, strip: Strip);
fn debuginfo(&mut self, strip: Strip, debugger_visualizers: &[PathBuf]);
fn no_crt_objects(&mut self);
fn no_default_libraries(&mut self);
fn export_symbols(&mut self, tmpdir: &Path, crate_type: CrateType, symbols: &[String]);
@ -611,7 +611,7 @@ impl<'a> Linker for GccLinker<'a> {
fn control_flow_guard(&mut self) {}
fn debuginfo(&mut self, strip: Strip) {
fn debuginfo(&mut self, strip: Strip, _: &[PathBuf]) {
// MacOS linker doesn't support stripping symbols directly anymore.
if self.sess.target.is_like_osx {
return;
@ -915,7 +915,7 @@ impl<'a> Linker for MsvcLinker<'a> {
self.cmd.arg("/guard:cf");
}
fn debuginfo(&mut self, strip: Strip) {
fn debuginfo(&mut self, strip: Strip, debugger_visualizers: &[PathBuf]) {
match strip {
Strip::None => {
// This will cause the Microsoft linker to generate a PDB file
@ -942,6 +942,13 @@ impl<'a> Linker for MsvcLinker<'a> {
}
}
}
// This will cause the Microsoft linker to embed .natvis info for all crates into the PDB file
for path in debugger_visualizers {
let mut arg = OsString::from("/NATVIS:");
arg.push(path);
self.cmd.arg(arg);
}
}
Strip::Debuginfo | Strip::Symbols => {
self.cmd.arg("/DEBUG:NONE");
@ -1124,7 +1131,7 @@ impl<'a> Linker for EmLinker<'a> {
fn control_flow_guard(&mut self) {}
fn debuginfo(&mut self, _strip: Strip) {
fn debuginfo(&mut self, _strip: Strip, _: &[PathBuf]) {
// Preserve names or generate source maps depending on debug info
self.cmd.arg(match self.sess.opts.debuginfo {
DebugInfo::None => "-g0",
@ -1315,7 +1322,7 @@ impl<'a> Linker for WasmLd<'a> {
fn pgo_gen(&mut self) {}
fn debuginfo(&mut self, strip: Strip) {
fn debuginfo(&mut self, strip: Strip, _: &[PathBuf]) {
match strip {
Strip::None => {}
Strip::Debuginfo => {
@ -1450,7 +1457,7 @@ impl<'a> Linker for L4Bender<'a> {
fn pgo_gen(&mut self) {}
fn debuginfo(&mut self, strip: Strip) {
fn debuginfo(&mut self, strip: Strip, _: &[PathBuf]) {
match strip {
Strip::None => {}
Strip::Debuginfo => {
@ -1600,7 +1607,7 @@ impl<'a> Linker for PtxLinker<'a> {
self.cmd.arg("-L").arg(path);
}
fn debuginfo(&mut self, _strip: Strip) {
fn debuginfo(&mut self, _strip: Strip, _: &[PathBuf]) {
self.cmd.arg("--debug");
}
@ -1699,7 +1706,7 @@ impl<'a> Linker for BpfLinker<'a> {
self.cmd.arg("-L").arg(path);
}
fn debuginfo(&mut self, _strip: Strip) {
fn debuginfo(&mut self, _strip: Strip, _: &[PathBuf]) {
self.cmd.arg("--debug");
}

View File

@ -847,7 +847,13 @@ impl CrateInfo {
missing_lang_items: Default::default(),
dependency_formats: tcx.dependency_formats(()).clone(),
windows_subsystem,
debugger_visualizers: Default::default(),
};
let debugger_visualizers = tcx.debugger_visualizers(LOCAL_CRATE).clone();
if !debugger_visualizers.is_empty() {
info.debugger_visualizers.insert(LOCAL_CRATE, debugger_visualizers);
}
let lang_items = tcx.lang_items();
let crates = tcx.crates(());
@ -862,7 +868,9 @@ impl CrateInfo {
info.native_libraries
.insert(cnum, tcx.native_libraries(cnum).iter().map(Into::into).collect());
info.crate_name.insert(cnum, tcx.crate_name(cnum));
info.used_crate_source.insert(cnum, tcx.used_crate_source(cnum).clone());
let used_crate_source = tcx.used_crate_source(cnum);
info.used_crate_source.insert(cnum, used_crate_source.clone());
if tcx.is_compiler_builtins(cnum) {
info.compiler_builtins = Some(cnum);
}
@ -883,6 +891,14 @@ impl CrateInfo {
let missing =
missing.iter().cloned().filter(|&l| lang_items::required(tcx, l)).collect();
info.missing_lang_items.insert(cnum, missing);
// Only include debugger visualizer files from crates that will be statically linked.
if used_crate_source.rlib.is_some() || used_crate_source.rmeta.is_some() {
let debugger_visualizers = tcx.debugger_visualizers(cnum).clone();
if !debugger_visualizers.is_empty() {
info.debugger_visualizers.insert(cnum, debugger_visualizers);
}
}
}
info

View File

@ -35,6 +35,7 @@ use rustc_session::config::{CrateType, OutputFilenames, OutputType, RUST_CGU_EXT
use rustc_session::cstore::{self, CrateSource};
use rustc_session::utils::NativeLibKind;
use rustc_span::symbol::Symbol;
use rustc_span::DebuggerVisualizerFile;
use std::path::{Path, PathBuf};
pub mod back;
@ -156,6 +157,7 @@ pub struct CrateInfo {
pub missing_lang_items: FxHashMap<CrateNum, Vec<LangItem>>,
pub dependency_formats: Lrc<Dependencies>,
pub windows_subsystem: Option<String>,
pub debugger_visualizers: FxHashMap<CrateNum, Vec<DebuggerVisualizerFile>>,
}
#[derive(Encodable, Decodable)]

View File

@ -10,7 +10,7 @@ use rustc_ast::{self as ast, AstLike, Attribute, Item, NodeId, PatKind};
use rustc_attr::{self as attr, Deprecation, Stability};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::sync::{self, Lrc};
use rustc_errors::{Applicability, DiagnosticBuilder, ErrorGuaranteed, MultiSpan};
use rustc_errors::{Applicability, DiagnosticBuilder, ErrorGuaranteed, MultiSpan, PResult};
use rustc_lint_defs::builtin::PROC_MACRO_BACK_COMPAT;
use rustc_lint_defs::BuiltinLintDiagnostics;
use rustc_parse::{self, nt_to_tokenstream, parser, MACRO_ARGUMENTS};
@ -20,7 +20,7 @@ use rustc_span::edition::Edition;
use rustc_span::hygiene::{AstPass, ExpnData, ExpnKind, LocalExpnId};
use rustc_span::source_map::SourceMap;
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_span::{Span, DUMMY_SP};
use rustc_span::{FileName, Span, DUMMY_SP};
use smallvec::{smallvec, SmallVec};
use std::default::Default;
@ -1136,6 +1136,43 @@ impl<'a> ExtCtxt<'a> {
}
}
/// Resolves a `path` mentioned inside Rust code, returning an absolute path.
///
/// This unifies the logic used for resolving `include_X!`.
pub fn resolve_path(
parse_sess: &ParseSess,
path: impl Into<PathBuf>,
span: Span,
) -> PResult<'_, PathBuf> {
let path = path.into();
// Relative paths are resolved relative to the file in which they are found
// after macro expansion (that is, they are unhygienic).
if !path.is_absolute() {
let callsite = span.source_callsite();
let mut result = match parse_sess.source_map().span_to_filename(callsite) {
FileName::Real(name) => name
.into_local_path()
.expect("attempting to resolve a file path in an external file"),
FileName::DocTest(path, _) => path,
other => {
return Err(parse_sess.span_diagnostic.struct_span_err(
span,
&format!(
"cannot resolve relative path in non-file source `{}`",
parse_sess.source_map().filename_for_diagnostics(&other)
),
));
}
};
result.pop();
result.push(path);
Ok(result)
} else {
Ok(path)
}
}
/// Extracts a string literal from the macro expanded version of `expr`,
/// returning a diagnostic error of `err_msg` if `expr` is not a string literal.
/// The returned bool indicates whether an applicable suggestion has already been

View File

@ -358,6 +358,8 @@ declare_features! (
(active, custom_inner_attributes, "1.30.0", Some(54726), None),
/// Allows custom test frameworks with `#![test_runner]` and `#[test_case]`.
(active, custom_test_frameworks, "1.30.0", Some(50297), None),
/// Allows using `#[debugger_visualizer]`.
(active, debugger_visualizer, "1.62.0", Some(95939), None),
/// Allows declarative macros 2.0 (`macro`).
(active, decl_macro, "1.17.0", Some(39412), None),
/// Allows rustc to inject a default alloc_error_handler

View File

@ -379,6 +379,12 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
// Unstable attributes:
// ==========================================================================
// RFC #3191: #[debugger_visualizer] support
gated!(
debugger_visualizer, Normal, template!(List: r#"natvis_file = "...""#),
DuplicatesOk, experimental!(debugger_visualizer)
),
// Linking:
gated!(naked, Normal, template!(Word), WarnFollowing, naked_functions, experimental!(naked)),
gated!(

View File

@ -1023,6 +1023,10 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
self.root.tables.expn_that_defined.get(self, id).unwrap().decode((self, sess))
}
fn get_debugger_visualizers(self) -> Vec<rustc_span::DebuggerVisualizerFile> {
self.root.debugger_visualizers.decode(self).collect::<Vec<_>>()
}
/// Iterates over all the stability attributes in the given crate.
fn get_lib_features(self, tcx: TyCtxt<'tcx>) -> &'tcx [(Symbol, Option<Symbol>)] {
tcx.arena.alloc_from_iter(self.root.lib_features.decode(self))

View File

@ -233,6 +233,7 @@ provide! { <'tcx> tcx, def_id, other, cdata,
}
used_crate_source => { Lrc::clone(&cdata.source) }
debugger_visualizers => { cdata.get_debugger_visualizers() }
exported_symbols => {
let syms = cdata.exported_symbols(tcx);

View File

@ -35,7 +35,9 @@ use rustc_serialize::{opaque, Encodable, Encoder};
use rustc_session::config::CrateType;
use rustc_session::cstore::{ForeignModule, LinkagePreference, NativeLib};
use rustc_span::symbol::{sym, Ident, Symbol};
use rustc_span::{self, ExternalSource, FileName, SourceFile, Span, SyntaxContext};
use rustc_span::{
self, DebuggerVisualizerFile, ExternalSource, FileName, SourceFile, Span, SyntaxContext,
};
use rustc_span::{
hygiene::{ExpnIndex, HygieneEncodeContext, MacroKind},
RealFileName,
@ -672,6 +674,10 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
let tables = self.tables.encode(&mut self.opaque);
let tables_bytes = self.position() - i;
i = self.position();
let debugger_visualizers = self.encode_debugger_visualizers();
let debugger_visualizers_bytes = self.position() - i;
// Encode exported symbols info. This is prefetched in `encode_metadata` so we encode
// this as late as possible to give the prefetching as much time as possible to complete.
i = self.position();
@ -717,6 +723,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
has_panic_handler: tcx.has_panic_handler(LOCAL_CRATE),
has_default_lib_allocator,
proc_macro_data,
debugger_visualizers,
compiler_builtins: tcx.sess.contains_name(&attrs, sym::compiler_builtins),
needs_allocator: tcx.sess.contains_name(&attrs, sym::needs_allocator),
needs_panic_runtime: tcx.sess.contains_name(&attrs, sym::needs_panic_runtime),
@ -762,6 +769,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
eprintln!(" lang item bytes: {}", lang_item_bytes);
eprintln!(" diagnostic item bytes: {}", diagnostic_item_bytes);
eprintln!(" native bytes: {}", native_lib_bytes);
eprintln!(" debugger visualizers bytes: {}", debugger_visualizers_bytes);
eprintln!(" source_map bytes: {}", source_map_bytes);
eprintln!(" traits bytes: {}", traits_bytes);
eprintln!(" impls bytes: {}", impls_bytes);
@ -1716,6 +1724,11 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
}
}
fn encode_debugger_visualizers(&mut self) -> Lazy<[DebuggerVisualizerFile]> {
empty_proc_macro!(self);
self.lazy(self.tcx.debugger_visualizers(LOCAL_CRATE).iter())
}
fn encode_crate_deps(&mut self) -> Lazy<[CrateDep]> {
empty_proc_macro!(self);

View File

@ -219,6 +219,7 @@ crate struct CrateRoot<'tcx> {
proc_macro_data: Option<ProcMacroData>,
tables: LazyTables<'tcx>,
debugger_visualizers: Lazy<[rustc_span::DebuggerVisualizerFile]>,
exported_symbols: Lazy!([(ExportedSymbol<'tcx>, SymbolExportInfo)]),

View File

@ -1628,6 +1628,12 @@ rustc_queries! {
desc { "looking at the source for a crate" }
separate_provide_extern
}
/// Returns the debugger visualizers defined for this crate.
query debugger_visualizers(_: CrateNum) -> Vec<rustc_span::DebuggerVisualizerFile> {
storage(ArenaCacheSelector<'tcx>)
desc { "looking up the debugger visualizers for this crate" }
separate_provide_extern
}
query postorder_cnums(_: ()) -> &'tcx [CrateNum] {
eval_always
desc { "generating a postorder list of CrateNums" }

View File

@ -9,6 +9,7 @@ rustc_middle = { path = "../rustc_middle" }
rustc_attr = { path = "../rustc_attr" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_errors = { path = "../rustc_errors" }
rustc_expand = { path = "../rustc_expand" }
rustc_hir = { path = "../rustc_hir" }
rustc_index = { path = "../rustc_index" }
rustc_parse = { path = "../rustc_parse" }

View File

@ -100,6 +100,7 @@ impl CheckAttrVisitor<'_> {
sym::allow_internal_unstable => {
self.check_allow_internal_unstable(hir_id, &attr, span, target, &attrs)
}
sym::debugger_visualizer => self.check_debugger_visualizer(&attr, target),
sym::rustc_allow_const_fn_unstable => {
self.check_rustc_allow_const_fn_unstable(hir_id, &attr, span, target)
}
@ -1860,6 +1861,65 @@ impl CheckAttrVisitor<'_> {
}
}
/// Checks if the items on the `#[debugger_visualizer]` attribute are valid.
fn check_debugger_visualizer(&self, attr: &Attribute, target: Target) -> bool {
match target {
Target::Mod => {}
_ => {
self.tcx
.sess
.struct_span_err(attr.span, "attribute should be applied to a module")
.emit();
return false;
}
}
let hints = match attr.meta_item_list() {
Some(meta_item_list) => meta_item_list,
None => {
self.emit_debugger_visualizer_err(attr);
return false;
}
};
let hint = match hints.len() {
1 => &hints[0],
_ => {
self.emit_debugger_visualizer_err(attr);
return false;
}
};
if !hint.has_name(sym::natvis_file) {
self.emit_debugger_visualizer_err(attr);
return false;
}
let meta_item = match hint.meta_item() {
Some(meta_item) => meta_item,
None => {
self.emit_debugger_visualizer_err(attr);
return false;
}
};
match (meta_item.name_or_empty(), meta_item.value_str()) {
(sym::natvis_file, Some(_)) => true,
(_, _) => {
self.emit_debugger_visualizer_err(attr);
false
}
}
}
fn emit_debugger_visualizer_err(&self, attr: &Attribute) {
self.tcx
.sess
.struct_span_err(attr.span, "invalid argument")
.note(r#"expected: `natvis_file = "..."`"#)
.emit();
}
/// Outputs an error for `#[allow_internal_unstable]` which can only be applied to macros.
/// (Allows proc_macro functions)
fn check_rustc_allow_const_fn_unstable(

View File

@ -0,0 +1,137 @@
//! Detecting usage of the `#[debugger_visualizer]` attribute.
use hir::CRATE_HIR_ID;
use rustc_data_structures::fx::FxHashSet;
use rustc_expand::base::resolve_path;
use rustc_hir as hir;
use rustc_hir::def_id::CrateNum;
use rustc_hir::itemlikevisit::ItemLikeVisitor;
use rustc_hir::{HirId, Target};
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::TyCtxt;
use rustc_span::def_id::LOCAL_CRATE;
use rustc_span::{sym, DebuggerVisualizerFile, DebuggerVisualizerType};
use std::sync::Arc;
struct DebuggerVisualizerCollector<'tcx> {
debugger_visualizers: FxHashSet<DebuggerVisualizerFile>,
tcx: TyCtxt<'tcx>,
}
impl<'v, 'tcx> ItemLikeVisitor<'v> for DebuggerVisualizerCollector<'tcx> {
fn visit_item(&mut self, item: &hir::Item<'_>) {
let target = Target::from_item(item);
match target {
Target::Mod => {
self.check_for_debugger_visualizer(item.hir_id());
}
_ => {}
}
}
fn visit_trait_item(&mut self, _: &hir::TraitItem<'_>) {}
fn visit_impl_item(&mut self, _: &hir::ImplItem<'_>) {}
fn visit_foreign_item(&mut self, _: &hir::ForeignItem<'_>) {}
}
impl<'tcx> DebuggerVisualizerCollector<'tcx> {
fn new(tcx: TyCtxt<'tcx>) -> DebuggerVisualizerCollector<'tcx> {
DebuggerVisualizerCollector { tcx, debugger_visualizers: FxHashSet::default() }
}
fn check_for_debugger_visualizer(&mut self, hir_id: HirId) {
let attrs = self.tcx.hir().attrs(hir_id);
for attr in attrs {
if attr.has_name(sym::debugger_visualizer) {
let list = match attr.meta_item_list() {
Some(list) => list,
_ => continue,
};
let meta_item = match list.len() {
1 => match list[0].meta_item() {
Some(meta_item) => meta_item,
_ => continue,
},
_ => continue,
};
let file = match (meta_item.name_or_empty(), meta_item.value_str()) {
(sym::natvis_file, Some(value)) => {
match resolve_path(&self.tcx.sess.parse_sess, value.as_str(), attr.span) {
Ok(file) => file,
Err(mut err) => {
err.emit();
continue;
}
}
}
(_, _) => continue,
};
if file.is_file() {
let contents = match std::fs::read(&file) {
Ok(contents) => contents,
Err(err) => {
self.tcx
.sess
.struct_span_err(
attr.span,
&format!(
"Unable to read contents of file `{}`. {}",
file.display(),
err
),
)
.emit();
continue;
}
};
self.debugger_visualizers.insert(DebuggerVisualizerFile::new(
Arc::from(contents),
DebuggerVisualizerType::Natvis,
));
} else {
self.tcx
.sess
.struct_span_err(
attr.span,
&format!("{} is not a valid file", file.display()),
)
.emit();
}
}
}
}
}
/// Traverses and collects the debugger visualizers for a specific crate.
fn debugger_visualizers<'tcx>(tcx: TyCtxt<'tcx>, cnum: CrateNum) -> Vec<DebuggerVisualizerFile> {
assert_eq!(cnum, LOCAL_CRATE);
// Initialize the collector.
let mut collector = DebuggerVisualizerCollector::new(tcx);
// Collect debugger visualizers in this crate.
tcx.hir().visit_all_item_likes(&mut collector);
// Collect debugger visualizers on the crate attributes.
collector.check_for_debugger_visualizer(CRATE_HIR_ID);
// Extract out the found debugger_visualizer items.
let DebuggerVisualizerCollector { debugger_visualizers, .. } = collector;
let mut visualizers = debugger_visualizers.into_iter().collect::<Vec<_>>();
// Sort the visualizers so we always get a deterministic query result.
visualizers.sort();
visualizers
}
pub fn provide(providers: &mut Providers) {
providers.debugger_visualizers = debugger_visualizers;
}

View File

@ -26,6 +26,7 @@ use rustc_middle::ty::query::Providers;
mod check_attr;
mod check_const;
pub mod dead;
mod debugger_visualizer;
mod diagnostic_items;
pub mod entry;
pub mod hir_id_validator;
@ -47,6 +48,7 @@ pub fn provide(providers: &mut Providers) {
check_attr::provide(providers);
check_const::provide(providers);
dead::provide(providers);
debugger_visualizer::provide(providers);
diagnostic_items::provide(providers);
entry::provide(providers);
lang_items::provide(providers);

View File

@ -70,6 +70,7 @@ use std::hash::Hash;
use std::ops::{Add, Range, Sub};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Arc;
use md5::Digest;
use md5::Md5;
@ -1199,6 +1200,28 @@ impl SourceFileHash {
}
}
#[derive(HashStable_Generic)]
#[derive(Copy, PartialEq, PartialOrd, Clone, Ord, Eq, Hash, Debug, Encodable, Decodable)]
pub enum DebuggerVisualizerType {
Natvis,
}
/// A single debugger visualizer file.
#[derive(HashStable_Generic)]
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Encodable, Decodable)]
pub struct DebuggerVisualizerFile {
/// The complete debugger visualizer source.
pub src: Arc<[u8]>,
/// Indicates which visualizer type this targets.
pub visualizer_type: DebuggerVisualizerType,
}
impl DebuggerVisualizerFile {
pub fn new(src: Arc<[u8]>, visualizer_type: DebuggerVisualizerType) -> Self {
DebuggerVisualizerFile { src, visualizer_type }
}
}
/// A single source in the [`SourceMap`].
#[derive(Clone)]
pub struct SourceFile {

View File

@ -556,6 +556,7 @@ symbols! {
debug_struct,
debug_trait_builder,
debug_tuple,
debugger_visualizer,
decl_macro,
declare_lint_pass,
decode,
@ -927,6 +928,7 @@ symbols! {
native_link_modifiers_bundle,
native_link_modifiers_verbatim,
native_link_modifiers_whole_archive,
natvis_file,
ne,
nearbyintf32,
nearbyintf64,

View File

@ -0,0 +1,25 @@
# `debugger_visualizer`
The tracking issue for this feature is: [#95939]
[#95939]: https://github.com/rust-lang/rust/issues/95939
------------------------
The `debugger_visualizer` attribute can be used to instruct the compiler
to embed a debugger visualizer file into the PDB/ELF generated by `rustc`.
## Examples
``` rust,ignore (partial-example)
#![feature(debugger_visualizer)]
#![debugger_visualizer(natvis_file = "foo.natvis")]
struct Foo {
}
```
## Limitations
Currently, this feature only supports embedding Natvis files on `-windows-msvc`
targets when using the MSVC linker via the `natvis_file` meta item.

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="msvc_embedded_natvis::Point">
<DisplayString>({x}, {y})</DisplayString>
<Expand>
<Item Name="[x]">x</Item>
<Item Name="[y]">y</Item>
</Expand>
</Type>
<Type Name="msvc_embedded_natvis::Line">
<DisplayString>({a}, {b})</DisplayString>
<Expand>
<Item Name="[a]">a</Item>
<Item Name="[b]">b</Item>
</Expand>
</Type>
</AutoVisualizer>

View File

@ -0,0 +1,64 @@
// only-cdb
// compile-flags:-g
// === CDB TESTS ==================================================================================
// cdb-command: g
// cdb-command: .nvlist
// cdb-check: [...].exe (embedded NatVis "[...]msvc_embedded_natvis-0.natvis")
// cdb-command: dx point_a
// cdb-check:point_a : (0, 0) [Type: msvc_embedded_natvis::Point]
// cdb-check: [<Raw View>] [Type: msvc_embedded_natvis::Point]
// cdb-check: [x] : 0 [Type: int]
// cdb-check: [y] : 0 [Type: int]
// cdb-command: dx point_b
// cdb-check:point_b : (5, 8) [Type: msvc_embedded_natvis::Point]
// cdb-check: [<Raw View>] [Type: msvc_embedded_natvis::Point]
// cdb-check: [x] : 5 [Type: int]
// cdb-check: [y] : 8 [Type: int]
// cdb-command: dx line
// cdb-check:line : ((0, 0), (5, 8)) [Type: msvc_embedded_natvis::Line]
// cdb-check: [<Raw View>] [Type: msvc_embedded_natvis::Line]
// cdb-check: [a] : (0, 0) [Type: msvc_embedded_natvis::Point]
// cdb-check: [b] : (5, 8) [Type: msvc_embedded_natvis::Point]
#![feature(debugger_visualizer)]
#![debugger_visualizer(natvis_file = "msvc-embedded-natvis.natvis")]
pub struct Point {
x: i32,
y: i32,
}
impl Point {
pub fn new(x: i32, y: i32) -> Point {
Point { x: x, y: y }
}
}
pub struct Line {
a: Point,
b: Point,
}
impl Line {
pub fn new(a: Point, b: Point) -> Line {
Line { a: a, b: b }
}
}
fn main() {
let point_a = Point::new(0, 0);
let point_b = Point::new(5, 8);
let line = Line::new(point_a, point_b);
zzz(); // #break
}
fn zzz() {
()
}

View File

@ -0,0 +1,3 @@
#![debugger_visualizer(natvis_file = "../foo.natvis")] //~ ERROR the `#[debugger_visualizer]` attribute is an experimental feature
fn main() {}

View File

@ -0,0 +1,12 @@
error[E0658]: the `#[debugger_visualizer]` attribute is an experimental feature
--> $DIR/feature-gate-debugger-visualizer.rs:1:1
|
LL | #![debugger_visualizer(natvis_file = "../foo.natvis")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #95939 <https://github.com/rust-lang/rust/issues/95939> for more information
= help: add `#![feature(debugger_visualizer)]` to the crate attributes to enable
error: aborting due to previous error
For more information about this error, try `rustc --explain E0658`.

View File

@ -0,0 +1,4 @@
#![feature(debugger_visualizer)]
#![debugger_visualizer(random_file = "../foo.random")] //~ ERROR invalid argument
fn main() {}

View File

@ -0,0 +1,10 @@
error: invalid argument
--> $DIR/invalid-debugger-visualizer-option.rs:2:1
|
LL | #![debugger_visualizer(random_file = "../foo.random")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: expected: `natvis_file = "..."`
error: aborting due to previous error

View File

@ -0,0 +1,5 @@
#![feature(debugger_visualizer)]
#[debugger_visualizer(natvis_file = "../foo.natvis")] //~ ERROR attribute should be applied to a module
fn main() {}

View File

@ -0,0 +1,8 @@
error: attribute should be applied to a module
--> $DIR/invalid-debugger-visualizer-target.rs:3:1
|
LL | #[debugger_visualizer(natvis_file = "../foo.natvis")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error