diff --git a/compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs b/compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs index 6230ca15d6e10..d1b21d0a0b6c5 100644 --- a/compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs +++ b/compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs @@ -68,15 +68,7 @@ impl DebugContext { // In order to have a good line stepping behavior in debugger, we overwrite debug // locations of macro expansions with that of the outermost expansion site (when the macro is // annotated with `#[collapse_debuginfo]` or when `-Zdebug-macros` is provided). - let span = if tcx.should_collapse_debuginfo(span) { - span - } else { - // Walk up the macro expansion chain until we reach a non-expanded span. - // We also stop at the function body level because no line stepping can occur - // at the level above that. - rustc_span::hygiene::walk_chain(span, function_span.ctxt()) - }; - + let span = tcx.collapsed_debuginfo(span, function_span); match tcx.sess.source_map().lookup_line(span.lo()) { Ok(SourceFileAndLine { sf: file, line }) => { let line_pos = file.lines()[line]; diff --git a/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs b/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs index 14915e816ee9b..48f3f4f25228a 100644 --- a/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs +++ b/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs @@ -228,21 +228,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { /// In order to have a good line stepping behavior in debugger, we overwrite debug /// locations of macro expansions with that of the outermost expansion site (when the macro is /// annotated with `#[collapse_debuginfo]` or when `-Zdebug-macros` is provided). - fn adjust_span_for_debugging(&self, mut span: Span) -> Span { + fn adjust_span_for_debugging(&self, span: Span) -> Span { // Bail out if debug info emission is not enabled. if self.debug_context.is_none() { return span; } - - if self.cx.tcx().should_collapse_debuginfo(span) { - // Walk up the macro expansion chain until we reach a non-expanded span. - // We also stop at the function body level because no line stepping can occur - // at the level above that. - // Use span of the outermost expansion site, while keeping the original lexical scope. - span = rustc_span::hygiene::walk_chain(span, self.mir.span.ctxt()); - } - - span + // Walk up the macro expansion chain until we reach a non-expanded span. + // We also stop at the function body level because no line stepping can occur + // at the level above that. + // Use span of the outermost expansion site, while keeping the original lexical scope. + self.cx.tcx().collapsed_debuginfo(span, self.mir.span) } fn spill_operand_to_stack( diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 4af82f124719a..9e8d7c2ef3ecb 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -51,7 +51,7 @@ use rustc_session::lint::LintBuffer; pub use rustc_session::lint::RegisteredTools; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{kw, sym, Ident, Symbol}; -use rustc_span::{ExpnId, ExpnKind, Span}; +use rustc_span::{hygiene, ExpnId, ExpnKind, Span}; use rustc_target::abi::{Align, FieldIdx, Integer, IntegerType, VariantIdx}; pub use rustc_target::abi::{ReprFlags, ReprOptions}; pub use rustc_type_ir::{DebugWithInfcx, InferCtxtLike, WithInfcx}; @@ -2518,21 +2518,21 @@ impl<'tcx> TyCtxt<'tcx> { (ident, scope) } - /// Returns `true` if the debuginfo for `span` should be collapsed to the outermost expansion - /// site. Only applies when `Span` is the result of macro expansion. + /// Returns corrected span if the debuginfo for `span` should be collapsed to the outermost + /// expansion site (with collapse_debuginfo attribute if the corresponding feature enabled). + /// Only applies when `Span` is the result of macro expansion. /// /// - If the `collapse_debuginfo` feature is enabled then debuginfo is not collapsed by default - /// and only when a macro definition is annotated with `#[collapse_debuginfo]`. + /// and only when a (some enclosing) macro definition is annotated with `#[collapse_debuginfo]`. /// - If `collapse_debuginfo` is not enabled, then debuginfo is collapsed by default. /// /// When `-Zdebug-macros` is provided then debuginfo will never be collapsed. - pub fn should_collapse_debuginfo(self, span: Span) -> bool { - !self.sess.opts.unstable_opts.debug_macros - && if self.features().collapse_debuginfo { - span.in_macro_expansion_with_collapse_debuginfo() - } else { - span.from_expansion() - } + pub fn collapsed_debuginfo(self, span: Span, upto: Span) -> Span { + if self.sess.opts.unstable_opts.debug_macros || !span.from_expansion() { + return span; + } + let collapse_debuginfo_enabled = self.features().collapse_debuginfo; + hygiene::walk_chain_collapsed(span, upto, collapse_debuginfo_enabled) } #[inline] diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index 6a15961ee20fb..d03965b539c84 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -445,18 +445,46 @@ impl HygieneData { } fn walk_chain(&self, mut span: Span, to: SyntaxContext) -> Span { + let orig_span = span; debug!("walk_chain({:?}, {:?})", span, to); debug!("walk_chain: span ctxt = {:?}", span.ctxt()); - while span.from_expansion() && span.ctxt() != to { + while span.ctxt() != to && span.from_expansion() { let outer_expn = self.outer_expn(span.ctxt()); debug!("walk_chain({:?}): outer_expn={:?}", span, outer_expn); let expn_data = self.expn_data(outer_expn); debug!("walk_chain({:?}): expn_data={:?}", span, expn_data); span = expn_data.call_site; } + debug!("walk_chain: for span {:?} >>> return span = {:?}", orig_span, span); span } + // We need to walk up and update return span if we meet macro instantiation to be collapsed + fn walk_chain_collapsed( + &self, + mut span: Span, + to: Span, + collapse_debuginfo_enabled: bool, + ) -> Span { + let orig_span = span; + let mut ret_span = span; + + debug!("walk_chain_collapsed({:?}, {:?})", span, to); + debug!("walk_chain_collapsed: span ctxt = {:?}", span.ctxt()); + while !span.eq_ctxt(to) && span.from_expansion() { + let outer_expn = self.outer_expn(span.ctxt()); + debug!("walk_chain_collapsed({:?}): outer_expn={:?}", span, outer_expn); + let expn_data = self.expn_data(outer_expn); + debug!("walk_chain_collapsed({:?}): expn_data={:?}", span, expn_data); + span = expn_data.call_site; + if !collapse_debuginfo_enabled || expn_data.collapse_debuginfo { + ret_span = span; + } + } + debug!("walk_chain_collapsed: for span {:?} >>> return span = {:?}", orig_span, ret_span); + ret_span + } + fn adjust(&self, ctxt: &mut SyntaxContext, expn_id: ExpnId) -> Option { let mut scope = None; while !self.is_descendant_of(expn_id, self.outer_expn(*ctxt)) { @@ -573,6 +601,10 @@ pub fn walk_chain(span: Span, to: SyntaxContext) -> Span { HygieneData::with(|data| data.walk_chain(span, to)) } +pub fn walk_chain_collapsed(span: Span, to: Span, collapse_debuginfo_enabled: bool) -> Span { + HygieneData::with(|hdata| hdata.walk_chain_collapsed(span, to, collapse_debuginfo_enabled)) +} + pub fn update_dollar_crate_names(mut get_name: impl FnMut(SyntaxContext) -> Symbol) { // The new contexts that need updating are at the end of the list and have `$crate` as a name. let (len, to_update) = HygieneData::with(|data| { diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index bac0802241d68..4235293823c29 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -567,13 +567,6 @@ impl Span { !self.ctxt().is_root() } - /// Returns `true` if `span` originates in a macro's expansion where debuginfo should be - /// collapsed. - pub fn in_macro_expansion_with_collapse_debuginfo(self) -> bool { - let outer_expn = self.ctxt().outer_expn_data(); - matches!(outer_expn.kind, ExpnKind::Macro(..)) && outer_expn.collapse_debuginfo - } - /// Returns `true` if `span` originates in a derive-macro's expansion. pub fn in_derive_expansion(self) -> bool { matches!(self.ctxt().outer_expn_data().kind, ExpnKind::Macro(MacroKind::Derive, _)) diff --git a/tests/debuginfo/collapse-debuginfo-in-non-collapse-macro.rs b/tests/debuginfo/collapse-debuginfo-in-non-collapse-macro.rs new file mode 100644 index 0000000000000..d9500c3641ef6 --- /dev/null +++ b/tests/debuginfo/collapse-debuginfo-in-non-collapse-macro.rs @@ -0,0 +1,124 @@ +// ignore-lldb +#![feature(collapse_debuginfo)] + +// Test that statement, skipped/added/reordered by macros, is correctly processed in debuginfo. +// When nested macros instantiations are tagged with collapse_debuginfo attribute, +// debug info should be corrected to the first outer macro instantiation +// without collapse_debuginfo attribute. +// collapse_debuginfo feature enabled. + +// compile-flags:-g + +// === GDB TESTS =================================================================================== + +// gdb-command:run +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_rem_call1[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call1_pre[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_in_proxy[...] +// gdb-command:next 2 +// gdb-check:[...]#loc_rem_call3[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_add_call1[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call1_pre[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_in_proxy[...] +// gdb-command:next 2 +// gdb-check:[...]#loc_add_macro[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_add_call3[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_reorder_call2[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_reorder_call3[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_reorder_call1[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call1_pre[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_in_proxy[...] +// gdb-command:next 2 +// gdb-command:frame +// gdb-command:continue + +#[inline(never)] +fn myprintln_impl(text: &str) { + println!("{}", text) +} + +#[collapse_debuginfo] +macro_rules! myprintln { + ($($arg:tt)*) => {{ + myprintln_impl($($arg)*); + }}; +} + +macro_rules! proxy_println { + ($($arg:tt)*) => {{ + myprintln!($($arg)*); // #loc_in_proxy + }}; +} + +// Macro accepts 3 statements and removes the 2nd statement +macro_rules! remove_second_statement { + ($s1:stmt; $s2:stmt; $s3:stmt;) => { $s1 $s3 } +} + +macro_rules! add_second_statement { + ($s1:stmt; $s3:stmt;) => { + $s1 + call2(); // #loc_add_macro + $s3 + } +} + +macro_rules! reorder_statements { + ($s1:stmt; $s2:stmt; $s3:stmt;) => { $s2 $s3 $s1 } +} + +fn call1() { + let rv = 0; // #loc_call1_pre + proxy_println!("one"); // #loc_call1 +} + +fn call2() { + proxy_println!("two"); // #loc_call2 +} + +fn call3() { + proxy_println!("three"); // #loc_call3 +} + +fn main() { + let ret = 0; // #break, step should go to call1 + remove_second_statement! { // #loc_rem_hdr + call1(); // #loc_rem_call1 + call2(); // #loc_rem_call2 + call3(); // #loc_rem_call3 + } + add_second_statement! { // #loc_add_hdr + call1(); // #loc_add_call1 + call3(); // #loc_add_call3 + } + reorder_statements! { // #loc_reorder_hdr + call1(); // #loc_reorder_call1 + call2(); // #loc_reorder_call2 + call3(); // #loc_reorder_call3 + } + std::process::exit(ret); // #loc_exit +} diff --git a/tests/debuginfo/skip_second_statement.rs b/tests/debuginfo/skip_second_statement.rs new file mode 100644 index 0000000000000..535b54747631a --- /dev/null +++ b/tests/debuginfo/skip_second_statement.rs @@ -0,0 +1,168 @@ +// ignore-lldb + +// Test that statement, skipped/added/reordered by macros, is correctly processed in debuginfo. +// Performed step-over and step-into debug stepping through call statements. +// collapse_debuginfo feature disabled. + +// compile-flags:-g + +// === GDB TESTS =================================================================================== + +// gdb-command:run +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_rem1_call1[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_rem1_call3[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_rem2_call1[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call1[...] +// gdb-command:next 2 +// gdb-check:[...]#loc_rem2_call3[...] +// gdb-command:step 2 +// gdb-command:frame +// gdb-check:[...]#loc_call3_println[...] +// gdb-command:next 3 +// gdb-command:frame +// gdb-check:[...]#loc_after_rem[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_add1_call1[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_add1_hdr[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_add1_call3[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_add2_call1[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call1[...] +// gdb-command:next 2 +// gdb-check:[...]#loc_add2_hdr[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call2[...] +// gdb-command:next 2 +// gdb-command:frame +// gdb-check:[...]#loc_add2_call3[...] +// gdb-command:step 2 +// gdb-command:frame +// gdb-check:[...]#loc_call3_println[...] +// gdb-command:next 3 +// gdb-command:frame +// gdb-check:[...]#loc_reorder1_call2[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_reorder1_call3[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_reorder1_call1[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_reorder2_call2[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call2[...] +// gdb-command:next 2 +// gdb-command:frame +// gdb-check:[...]#loc_reorder2_call3[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call3[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call3_println[...] +// gdb-command:next 3 +// gdb-command:frame +// gdb-check:[...]#loc_reorder2_call1[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call1[...] +// gdb-command:next 2 +// gdb-command:continue + +#[inline(never)] +fn myprintln_impl(text: &str) { + println!("{}", text) +} + +macro_rules! myprintln { + ($($arg:tt)*) => {{ + myprintln_impl($($arg)*); + }}; +} + +// Macro accepts 3 statements and removes the 2nd statement +macro_rules! remove_second_statement { + ($s1:stmt; $s2:stmt; $s3:stmt;) => { $s1 $s3 } +} + +macro_rules! add_second_statement { + ($s1:stmt; $s3:stmt;) => { + $s1 + call2(); // #loc_add_macro + $s3 + } +} + +macro_rules! reorder_statements { + ($s1:stmt; $s2:stmt; $s3:stmt;) => { $s2 $s3 $s1 } +} + +fn call1() { + myprintln!("one"); // #loc_call1 +} + +fn call2() { + myprintln!("two"); // #loc_call2 +} + +fn call3() { + (||{ + myprintln!("three") // #loc_call3_println + })(); // #loc_call3 +} + +fn main() { + let ret = 0; // #break, step should go to call1 + remove_second_statement! { // #loc_rem1_hdr + call1(); // #loc_rem1_call1, breakpoint should set to call1, step should go call3 + call2(); // #loc_rem1_call2, breakpoint should set to call3 + call3(); // #loc_rem1_call3 + } + remove_second_statement! { // #loc_rem2_hdr + call1(); // #loc_rem2_call1, breakpoint should set to call1, step should go call3 + call2(); // #loc_rem2_call2, breakpoint should set to call3 + call3(); // #loc_rem2_call3, breakpoint should set to call3 + } + myprintln!("After remove_second_statement test"); // #loc_after_rem + + add_second_statement! { // #loc_add1_hdr + call1(); // #loc_add1_call1 + call3(); // #loc_add1_call3 + } + add_second_statement! { // #loc_add2_hdr + call1(); // #loc_add2_call1 + call3(); // #loc_add2_call3 + } + + reorder_statements! { // #loc_reorder1_hdr + call1(); // #loc_reorder1_call1 + call2(); // #loc_reorder1_call2 + call3(); // #loc_reorder1_call3 + } + reorder_statements! { // #loc_reorder2_hdr + call1(); // #loc_reorder2_call1 + call2(); // #loc_reorder2_call2 + call3(); // #loc_reorder2_call3 + } + + std::process::exit(ret); // #loc_exit +} diff --git a/tests/debuginfo/skip_second_statement_collapse.rs b/tests/debuginfo/skip_second_statement_collapse.rs new file mode 100644 index 0000000000000..a0557ca9feec2 --- /dev/null +++ b/tests/debuginfo/skip_second_statement_collapse.rs @@ -0,0 +1,170 @@ +// ignore-lldb +#![feature(collapse_debuginfo)] + +// Test that statement, skipped/added/reordered by macros, is correctly processed in debuginfo +// Performed step-over and step-into debug stepping through call statements. +// collapse_debuginfo feature enabled. + +// compile-flags:-g + +// === GDB TESTS =================================================================================== + +// gdb-command:run +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_rem1_call1[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_rem1_call3[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_rem2_call1[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call1[...] +// gdb-command:next 2 +// gdb-check:[...]#loc_rem2_call3[...] +// gdb-command:step 2 +// gdb-command:frame +// gdb-check:[...]#loc_call3_println[...] +// gdb-command:next 3 +// gdb-command:frame +// gdb-check:[...]#loc_after_rem[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_add1_call1[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_add_macro[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_add1_call3[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_add2_call1[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call1[...] +// gdb-command:next 2 +// gdb-check:[...]#loc_add_macro[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call2[...] +// gdb-command:next 2 +// gdb-command:frame +// gdb-check:[...]#loc_add2_call3[...] +// gdb-command:step 2 +// gdb-command:frame +// gdb-check:[...]#loc_call3_println[...] +// gdb-command:next 3 +// gdb-command:frame +// gdb-check:[...]#loc_reorder1_call2[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_reorder1_call3[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_reorder1_call1[...] +// gdb-command:next +// gdb-command:frame +// gdb-check:[...]#loc_reorder2_call2[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call2[...] +// gdb-command:next 2 +// gdb-command:frame +// gdb-check:[...]#loc_reorder2_call3[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call3[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call3_println[...] +// gdb-command:next 3 +// gdb-command:frame +// gdb-check:[...]#loc_reorder2_call1[...] +// gdb-command:step +// gdb-command:frame +// gdb-check:[...]#loc_call1[...] +// gdb-command:next 2 +// gdb-command:continue + +#[inline(never)] +fn myprintln_impl(text: &str) { + println!("{}", text) +} + +#[collapse_debuginfo] +macro_rules! myprintln { + ($($arg:tt)*) => {{ + myprintln_impl($($arg)*); + }}; +} + +// Macro accepts 3 statements and removes the 2nd statement +macro_rules! remove_second_statement { + ($s1:stmt; $s2:stmt; $s3:stmt;) => { $s1 $s3 } +} + +macro_rules! add_second_statement { + ($s1:stmt; $s3:stmt;) => { + $s1 + call2(); // #loc_add_macro + $s3 + } +} + +macro_rules! reorder_statements { + ($s1:stmt; $s2:stmt; $s3:stmt;) => { $s2 $s3 $s1 } +} + +fn call1() { + myprintln!("one"); // #loc_call1 +} + +fn call2() { + myprintln!("two"); // #loc_call2 +} + +fn call3() { + (||{ + myprintln!("three") // #loc_call3_println + })(); // #loc_call3 +} + +fn main() { + let ret = 0; // #break, step should go to call1 + remove_second_statement! { // #loc_rem1_hdr + call1(); // #loc_rem1_call1, breakpoint should set to call1, step should go call3 + call2(); // #loc_rem1_call2, breakpoint should set to call3 + call3(); // #loc_rem1_call3 + } + remove_second_statement! { // #loc_rem2_hdr + call1(); // #loc_rem2_call1, breakpoint should set to call1, step should go call3 + call2(); // #loc_rem2_call2, breakpoint should set to call3 + call3(); // #loc_rem2_call3, breakpoint should set to call3 + } + myprintln!("After remove_second_statement test"); // #loc_after_rem + + add_second_statement! { // #loc_add1_hdr + call1(); // #loc_add1_call1 + call3(); // #loc_add1_call3 + } + add_second_statement! { // #loc_add2_hdr + call1(); // #loc_add2_call1 + call3(); // #loc_add2_call3 + } + + reorder_statements! { // #loc_reorder1_hdr + call1(); // #loc_reorder1_call1 + call2(); // #loc_reorder1_call2 + call3(); // #loc_reorder1_call3 + } + reorder_statements! { // #loc_reorder2_hdr + call1(); // #loc_reorder2_call1 + call2(); // #loc_reorder2_call2 + call3(); // #loc_reorder2_call3 + } + + std::process::exit(ret); // #loc_exit +}