//! Type Names for Debug Info. // Notes on targeting MSVC: // In general, MSVC's debugger attempts to parse all arguments as C++ expressions, // even if the argument is explicitly a symbol name. // As such, there are many things that cause parsing issues: // * `#` is treated as a special character for macros. // * `{` or `<` at the beginning of a name is treated as an operator. // * `>>` is always treated as a right-shift. // * `[` in a name is treated like a regex bracket expression (match any char // within the brackets). // * `"` is treated as the start of a string. use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_hir::def_id::DefId; use rustc_hir::definitions::{DefPathData, DefPathDataName, DisambiguatedDefPathData}; use rustc_hir::{AsyncGeneratorKind, GeneratorKind, Mutability}; use rustc_middle::ty::layout::{IntegerExt, TyAndLayout}; use rustc_middle::ty::subst::{GenericArgKind, SubstsRef}; use rustc_middle::ty::{self, ExistentialProjection, ParamEnv, Ty, TyCtxt}; use rustc_target::abi::Integer; use smallvec::SmallVec; use std::fmt::Write; use crate::debuginfo::wants_c_like_enum_debuginfo; /// Compute the name of the type as it should be stored in debuginfo. Does not do /// any caching, i.e., calling the function twice with the same type will also do /// the work twice. The `qualified` parameter only affects the first level of the /// type name, further levels (i.e., type parameters) are always fully qualified. pub fn compute_debuginfo_type_name<'tcx>( tcx: TyCtxt<'tcx>, t: Ty<'tcx>, qualified: bool, ) -> String { let _prof = tcx.prof.generic_activity("compute_debuginfo_type_name"); let mut result = String::with_capacity(64); let mut visited = FxHashSet::default(); push_debuginfo_type_name(tcx, t, qualified, &mut result, &mut visited); result } // Pushes the name of the type as it should be stored in debuginfo on the // `output` String. See also compute_debuginfo_type_name(). fn push_debuginfo_type_name<'tcx>( tcx: TyCtxt<'tcx>, t: Ty<'tcx>, qualified: bool, output: &mut String, visited: &mut FxHashSet>, ) { // When targeting MSVC, emit C++ style type names for compatibility with // .natvis visualizers (and perhaps other existing native debuggers?) let cpp_like_debuginfo = cpp_like_debuginfo(tcx); match *t.kind() { ty::Bool => output.push_str("bool"), ty::Char => output.push_str("char"), ty::Str => { if cpp_like_debuginfo { output.push_str("str$") } else { output.push_str("str") } } ty::Never => { if cpp_like_debuginfo { output.push_str("never$"); } else { output.push('!'); } } ty::Int(int_ty) => output.push_str(int_ty.name_str()), ty::Uint(uint_ty) => output.push_str(uint_ty.name_str()), ty::Float(float_ty) => output.push_str(float_ty.name_str()), ty::Foreign(def_id) => push_item_name(tcx, def_id, qualified, output), ty::Adt(def, substs) => { // `layout_for_cpp_like_fallback` will be `Some` if we want to use the fallback encoding. let layout_for_cpp_like_fallback = if cpp_like_debuginfo && def.is_enum() { match tcx.layout_of(ParamEnv::reveal_all().and(t)) { Ok(layout) => { if !wants_c_like_enum_debuginfo(layout) { Some(layout) } else { // This is a C-like enum so we don't want to use the fallback encoding // for the name. None } } Err(e) => { // Computing the layout can still fail here, e.g. if the target architecture // cannot represent the type. See https://github.com/rust-lang/rust/issues/94961. // FIXME: migrate once `rustc_middle::mir::interpret::InterpError` is translatable. tcx.sess.fatal(&format!("{}", e)); } } } else { // We are not emitting cpp-like debuginfo or this isn't even an enum. None }; if let Some(ty_and_layout) = layout_for_cpp_like_fallback { msvc_enum_fallback( ty_and_layout, &|output, visited| { push_item_name(tcx, def.did(), true, output); push_generic_params_internal(tcx, substs, output, visited); }, output, visited, ); } else { push_item_name(tcx, def.did(), qualified, output); push_generic_params_internal(tcx, substs, output, visited); } } ty::Tuple(component_types) => { if cpp_like_debuginfo { output.push_str("tuple$<"); } else { output.push('('); } for component_type in component_types { push_debuginfo_type_name(tcx, component_type, true, output, visited); push_arg_separator(cpp_like_debuginfo, output); } if !component_types.is_empty() { pop_arg_separator(output); } if cpp_like_debuginfo { push_close_angle_bracket(cpp_like_debuginfo, output); } else { output.push(')'); } } ty::RawPtr(ty::TypeAndMut { ty: inner_type, mutbl }) => { if cpp_like_debuginfo { match mutbl { Mutability::Not => output.push_str("ptr_const$<"), Mutability::Mut => output.push_str("ptr_mut$<"), } } else { output.push('*'); match mutbl { Mutability::Not => output.push_str("const "), Mutability::Mut => output.push_str("mut "), } } push_debuginfo_type_name(tcx, inner_type, qualified, output, visited); if cpp_like_debuginfo { push_close_angle_bracket(cpp_like_debuginfo, output); } } ty::Ref(_, inner_type, mutbl) => { if cpp_like_debuginfo { match mutbl { Mutability::Not => output.push_str("ref$<"), Mutability::Mut => output.push_str("ref_mut$<"), } } else { output.push('&'); output.push_str(mutbl.prefix_str()); } push_debuginfo_type_name(tcx, inner_type, qualified, output, visited); if cpp_like_debuginfo { push_close_angle_bracket(cpp_like_debuginfo, output); } } ty::Array(inner_type, len) => { if cpp_like_debuginfo { output.push_str("array$<"); push_debuginfo_type_name(tcx, inner_type, true, output, visited); match len.kind() { ty::ConstKind::Param(param) => write!(output, ",{}>", param.name).unwrap(), _ => write!( output, ",{}>", len.eval_target_usize(tcx, ty::ParamEnv::reveal_all()) ) .unwrap(), } } else { output.push('['); push_debuginfo_type_name(tcx, inner_type, true, output, visited); match len.kind() { ty::ConstKind::Param(param) => write!(output, "; {}]", param.name).unwrap(), _ => write!( output, "; {}]", len.eval_target_usize(tcx, ty::ParamEnv::reveal_all()) ) .unwrap(), } } } ty::Slice(inner_type) => { if cpp_like_debuginfo { output.push_str("slice2$<"); } else { output.push('['); } push_debuginfo_type_name(tcx, inner_type, true, output, visited); if cpp_like_debuginfo { push_close_angle_bracket(cpp_like_debuginfo, output); } else { output.push(']'); } } ty::Dynamic(ref trait_data, ..) => { let auto_traits: SmallVec<[DefId; 4]> = trait_data.auto_traits().collect(); let has_enclosing_parens = if cpp_like_debuginfo { output.push_str("dyn$<"); false } else { if trait_data.len() > 1 && auto_traits.len() != 0 { // We need enclosing parens because there is more than one trait output.push_str("(dyn "); true } else { output.push_str("dyn "); false } }; if let Some(principal) = trait_data.principal() { let principal = tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), principal); push_item_name(tcx, principal.def_id, qualified, output); let principal_has_generic_params = push_generic_params_internal(tcx, principal.substs, output, visited); let projection_bounds: SmallVec<[_; 4]> = trait_data .projection_bounds() .map(|bound| { let ExistentialProjection { def_id: item_def_id, term, .. } = tcx.erase_late_bound_regions(bound); // FIXME(associated_const_equality): allow for consts here (item_def_id, term.ty().unwrap()) }) .collect(); if projection_bounds.len() != 0 { if principal_has_generic_params { // push_generic_params_internal() above added a `>` but we actually // want to add more items to that list, so remove that again... pop_close_angle_bracket(output); // .. and add a comma to separate the regular generic args from the // associated types. push_arg_separator(cpp_like_debuginfo, output); } else { // push_generic_params_internal() did not add `<...>`, so we open // angle brackets here. output.push('<'); } for (item_def_id, ty) in projection_bounds { if cpp_like_debuginfo { output.push_str("assoc$<"); push_item_name(tcx, item_def_id, false, output); push_arg_separator(cpp_like_debuginfo, output); push_debuginfo_type_name(tcx, ty, true, output, visited); push_close_angle_bracket(cpp_like_debuginfo, output); } else { push_item_name(tcx, item_def_id, false, output); output.push('='); push_debuginfo_type_name(tcx, ty, true, output, visited); } push_arg_separator(cpp_like_debuginfo, output); } pop_arg_separator(output); push_close_angle_bracket(cpp_like_debuginfo, output); } if auto_traits.len() != 0 { push_auto_trait_separator(cpp_like_debuginfo, output); } } if auto_traits.len() != 0 { let mut auto_traits: SmallVec<[String; 4]> = auto_traits .into_iter() .map(|def_id| { let mut name = String::with_capacity(20); push_item_name(tcx, def_id, true, &mut name); name }) .collect(); auto_traits.sort_unstable(); for auto_trait in auto_traits { output.push_str(&auto_trait); push_auto_trait_separator(cpp_like_debuginfo, output); } pop_auto_trait_separator(output); } if cpp_like_debuginfo { push_close_angle_bracket(cpp_like_debuginfo, output); } else if has_enclosing_parens { output.push(')'); } } ty::FnDef(..) | ty::FnPtr(_) => { // We've encountered a weird 'recursive type' // Currently, the only way to generate such a type // is by using 'impl trait': // // fn foo() -> impl Copy { foo } // // There's not really a sensible name we can generate, // since we don't include 'impl trait' types (e.g. ty::Opaque) // in the output // // Since we need to generate *something*, we just // use a dummy string that should make it clear // that something unusual is going on if !visited.insert(t) { output.push_str(if cpp_like_debuginfo { "recursive_type$" } else { "" }); return; } let sig = tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), t.fn_sig(tcx)); if cpp_like_debuginfo { // Format as a C++ function pointer: return_type (*)(params...) if sig.output().is_unit() { output.push_str("void"); } else { push_debuginfo_type_name(tcx, sig.output(), true, output, visited); } output.push_str(" (*)("); } else { output.push_str(sig.unsafety.prefix_str()); if sig.abi != rustc_target::spec::abi::Abi::Rust { output.push_str("extern \""); output.push_str(sig.abi.name()); output.push_str("\" "); } output.push_str("fn("); } if !sig.inputs().is_empty() { for ¶meter_type in sig.inputs() { push_debuginfo_type_name(tcx, parameter_type, true, output, visited); push_arg_separator(cpp_like_debuginfo, output); } pop_arg_separator(output); } if sig.c_variadic { if !sig.inputs().is_empty() { output.push_str(", ..."); } else { output.push_str("..."); } } output.push(')'); if !cpp_like_debuginfo && !sig.output().is_unit() { output.push_str(" -> "); push_debuginfo_type_name(tcx, sig.output(), true, output, visited); } // We only keep the type in 'visited' // for the duration of the body of this method. // It's fine for a particular function type // to show up multiple times in one overall type // (e.g. MyType u8, fn() -> u8> // // We only care about avoiding recursing // directly back to the type we're currently // processing visited.remove(&t); } ty::Closure(def_id, substs) | ty::Generator(def_id, substs, ..) => { // Name will be "{closure_env#0}", "{generator_env#0}", or // "{async_fn_env#0}", etc. // In the case of cpp-like debuginfo, the name additionally gets wrapped inside of // an artificial `enum2$<>` type, as defined in msvc_enum_fallback(). if cpp_like_debuginfo && t.is_generator() { let ty_and_layout = tcx.layout_of(ParamEnv::reveal_all().and(t)).unwrap(); msvc_enum_fallback( ty_and_layout, &|output, visited| { push_closure_or_generator_name(tcx, def_id, substs, true, output, visited); }, output, visited, ); } else { push_closure_or_generator_name(tcx, def_id, substs, qualified, output, visited); } } // Type parameters from polymorphized functions. ty::Param(_) => { write!(output, "{:?}", t).unwrap(); } ty::Error(_) | ty::Infer(_) | ty::Placeholder(..) | ty::Alias(..) | ty::Bound(..) | ty::GeneratorWitnessMIR(..) | ty::GeneratorWitness(..) => { bug!( "debuginfo: Trying to create type name for \ unexpected type: {:?}", t ); } } /// MSVC names enums differently than other platforms so that the debugging visualization // format (natvis) is able to understand enums and render the active variant correctly in the // debugger. For more information, look in // rustc_codegen_llvm/src/debuginfo/metadata/enums/cpp_like.rs. fn msvc_enum_fallback<'tcx>( ty_and_layout: TyAndLayout<'tcx>, push_inner: &dyn Fn(/*output*/ &mut String, /*visited*/ &mut FxHashSet>), output: &mut String, visited: &mut FxHashSet>, ) { debug_assert!(!wants_c_like_enum_debuginfo(ty_and_layout)); output.push_str("enum2$<"); push_inner(output, visited); push_close_angle_bracket(true, output); } const NON_CPP_AUTO_TRAIT_SEPARATOR: &str = " + "; fn push_auto_trait_separator(cpp_like_debuginfo: bool, output: &mut String) { if cpp_like_debuginfo { push_arg_separator(cpp_like_debuginfo, output); } else { output.push_str(NON_CPP_AUTO_TRAIT_SEPARATOR); } } fn pop_auto_trait_separator(output: &mut String) { if output.ends_with(NON_CPP_AUTO_TRAIT_SEPARATOR) { output.truncate(output.len() - NON_CPP_AUTO_TRAIT_SEPARATOR.len()); } else { pop_arg_separator(output); } } } pub enum VTableNameKind { // Is the name for the const/static holding the vtable? GlobalVariable, // Is the name for the type of the vtable? Type, } /// Computes a name for the global variable storing a vtable (or the type of that global variable). /// /// The name is of the form: /// /// `::{vtable}` /// /// or, when generating C++-like names: /// /// `impl$::vtable$` /// /// If `kind` is `VTableNameKind::Type` then the last component is `{vtable_ty}` instead of just /// `{vtable}`, so that the type and the corresponding global variable get assigned different /// names. pub fn compute_debuginfo_vtable_name<'tcx>( tcx: TyCtxt<'tcx>, t: Ty<'tcx>, trait_ref: Option>, kind: VTableNameKind, ) -> String { let cpp_like_debuginfo = cpp_like_debuginfo(tcx); let mut vtable_name = String::with_capacity(64); if cpp_like_debuginfo { vtable_name.push_str("impl$<"); } else { vtable_name.push('<'); } let mut visited = FxHashSet::default(); push_debuginfo_type_name(tcx, t, true, &mut vtable_name, &mut visited); if cpp_like_debuginfo { vtable_name.push_str(", "); } else { vtable_name.push_str(" as "); } if let Some(trait_ref) = trait_ref { let trait_ref = tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), trait_ref); push_item_name(tcx, trait_ref.def_id, true, &mut vtable_name); visited.clear(); push_generic_params_internal(tcx, trait_ref.substs, &mut vtable_name, &mut visited); } else { vtable_name.push('_'); } push_close_angle_bracket(cpp_like_debuginfo, &mut vtable_name); let suffix = match (cpp_like_debuginfo, kind) { (true, VTableNameKind::GlobalVariable) => "::vtable$", (false, VTableNameKind::GlobalVariable) => "::{vtable}", (true, VTableNameKind::Type) => "::vtable_type$", (false, VTableNameKind::Type) => "::{vtable_type}", }; vtable_name.reserve_exact(suffix.len()); vtable_name.push_str(suffix); vtable_name } pub fn push_item_name(tcx: TyCtxt<'_>, def_id: DefId, qualified: bool, output: &mut String) { let def_key = tcx.def_key(def_id); if qualified { if let Some(parent) = def_key.parent { push_item_name(tcx, DefId { krate: def_id.krate, index: parent }, true, output); output.push_str("::"); } } push_unqualified_item_name(tcx, def_id, def_key.disambiguated_data, output); } fn generator_kind_label(generator_kind: Option) -> &'static str { match generator_kind { Some(GeneratorKind::Async(AsyncGeneratorKind::Block)) => "async_block", Some(GeneratorKind::Async(AsyncGeneratorKind::Closure)) => "async_closure", Some(GeneratorKind::Async(AsyncGeneratorKind::Fn)) => "async_fn", Some(GeneratorKind::Gen) => "generator", None => "closure", } } fn push_disambiguated_special_name( label: &str, disambiguator: u32, cpp_like_debuginfo: bool, output: &mut String, ) { if cpp_like_debuginfo { write!(output, "{}${}", label, disambiguator).unwrap(); } else { write!(output, "{{{}#{}}}", label, disambiguator).unwrap(); } } fn push_unqualified_item_name( tcx: TyCtxt<'_>, def_id: DefId, disambiguated_data: DisambiguatedDefPathData, output: &mut String, ) { match disambiguated_data.data { DefPathData::CrateRoot => { output.push_str(tcx.crate_name(def_id.krate).as_str()); } DefPathData::ClosureExpr => { let label = generator_kind_label(tcx.generator_kind(def_id)); push_disambiguated_special_name( label, disambiguated_data.disambiguator, cpp_like_debuginfo(tcx), output, ); } _ => match disambiguated_data.data.name() { DefPathDataName::Named(name) => { output.push_str(name.as_str()); } DefPathDataName::Anon { namespace } => { push_disambiguated_special_name( namespace.as_str(), disambiguated_data.disambiguator, cpp_like_debuginfo(tcx), output, ); } }, }; } fn push_generic_params_internal<'tcx>( tcx: TyCtxt<'tcx>, substs: SubstsRef<'tcx>, output: &mut String, visited: &mut FxHashSet>, ) -> bool { if substs.non_erasable_generics().next().is_none() { return false; } debug_assert_eq!(substs, tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), substs)); let cpp_like_debuginfo = cpp_like_debuginfo(tcx); output.push('<'); for type_parameter in substs.non_erasable_generics() { match type_parameter { GenericArgKind::Type(type_parameter) => { push_debuginfo_type_name(tcx, type_parameter, true, output, visited); } GenericArgKind::Const(ct) => { push_const_param(tcx, ct, output); } other => bug!("Unexpected non-erasable generic: {:?}", other), } push_arg_separator(cpp_like_debuginfo, output); } pop_arg_separator(output); push_close_angle_bracket(cpp_like_debuginfo, output); true } fn push_const_param<'tcx>(tcx: TyCtxt<'tcx>, ct: ty::Const<'tcx>, output: &mut String) { match ct.kind() { ty::ConstKind::Param(param) => { write!(output, "{}", param.name) } _ => match ct.ty().kind() { ty::Int(ity) => { let bits = ct.eval_bits(tcx, ty::ParamEnv::reveal_all(), ct.ty()); let val = Integer::from_int_ty(&tcx, *ity).size().sign_extend(bits) as i128; write!(output, "{}", val) } ty::Uint(_) => { let val = ct.eval_bits(tcx, ty::ParamEnv::reveal_all(), ct.ty()); write!(output, "{}", val) } ty::Bool => { let val = ct.try_eval_bool(tcx, ty::ParamEnv::reveal_all()).unwrap(); write!(output, "{}", val) } _ => { // If we cannot evaluate the constant to a known type, we fall back // to emitting a stable hash value of the constant. This isn't very pretty // but we get a deterministic, virtually unique value for the constant. // // Let's only emit 64 bits of the hash value. That should be plenty for // avoiding collisions and will make the emitted type names shorter. let hash_short = tcx.with_stable_hashing_context(|mut hcx| { let mut hasher = StableHasher::new(); let ct = ct.eval(tcx, ty::ParamEnv::reveal_all()); hcx.while_hashing_spans(false, |hcx| { ct.to_valtree().hash_stable(hcx, &mut hasher) }); let hash: u64 = hasher.finish(); hash }); if cpp_like_debuginfo(tcx) { write!(output, "CONST${:x}", hash_short) } else { write!(output, "{{CONST#{:x}}}", hash_short) } } }, } .unwrap(); } pub fn push_generic_params<'tcx>(tcx: TyCtxt<'tcx>, substs: SubstsRef<'tcx>, output: &mut String) { let _prof = tcx.prof.generic_activity("compute_debuginfo_type_name"); let mut visited = FxHashSet::default(); push_generic_params_internal(tcx, substs, output, &mut visited); } fn push_closure_or_generator_name<'tcx>( tcx: TyCtxt<'tcx>, def_id: DefId, substs: SubstsRef<'tcx>, qualified: bool, output: &mut String, visited: &mut FxHashSet>, ) { // Name will be "{closure_env#0}", "{generator_env#0}", or // "{async_fn_env#0}", etc. let def_key = tcx.def_key(def_id); let generator_kind = tcx.generator_kind(def_id); if qualified { let parent_def_id = DefId { index: def_key.parent.unwrap(), ..def_id }; push_item_name(tcx, parent_def_id, true, output); output.push_str("::"); } let mut label = String::with_capacity(20); write!(&mut label, "{}_env", generator_kind_label(generator_kind)).unwrap(); push_disambiguated_special_name( &label, def_key.disambiguated_data.disambiguator, cpp_like_debuginfo(tcx), output, ); // We also need to add the generic arguments of the async fn/generator or // the enclosing function (for closures or async blocks), so that we end // up with a unique name for every instantiation. // Find the generics of the enclosing function, as defined in the source code. let enclosing_fn_def_id = tcx.typeck_root_def_id(def_id); let generics = tcx.generics_of(enclosing_fn_def_id); // Truncate the substs to the length of the above generics. This will cut off // anything closure- or generator-specific. let substs = substs.truncate_to(tcx, generics); push_generic_params_internal(tcx, substs, output, visited); } fn push_close_angle_bracket(cpp_like_debuginfo: bool, output: &mut String) { // MSVC debugger always treats `>>` as a shift, even when parsing templates, // so add a space to avoid confusion. if cpp_like_debuginfo && output.ends_with('>') { output.push(' ') }; output.push('>'); } fn pop_close_angle_bracket(output: &mut String) { assert!(output.ends_with('>'), "'output' does not end with '>': {}", output); output.pop(); if output.ends_with(' ') { output.pop(); } } fn push_arg_separator(cpp_like_debuginfo: bool, output: &mut String) { // Natvis does not always like having spaces between parts of the type name // and this causes issues when we need to write a typename in natvis, for example // as part of a cast like the `HashMap` visualizer does. if cpp_like_debuginfo { output.push(','); } else { output.push_str(", "); }; } fn pop_arg_separator(output: &mut String) { if output.ends_with(' ') { output.pop(); } assert!(output.ends_with(',')); output.pop(); } /// Check if we should generate C++ like names and debug information. pub fn cpp_like_debuginfo(tcx: TyCtxt<'_>) -> bool { tcx.sess.target.is_like_msvc }