about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAndrew Zhogin <andrew.zhogin@gmail.com>2023-11-29 12:49:48 +0700
committerAndrew Zhogin <andrew.zhogin@gmail.com>2024-01-08 17:47:18 +0700
commitf2dbebafad629316edf37f9fc6a91633d9d1a702 (patch)
tree80aff022090efbd758320277738cf73d6798a237
parentca9ff83f1be558d4ce0a9a49b3d4d25034e5bd1e (diff)
downloadrust-f2dbebafad629316edf37f9fc6a91633d9d1a702.tar.gz
rust-f2dbebafad629316edf37f9fc6a91633d9d1a702.zip
Improved support of collapse_debuginfo attribute for macros.
-rw-r--r--compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs10
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/debuginfo.rs17
-rw-r--r--compiler/rustc_middle/src/ty/mod.rs22
-rw-r--r--compiler/rustc_span/src/hygiene.rs34
-rw-r--r--compiler/rustc_span/src/lib.rs7
-rw-r--r--tests/debuginfo/collapse-debuginfo-in-non-collapse-macro.rs124
-rw-r--r--tests/debuginfo/skip_second_statement.rs168
-rw-r--r--tests/debuginfo/skip_second_statement_collapse.rs170
8 files changed, 513 insertions, 39 deletions
diff --git a/compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs b/compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs
index 6230ca15d6e..d1b21d0a0b6 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 14915e816ee..48f3f4f2522 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 9f1ff4538aa..8983fb7e40c 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -50,7 +50,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};
@@ -2515,21 +2515,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 b717229b68d..d9ed85dbb98 100644
--- a/compiler/rustc_span/src/hygiene.rs
+++ b/compiler/rustc_span/src/hygiene.rs
@@ -443,18 +443,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<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| {
diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs
index 8f64eed9a87..d42c420ea9c 100644
--- a/compiler/rustc_span/src/lib.rs
+++ b/compiler/rustc_span/src/lib.rs
@@ -568,13 +568,6 @@ impl Span {
         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, _))
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 00000000000..d9500c3641e
--- /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 00000000000..535b5474763
--- /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 00000000000..a0557ca9fee
--- /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
+}