diff --git a/Cargo.lock b/Cargo.lock index ef4800a2261..360bb5e454a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4183,6 +4183,7 @@ dependencies = [ "rustc_attr", "rustc_data_structures", "rustc_errors", + "rustc_expand", "rustc_feature", "rustc_hir", "rustc_index", diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs index 2817fce463b..8bf3a0799b6 100644 --- a/compiler/rustc_builtin_macros/src/source_util.rs +++ b/compiler/rustc_builtin_macros/src/source_util.rs @@ -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, - 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) - } -} diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 6887f0666a4..04ec1e7f3c1 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -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 { + 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 diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index 50db2c22ae6..2a71377d2f1 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -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"); } diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 5bc95614c19..7b7e09208a2 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -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 diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 05d32972dab..d6979bb625c 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -36,6 +36,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; @@ -157,6 +158,7 @@ pub struct CrateInfo { pub missing_lang_items: FxHashMap>, pub dependency_formats: Lrc, pub windows_subsystem: Option, + pub debugger_visualizers: FxHashMap>, } #[derive(Encodable, Decodable)] diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index ae1b50a4176..2b30ec601a0 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -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, + 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 diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs index 9159d60463c..5c07d9121cc 100644 --- a/compiler/rustc_feature/src/active.rs +++ b/compiler/rustc_feature/src/active.rs @@ -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 diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index e588385cfca..bdbda8bf20c 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -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!( diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 1edb62e189f..b25522cfd96 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -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 { + self.root.debugger_visualizers.decode(self).collect::>() + } + /// Iterates over all the stability attributes in the given crate. fn get_lib_features(self, tcx: TyCtxt<'tcx>) -> &'tcx [(Symbol, Option)] { tcx.arena.alloc_from_iter(self.root.lib_features.decode(self)) diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index da1dd6af65a..c00c6ce2f71 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -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); diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index b46ea955a3a..b2eafa035db 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -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), @@ -757,25 +764,26 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } eprintln!("metadata stats:"); - eprintln!(" dep bytes: {}", dep_bytes); - eprintln!(" lib feature bytes: {}", lib_feature_bytes); - eprintln!(" lang item bytes: {}", lang_item_bytes); - eprintln!(" diagnostic item bytes: {}", diagnostic_item_bytes); - eprintln!(" native bytes: {}", native_lib_bytes); - eprintln!(" source_map bytes: {}", source_map_bytes); - eprintln!(" traits bytes: {}", traits_bytes); - eprintln!(" impls bytes: {}", impls_bytes); - eprintln!("incoherent_impls bytes: {}", incoherent_impls_bytes); - eprintln!(" exp. symbols bytes: {}", exported_symbols_bytes); - eprintln!(" def-path table bytes: {}", def_path_table_bytes); - eprintln!(" def-path hashes bytes: {}", def_path_hash_map_bytes); - eprintln!(" proc-macro-data-bytes: {}", proc_macro_data_bytes); - eprintln!(" mir bytes: {}", mir_bytes); - eprintln!(" item bytes: {}", item_bytes); - eprintln!(" table bytes: {}", tables_bytes); - eprintln!(" hygiene bytes: {}", hygiene_bytes); - eprintln!(" zero bytes: {}", zero_bytes); - eprintln!(" total bytes: {}", total_bytes); + eprintln!(" dep bytes: {}", dep_bytes); + eprintln!(" lib feature bytes: {}", lib_feature_bytes); + 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); + eprintln!(" incoherent_impls bytes: {}", incoherent_impls_bytes); + eprintln!(" exp. symbols bytes: {}", exported_symbols_bytes); + eprintln!(" def-path table bytes: {}", def_path_table_bytes); + eprintln!(" def-path hashes bytes: {}", def_path_hash_map_bytes); + eprintln!(" proc-macro-data-bytes: {}", proc_macro_data_bytes); + eprintln!(" mir bytes: {}", mir_bytes); + eprintln!(" item bytes: {}", item_bytes); + eprintln!(" table bytes: {}", tables_bytes); + eprintln!(" hygiene bytes: {}", hygiene_bytes); + eprintln!(" zero bytes: {}", zero_bytes); + eprintln!(" total bytes: {}", total_bytes); } root @@ -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); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index cdbed90e6b9..a0fd9ef4f87 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -219,6 +219,7 @@ crate struct CrateRoot<'tcx> { proc_macro_data: Option, tables: LazyTables<'tcx>, + debugger_visualizers: Lazy<[rustc_span::DebuggerVisualizerFile]>, exported_symbols: Lazy!([(ExportedSymbol<'tcx>, SymbolExportInfo)]), diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index cc80ab8f16e..e439d128dbc 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -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 { + 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" } diff --git a/compiler/rustc_passes/Cargo.toml b/compiler/rustc_passes/Cargo.toml index 39e578bce7e..a3ef1981e84 100644 --- a/compiler/rustc_passes/Cargo.toml +++ b/compiler/rustc_passes/Cargo.toml @@ -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" } diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index a9444972130..fd7e2901ee2 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -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( diff --git a/compiler/rustc_passes/src/debugger_visualizer.rs b/compiler/rustc_passes/src/debugger_visualizer.rs new file mode 100644 index 00000000000..09a73cc282e --- /dev/null +++ b/compiler/rustc_passes/src/debugger_visualizer.rs @@ -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, + 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 { + 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::>(); + + // 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; +} diff --git a/compiler/rustc_passes/src/lib.rs b/compiler/rustc_passes/src/lib.rs index 71d49d8b7ea..d9d08488d28 100644 --- a/compiler/rustc_passes/src/lib.rs +++ b/compiler/rustc_passes/src/lib.rs @@ -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); diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index f22faef2580..14668a3d411 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -71,6 +71,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; @@ -1200,6 +1201,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 { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index c1299c94c4b..e7149f4a672 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -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, diff --git a/src/doc/unstable-book/src/language-features/debugger-visualizer.md b/src/doc/unstable-book/src/language-features/debugger-visualizer.md new file mode 100644 index 00000000000..4ab482fffb9 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/debugger-visualizer.md @@ -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. diff --git a/src/test/debuginfo/msvc-embedded-natvis.natvis b/src/test/debuginfo/msvc-embedded-natvis.natvis new file mode 100644 index 00000000000..201d014b520 --- /dev/null +++ b/src/test/debuginfo/msvc-embedded-natvis.natvis @@ -0,0 +1,18 @@ + + + + ({x}, {y}) + + x + y + + + + + ({a}, {b}) + + a + b + + + diff --git a/src/test/debuginfo/msvc-embedded-natvis.rs b/src/test/debuginfo/msvc-embedded-natvis.rs new file mode 100644 index 00000000000..f714fb2ad17 --- /dev/null +++ b/src/test/debuginfo/msvc-embedded-natvis.rs @@ -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: [] [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: [] [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: [] [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() { + () +} diff --git a/src/test/ui/feature-gates/feature-gate-debugger-visualizer.rs b/src/test/ui/feature-gates/feature-gate-debugger-visualizer.rs new file mode 100644 index 00000000000..3f9eb27a0d6 --- /dev/null +++ b/src/test/ui/feature-gates/feature-gate-debugger-visualizer.rs @@ -0,0 +1,3 @@ +#![debugger_visualizer(natvis_file = "../foo.natvis")] //~ ERROR the `#[debugger_visualizer]` attribute is an experimental feature + +fn main() {} diff --git a/src/test/ui/feature-gates/feature-gate-debugger-visualizer.stderr b/src/test/ui/feature-gates/feature-gate-debugger-visualizer.stderr new file mode 100644 index 00000000000..721b2b185da --- /dev/null +++ b/src/test/ui/feature-gates/feature-gate-debugger-visualizer.stderr @@ -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 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`. diff --git a/src/test/ui/invalid/invalid-debugger-visualizer-option.rs b/src/test/ui/invalid/invalid-debugger-visualizer-option.rs new file mode 100644 index 00000000000..5332298f0ef --- /dev/null +++ b/src/test/ui/invalid/invalid-debugger-visualizer-option.rs @@ -0,0 +1,4 @@ +#![feature(debugger_visualizer)] +#![debugger_visualizer(random_file = "../foo.random")] //~ ERROR invalid argument + +fn main() {} diff --git a/src/test/ui/invalid/invalid-debugger-visualizer-option.stderr b/src/test/ui/invalid/invalid-debugger-visualizer-option.stderr new file mode 100644 index 00000000000..24ad9361fe3 --- /dev/null +++ b/src/test/ui/invalid/invalid-debugger-visualizer-option.stderr @@ -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 + diff --git a/src/test/ui/invalid/invalid-debugger-visualizer-target.rs b/src/test/ui/invalid/invalid-debugger-visualizer-target.rs new file mode 100644 index 00000000000..7668d092e61 --- /dev/null +++ b/src/test/ui/invalid/invalid-debugger-visualizer-target.rs @@ -0,0 +1,5 @@ +#![feature(debugger_visualizer)] + +#[debugger_visualizer(natvis_file = "../foo.natvis")] //~ ERROR attribute should be applied to a module + +fn main() {} diff --git a/src/test/ui/invalid/invalid-debugger-visualizer-target.stderr b/src/test/ui/invalid/invalid-debugger-visualizer-target.stderr new file mode 100644 index 00000000000..3555bbb169b --- /dev/null +++ b/src/test/ui/invalid/invalid-debugger-visualizer-target.stderr @@ -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 +