From 45c1dcd1627da62d9eac07c0ef5a4828241a80e3 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 20 Jul 2025 15:08:40 +0000 Subject: [PATCH] Add debuginfo_transparent attribute for structs This attribute causes the struct to be unwrapped at the debuginfo level the same way that repr(transparent) unwraps it at the ABI level. This is useful for preventing types like NonNull and Unique from making the debuginfo harder to read when pretty printers aren't used. --- .../src/attributes/debuginfo.rs | 15 +++++++ .../rustc_attr_parsing/src/attributes/mod.rs | 1 + compiler/rustc_attr_parsing/src/context.rs | 2 + .../src/debuginfo/metadata.rs | 12 ++++++ compiler/rustc_feature/src/builtin_attrs.rs | 5 +++ compiler/rustc_feature/src/unstable.rs | 2 + .../rustc_hir/src/attrs/data_structures.rs | 3 ++ .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_passes/messages.ftl | 4 ++ compiler/rustc_passes/src/check_attr.rs | 11 ++++++ compiler/rustc_passes/src/errors.rs | 9 +++++ compiler/rustc_span/src/symbol.rs | 2 + library/core/src/lib.rs | 1 + library/core/src/num/niche_types.rs | 1 + library/core/src/ptr/non_null.rs | 1 + library/core/src/ptr/unique.rs | 1 + src/etc/gdb_providers.py | 34 ++++++++++------ src/tools/tidy/src/features.rs | 1 + tests/debuginfo/debuginfo-attrs.rs | 39 +++++++++++++++++++ tests/debuginfo/strings-and-strs.rs | 5 +-- 20 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 compiler/rustc_attr_parsing/src/attributes/debuginfo.rs create mode 100644 tests/debuginfo/debuginfo-attrs.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/debuginfo.rs b/compiler/rustc_attr_parsing/src/attributes/debuginfo.rs new file mode 100644 index 0000000000000..2811bb9764d9e --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/debuginfo.rs @@ -0,0 +1,15 @@ +use rustc_hir::Target; +use rustc_hir::attrs::AttributeKind; +use rustc_span::{Span, Symbol, sym}; + +use crate::attributes::{NoArgsAttributeParser, OnDuplicate}; +use crate::context::MaybeWarn::Allow; +use crate::context::{AllowedTargets, Stage}; + +pub(crate) struct DebuginfoTransparentParser; +impl NoArgsAttributeParser for DebuginfoTransparentParser { + const PATH: &[Symbol] = &[sym::debuginfo_transparent]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); + const CREATE: fn(Span) -> AttributeKind = AttributeKind::DebuginfoTransparent; +} diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index ed5d1d92b8caf..6850b0aeb2b0f 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -31,6 +31,7 @@ pub(crate) mod cfg; pub(crate) mod cfg_old; pub(crate) mod codegen_attrs; pub(crate) mod confusables; +pub(crate) mod debuginfo; pub(crate) mod deprecation; pub(crate) mod dummy; pub(crate) mod inline; diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index bebe3350c4e0d..ede15295b94af 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -25,6 +25,7 @@ use crate::attributes::codegen_attrs::{ TargetFeatureParser, TrackCallerParser, UsedParser, }; use crate::attributes::confusables::ConfusablesParser; +use crate::attributes::debuginfo::DebuginfoTransparentParser; use crate::attributes::deprecation::DeprecationParser; use crate::attributes::dummy::DummyParser; use crate::attributes::inline::{InlineParser, RustcForceInlineParser}; @@ -199,6 +200,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs index 0e9dbfba658d2..4b5780738126f 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs @@ -9,8 +9,10 @@ use libc::{c_longlong, c_uint}; use rustc_abi::{Align, Size}; use rustc_codegen_ssa::debuginfo::type_names::{VTableNameKind, cpp_like_debuginfo}; use rustc_codegen_ssa::traits::*; +use rustc_hir::attrs::AttributeKind; use rustc_hir::def::{CtorKind, DefKind}; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_hir::find_attr; use rustc_middle::bug; use rustc_middle::ty::layout::{ HasTypingEnv, LayoutOf, TyAndLayout, WIDE_PTR_ADDR, WIDE_PTR_EXTRA, @@ -1070,6 +1072,16 @@ fn build_struct_type_di_node<'ll, 'tcx>( None }; + if find_attr!(cx.tcx.get_all_attrs(adt_def.did()), AttributeKind::DebuginfoTransparent(..)) { + let ty = struct_type_and_layout.non_1zst_field(cx).unwrap().1.ty; + + let di_node = type_di_node(cx, ty); + + return_if_di_node_created_in_meantime!(cx, unique_type_id); + + return DINodeCreationResult::new(di_node, false); + } + type_map::build_type_with_children( cx, type_map::stub( diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index ab6b8f9280217..d238d9dc46acd 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -880,6 +880,11 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ EncodeCrossCrate::No, loop_match, experimental!(loop_match) ), + gated!( + debuginfo_transparent, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::Yes, debuginfo_attrs, experimental!(debuginfo_transparent) + ), + // ========================================================================== // Internal attributes: Stability, deprecation, and unsafe: // ========================================================================== diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 87ecc7b41e213..9aa9e52bb854b 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -459,6 +459,8 @@ declare_features! ( (unstable, custom_inner_attributes, "1.30.0", Some(54726)), /// Allows custom test frameworks with `#![test_runner]` and `#[test_case]`. (unstable, custom_test_frameworks, "1.30.0", Some(50297)), + /// Allows `debuginfo_*` attributes. + (unstable, debuginfo_attrs, "CURRENT_RUSTC_VERSION", None), /// Allows declarative macros 2.0 (`macro`). (unstable, decl_macro, "1.17.0", Some(39412)), /// Allows the use of default values on struct definitions and the construction of struct diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 510fc83297829..ba389f852e793 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -324,6 +324,9 @@ pub enum AttributeKind { /// Represents `#[coverage(..)]`. Coverage(Span, CoverageAttrKind), + /// Represents `#[debuginfo_transparent]`. + DebuginfoTransparent(Span), + ///Represents `#[rustc_deny_explicit_impl]`. DenyExplicitImpl(Span), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 84a975523f2c8..aff0ccf11e710 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -31,6 +31,7 @@ impl AttributeKind { ConstTrait(..) => No, Coroutine(..) => No, Coverage(..) => No, + DebuginfoTransparent { .. } => Yes, DenyExplicitImpl(..) => No, Deprecation { .. } => Yes, DoNotImplementViaObject(..) => No, diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 7481b0ea96018..16f996e1082c5 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -83,6 +83,10 @@ passes_dead_codes = } } never {$participle} +passes_debuginfo_transparent = + attribute should be applied to `#[repr(transparent)]` types + .label = not a `#[repr(transparent)]` type + passes_debug_visualizer_invalid = invalid argument .note_1 = expected: `natvis_file = "..."` diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 0e28c51e981f8..3e81717a2979a 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -138,6 +138,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> { &Attribute::Parsed(AttributeKind::TypeConst(attr_span)) => { self.check_type_const(hir_id, attr_span, target) } + &Attribute::Parsed(AttributeKind::DebuginfoTransparent(attr_span)) => { + self.check_debuginfo_transparent(attr_span, span, attrs) + }, Attribute::Parsed( AttributeKind::Stability { span: attr_span, @@ -2015,6 +2018,14 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } + fn check_debuginfo_transparent(&self, attr_span: Span, span: Span, attrs: &[Attribute]) { + if !find_attr!(attrs, AttributeKind::Repr { reprs, .. } => reprs.iter().any(|(r, _)| r == &ReprAttr::ReprTransparent)) + .unwrap_or(false) + { + self.dcx().emit_err(errors::DebuginfoTransparent { span, attr_span }); + } + } + fn check_rustc_pub_transparent(&self, attr_span: Span, span: Span, attrs: &[Attribute]) { if !find_attr!(attrs, AttributeKind::Repr { reprs, .. } => reprs.iter().any(|(r, _)| r == &ReprAttr::ReprTransparent)) .unwrap_or(false) diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index c5d5155d0e5a5..ca0d9d7710950 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -112,6 +112,15 @@ pub(crate) struct AttrShouldBeAppliedToStatic { pub defn_span: Span, } +#[derive(Diagnostic)] +#[diag(passes_debuginfo_transparent)] +pub(crate) struct DebuginfoTransparent { + #[primary_span] + pub attr_span: Span, + #[label] + pub span: Span, +} + #[derive(Diagnostic)] #[diag(passes_doc_expect_str)] pub(crate) struct DocExpectStr<'a> { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 416ce27367e5e..799829a6eebe2 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -809,6 +809,8 @@ symbols! { debug_tuple, debug_tuple_fields_finish, debugger_visualizer, + debuginfo_attrs, + debuginfo_transparent, decl_macro, declare_lint_pass, decode, diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index a6825b4be683a..c3e927496ec41 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -108,6 +108,7 @@ #![feature(const_eval_select)] #![feature(core_intrinsics)] #![feature(coverage_attribute)] +#![feature(debuginfo_attrs)] #![feature(disjoint_bitor)] #![feature(internal_impls_macro)] #![feature(ip)] diff --git a/library/core/src/num/niche_types.rs b/library/core/src/num/niche_types.rs index d57b1d433e51c..0b74f874dd335 100644 --- a/library/core/src/num/niche_types.rs +++ b/library/core/src/num/niche_types.rs @@ -18,6 +18,7 @@ macro_rules! define_valid_range_type { #[repr(transparent)] #[rustc_layout_scalar_valid_range_start($low)] #[rustc_layout_scalar_valid_range_end($high)] + #[debuginfo_transparent] $(#[$m])* $vis struct $name($int); diff --git a/library/core/src/ptr/non_null.rs b/library/core/src/ptr/non_null.rs index 117eb18826e49..bb3d7e42d5f23 100644 --- a/library/core/src/ptr/non_null.rs +++ b/library/core/src/ptr/non_null.rs @@ -72,6 +72,7 @@ use crate::{fmt, hash, intrinsics, mem, ptr}; #[rustc_layout_scalar_valid_range_start(1)] #[rustc_nonnull_optimization_guaranteed] #[rustc_diagnostic_item = "NonNull"] +#[debuginfo_transparent] pub struct NonNull { // Remember to use `.as_ptr()` instead of `.pointer`, as field projecting to // this is banned by . diff --git a/library/core/src/ptr/unique.rs b/library/core/src/ptr/unique.rs index 4302c1b1e4451..b116edc1d1939 100644 --- a/library/core/src/ptr/unique.rs +++ b/library/core/src/ptr/unique.rs @@ -32,6 +32,7 @@ use crate::ptr::NonNull; )] #[doc(hidden)] #[repr(transparent)] +#[debuginfo_transparent] pub struct Unique { pointer: NonNull, // NOTE: this marker has no consequences for variance, but is necessary diff --git a/src/etc/gdb_providers.py b/src/etc/gdb_providers.py index c8f4a32cb17e2..005c9957eb3a1 100644 --- a/src/etc/gdb_providers.py +++ b/src/etc/gdb_providers.py @@ -10,12 +10,15 @@ def unwrap_unique_or_non_null(unique_or_nonnull): - # BACKCOMPAT: rust 1.32 - # https://github.com/rust-lang/rust/commit/7a0911528058e87d22ea305695f4047572c5e067 - # BACKCOMPAT: rust 1.60 - # https://github.com/rust-lang/rust/commit/2a91eeac1a2d27dd3de1bf55515d765da20fd86f - ptr = unique_or_nonnull["pointer"] - return ptr if ptr.type.code == gdb.TYPE_CODE_PTR else ptr[ptr.type.fields()[0]] + if unique_or_nonnull.type.code != gdb.TYPE_CODE_PTR: + # BACKCOMPAT: rust 1.32 + # https://github.com/rust-lang/rust/commit/7a0911528058e87d22ea305695f4047572c5e067 + # BACKCOMPAT: rust 1.60 + # https://github.com/rust-lang/rust/commit/2a91eeac1a2d27dd3de1bf55515d765da20fd86f + # BACKCOMPAT: rust 1.89 + ptr = unique_or_nonnull["pointer"] + return ptr if ptr.type.code == gdb.TYPE_CODE_PTR else ptr[ptr.type.fields()[0]] + return unique_or_nonnull # GDB 14 has a tag class that indicates that extension methods are ok @@ -255,12 +258,16 @@ def __init__(self, valobj): field = list(fields)[0] inner_valobj = valobj[field.name] + if inner_valobj.type.code != gdb.TYPE_CODE_INT: + # BACKCOMPAT: rust 1.89 + inner_fields = inner_valobj.type.fields() + assert len(inner_fields) == 1 + inner_field = list(inner_fields)[0] - inner_fields = inner_valobj.type.fields() - assert len(inner_fields) == 1 - inner_field = list(inner_fields)[0] + self._value = str(inner_valobj[inner_field.name]) + return - self._value = str(inner_valobj[inner_field.name]) + self._value = inner_valobj def to_string(self): return self._value @@ -277,7 +284,9 @@ def cast_to_internal(node): internal_type = gdb.lookup_type(internal_type_name) return node.cast(internal_type.pointer()) - if node_ptr.type.name.startswith("alloc::collections::btree::node::BoxedNode<"): + if node_ptr.type.name is not None and node_ptr.type.name.startswith( + "alloc::collections::btree::node::BoxedNode<" + ): # BACKCOMPAT: rust 1.49 node_ptr = node_ptr["ptr"] node_ptr = unwrap_unique_or_non_null(node_ptr) @@ -427,7 +436,8 @@ def __init__(self, valobj, show_values=True): table = self._table() table_inner = table["table"] capacity = int(table_inner["bucket_mask"]) + 1 - ctrl = table_inner["ctrl"]["pointer"] + # BACKCOMPAT: rust 1.89 + ctrl = unwrap_unique_or_non_null(table_inner["ctrl"]) self._size = int(table_inner["items"]) self._pair_type = table.type.template_argument(0).strip_typedefs() diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs index 6618ba24be609..88d82cfa42ae1 100644 --- a/src/tools/tidy/src/features.rs +++ b/src/tools/tidy/src/features.rs @@ -102,6 +102,7 @@ pub fn check( walk_many( &[ + &tests_path.join("debuginfo"), &tests_path.join("ui"), &tests_path.join("ui-fulldeps"), &tests_path.join("rustdoc-ui"), diff --git a/tests/debuginfo/debuginfo-attrs.rs b/tests/debuginfo/debuginfo-attrs.rs new file mode 100644 index 0000000000000..a309e38571d29 --- /dev/null +++ b/tests/debuginfo/debuginfo-attrs.rs @@ -0,0 +1,39 @@ +//@ compile-flags:-g + +// gate-test-debuginfo_attrs +// Tests the `#[debuginfo_transparent]` attribute. + +// === CDB TESTS ================================================================================== +// cdb-command: g + +// cdb-command: dx transparent +// cdb-check:transparent : 1 [Type: u32] +// cdb-check: [] [Type: u32] + +// === GDB TESTS =================================================================================== + +// gdb-command:run + +// gdb-command:print transparent +// gdb-check:[...]$1 = 1 + +// === LLDB TESTS ================================================================================== + +// lldb-command:run + +// lldb-command:v transparent +// lldb-check:[...] 1 + +#![feature(debuginfo_attrs)] + +#[repr(transparent)] +#[debuginfo_transparent] +struct Transparent(u32); + +fn main() { + let transparent = Transparent(1); + + zzz(); // #break +} + +fn zzz() {} diff --git a/tests/debuginfo/strings-and-strs.rs b/tests/debuginfo/strings-and-strs.rs index 392cf697e110b..bac75dbc60872 100644 --- a/tests/debuginfo/strings-and-strs.rs +++ b/tests/debuginfo/strings-and-strs.rs @@ -8,7 +8,7 @@ // gdb-command:run // gdb-command:print plain_string -// gdb-check:$1 = alloc::string::String {vec: alloc::vec::Vec {buf: alloc::raw_vec::RawVec {inner: alloc::raw_vec::RawVecInner {ptr: core::ptr::unique::Unique {pointer: core::ptr::non_null::NonNull {pointer: 0x[...]}, _marker: core::marker::PhantomData}, cap: core::num::niche_types::UsizeNoHighBit (5), alloc: alloc::alloc::Global}, _marker: core::marker::PhantomData}, len: 5}} +// gdb-check:$1 = alloc::string::String {vec: alloc::vec::Vec {buf: alloc::raw_vec::RawVec {inner: alloc::raw_vec::RawVecInner {ptr: 0x[...], cap: 5, alloc: alloc::alloc::Global}, _marker: core::marker::PhantomData}, len: 5}} // gdb-command:print plain_str // gdb-check:$2 = "Hello" @@ -20,7 +20,7 @@ // gdb-check:$4 = ("Hello", "World") // gdb-command:print str_in_rc -// gdb-check:$5 = alloc::rc::Rc<&str, alloc::alloc::Global> {ptr: core::ptr::non_null::NonNull> {pointer: 0x[...]}, phantom: core::marker::PhantomData>, alloc: alloc::alloc::Global} +// gdb-check:$5 = alloc::rc::Rc<&str, alloc::alloc::Global> {ptr: 0x[...], alloc: alloc::alloc::Global} // === LLDB TESTS ================================================================================== // lldb-command:run @@ -39,7 +39,6 @@ // lldb-command:v str_in_rc // lldb-check:(alloc::rc::Rc<&str, alloc::alloc::Global>) str_in_rc = strong=1, weak=0 { value = "Hello" { [0] = 'H' [1] = 'e' [2] = 'l' [3] = 'l' [4] = 'o' } } - #![allow(unused_variables)] pub struct Foo<'a> {