Improved support of collapse_debuginfo attribute for macros.

This commit is contained in:
Andrew Zhogin 2023-11-29 12:49:48 +07:00
parent ca9ff83f1b
commit f2dbebafad
8 changed files with 513 additions and 39 deletions

View file

@ -68,15 +68,7 @@ pub(crate) fn get_span_loc(
// 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];

View file

@ -228,21 +228,16 @@ fn adjusted_span_and_dbg_scope(
/// 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(

View file

@ -50,7 +50,7 @@
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};
@ -2515,21 +2515,21 @@ pub fn adjust_ident_and_get_scope(
(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]

View file

@ -443,18 +443,46 @@ fn marks(&self, mut ctxt: SyntaxContext) -> Vec<(ExpnId, Transparency)> {
}
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<ExpnId> {
let mut scope = None;
while !self.is_descendant_of(expn_id, self.outer_expn(*ctxt)) {
@ -571,6 +599,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| {

View file

@ -568,13 +568,6 @@ pub fn from_expansion(self) -> bool {
self.ctxt() != SyntaxContext::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, _))

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}