mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-26 08:44:35 +00:00
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:
commit
a7d6768e3b
@ -4184,6 +4184,7 @@ dependencies = [
|
||||
"rustc_attr",
|
||||
"rustc_data_structures",
|
||||
"rustc_errors",
|
||||
"rustc_expand",
|
||||
"rustc_feature",
|
||||
"rustc_hir",
|
||||
"rustc_index",
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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!(
|
||||
|
@ -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))
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)]),
|
||||
|
||||
|
@ -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" }
|
||||
|
@ -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" }
|
||||
|
@ -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(
|
||||
|
137
compiler/rustc_passes/src/debugger_visualizer.rs
Normal file
137
compiler/rustc_passes/src/debugger_visualizer.rs
Normal 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;
|
||||
}
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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.
|
18
src/test/debuginfo/msvc-embedded-natvis.natvis
Normal file
18
src/test/debuginfo/msvc-embedded-natvis.natvis
Normal 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>
|
64
src/test/debuginfo/msvc-embedded-natvis.rs
Normal file
64
src/test/debuginfo/msvc-embedded-natvis.rs
Normal 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() {
|
||||
()
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
#![debugger_visualizer(natvis_file = "../foo.natvis")] //~ ERROR the `#[debugger_visualizer]` attribute is an experimental feature
|
||||
|
||||
fn main() {}
|
@ -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`.
|
@ -0,0 +1,4 @@
|
||||
#![feature(debugger_visualizer)]
|
||||
#![debugger_visualizer(random_file = "../foo.random")] //~ ERROR invalid argument
|
||||
|
||||
fn main() {}
|
@ -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
|
||||
|
@ -0,0 +1,5 @@
|
||||
#![feature(debugger_visualizer)]
|
||||
|
||||
#[debugger_visualizer(natvis_file = "../foo.natvis")] //~ ERROR attribute should be applied to a module
|
||||
|
||||
fn main() {}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user