Skip to content
Merged
Prev Previous commit
Next Next commit
don't drop arguments' temporaries in dbg!
(cherry picked from commit 51816ef)
  • Loading branch information
dianne authored and cuviper committed Apr 2, 2026
commit 4f50466be4802fb231200737fb0dc0933f421908
82 changes: 43 additions & 39 deletions library/std/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
//! library.
// ignore-tidy-dbg

#[cfg(test)]
mod tests;

#[doc = include_str!("../../core/src/macros/panic.md")]
#[macro_export]
#[rustc_builtin_macro(std_panic)]
Expand Down Expand Up @@ -359,19 +362,16 @@ macro_rules! dbg {
};
}

/// Internal macro that processes a list of expressions and produces a chain of
/// nested `match`es, one for each expression, before finally calling `eprint!`
/// with the collected information and returning all the evaluated expressions
/// in a tuple.
/// Internal macro that processes a list of expressions, binds their results
/// with `match`, calls `eprint!` with the collected information, and returns
/// all the evaluated expressions in a tuple.
///
/// E.g. `dbg_internal!(() () (1, 2))` expands into
/// ```rust, ignore
/// match 1 {
/// tmp_1 => match 2 {
/// tmp_2 => {
/// eprint!("...", &tmp_1, &tmp_2, /* some other arguments */);
/// (tmp_1, tmp_2)
/// }
/// match (1, 2) {
/// (tmp_1, tmp_2) => {
/// eprint!("...", &tmp_1, &tmp_2, /* some other arguments */);
/// (tmp_1, tmp_2)
/// }
/// }
/// ```
Expand All @@ -380,37 +380,41 @@ macro_rules! dbg {
#[doc(hidden)]
#[rustc_macro_transparency = "semiopaque"]
pub macro dbg_internal {
(($($piece:literal),+) ($($processed:expr => $bound:expr),+) ()) => {{
$crate::eprint!(
$crate::concat!($($piece),+),
$(
$crate::stringify!($processed),
// The `&T: Debug` check happens here (not in the format literal desugaring)
// to avoid format literal related messages and suggestions.
&&$bound as &dyn $crate::fmt::Debug
),+,
// The location returned here is that of the macro invocation, so
// it will be the same for all expressions. Thus, label these
// arguments so that they can be reused in every piece of the
// formatting template.
file=$crate::file!(),
line=$crate::line!(),
column=$crate::column!()
);
// Comma separate the variables only when necessary so that this will
// not yield a tuple for a single expression, but rather just parenthesize
// the expression.
($($bound),+)
}},
(($($piece:literal),*) ($($processed:expr => $bound:expr),*) ($val:expr $(,$rest:expr)*)) => {
(($($piece:literal),+) ($($processed:expr => $bound:ident),+) ()) => {
// Use of `match` here is intentional because it affects the lifetimes
// of temporaries - https://stackoverflow.com/a/48732525/1063961
match $val {
tmp => $crate::macros::dbg_internal!(
($($piece,)* "[{file}:{line}:{column}] {} = {:#?}\n")
($($processed => $bound,)* $val => tmp)
($($rest),*)
),
// Always put the arguments in a tuple to avoid an unused parens lint on the pattern.
match ($($processed,)+) {
($($bound,)+) => {
$crate::eprint!(
$crate::concat!($($piece),+),
$(
$crate::stringify!($processed),
// The `&T: Debug` check happens here (not in the format literal desugaring)
// to avoid format literal related messages and suggestions.
&&$bound as &dyn $crate::fmt::Debug
),+,
// The location returned here is that of the macro invocation, so
// it will be the same for all expressions. Thus, label these
// arguments so that they can be reused in every piece of the
// formatting template.
file=$crate::file!(),
line=$crate::line!(),
column=$crate::column!()
);
// Comma separate the variables only when necessary so that this will
// not yield a tuple for a single expression, but rather just parenthesize
// the expression.
($($bound),+)

}
}
},
(($($piece:literal),*) ($($processed:expr => $bound:ident),*) ($val:expr $(,$rest:expr)*)) => {
$crate::macros::dbg_internal!(
($($piece,)* "[{file}:{line}:{column}] {} = {:#?}\n")
($($processed => $bound,)* $val => tmp)
($($rest),*)
)
},
}
13 changes: 13 additions & 0 deletions library/std/src/macros/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// ignore-tidy-dbg

/// Test for <https://github.com/rust-lang/rust/issues/153850>:
/// `dbg!` shouldn't drop arguments' temporaries.
#[test]
fn no_dropping_temps() {
fn temp() {}

*dbg!(&temp());
*dbg!(&temp(), 1).0;
*dbg!(0, &temp()).1;
*dbg!(0, &temp(), 2).1;
}
47 changes: 5 additions & 42 deletions src/tools/clippy/clippy_lints/src/dbg_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use clippy_utils::macros::{MacroCall, macro_backtrace};
use clippy_utils::source::snippet_with_applicability;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::{Arm, Closure, ClosureKind, CoroutineKind, Expr, ExprKind, LetStmt, LocalSource, Node, Stmt, StmtKind};
use rustc_hir::{Closure, ClosureKind, CoroutineKind, Expr, ExprKind, LetStmt, LocalSource, Node, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;
use rustc_span::{Span, SyntaxContext};
Expand Down Expand Up @@ -92,16 +92,15 @@ impl LateLintPass<'_> for DbgMacro {
(macro_call.span, String::from("()"))
}
},
ExprKind::Match(first, arms, _) => {
let vals = collect_vals(first, arms);
let suggestion = match *vals.as_slice() {
ExprKind::Match(args, _, _) => {
let suggestion = match args.kind {
// dbg!(1) => 1
[val] => {
ExprKind::Tup([val]) => {
snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability)
.to_string()
},
// dbg!(2, 3) => (2, 3)
[first, .., last] => {
ExprKind::Tup([first, .., last]) => {
let snippet = snippet_with_applicability(
cx,
first.span.source_callsite().to(last.span.source_callsite()),
Expand Down Expand Up @@ -165,39 +164,3 @@ fn is_async_move_desugar<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx
fn first_dbg_macro_in_expansion(cx: &LateContext<'_>, span: Span) -> Option<MacroCall> {
macro_backtrace(span).find(|mc| cx.tcx.is_diagnostic_item(sym::dbg_macro, mc.def_id))
}

/// Extracts all value expressions from the `match`-tree generated by `dbg!`.
///
/// E.g. from
/// ```rust, ignore
/// match 1 {
/// tmp_1 => match 2 {
/// tmp_2 => {
/// /* printing */
/// (tmp_1, tmp_2)
/// }
/// }
/// }
/// ```
/// this extracts `1` and `2`.
fn collect_vals<'hir>(first: &'hir Expr<'hir>, mut arms: &'hir [Arm<'hir>]) -> Vec<&'hir Expr<'hir>> {
let mut vals = vec![first];
loop {
let [arm] = arms else {
unreachable!("dbg! macro expansion only has single-arm matches")
};

match is_async_move_desugar(arm.body)
.unwrap_or(arm.body)
.peel_drop_temps()
.kind
{
ExprKind::Block(..) => return vals,
ExprKind::Match(val, a, _) => {
vals.push(val);
arms = a;
},
_ => unreachable!("dbg! macro expansion only results in block or match expressions"),
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ error: Undefined Behavior: memory access failed: ALLOC has been freed, so this p
--> tests/fail/dangling_pointers/dangling_primitive.rs:LL:CC
|
LL | dbg!(*ptr);
| ^^^^^^^^^^ Undefined Behavior occurred here
| ^^^^ Undefined Behavior occurred here
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ error: Undefined Behavior: reading memory at ALLOC[0x0..0x4], but memory is unin
--> tests/fail/function_calls/return_pointer_on_unwind.rs:LL:CC
|
LL | dbg!(x.0);
| ^^^^^^^^^ Undefined Behavior occurred here
| ^^^ Undefined Behavior occurred here
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Expand Down
40 changes: 20 additions & 20 deletions tests/ui/borrowck/dbg-issue-120327.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,44 @@ error[E0382]: use of moved value: `a`
LL | let a = String::new();
| - move occurs because `a` has type `String`, which does not implement the `Copy` trait
LL | dbg!(a);
| ------- value moved here
| - value moved here
LL | return a;
| ^ value used here after move
|
help: consider borrowing instead of transferring ownership
help: consider cloning the value if the performance cost is acceptable
|
LL | dbg!(&a);
| +
LL | dbg!(a.clone());
| ++++++++

error[E0382]: use of moved value: `a`
--> $DIR/dbg-issue-120327.rs:10:12
|
LL | let a = String::new();
| - move occurs because `a` has type `String`, which does not implement the `Copy` trait
LL | dbg!(1, 2, a, 1, 2);
| ------------------- value moved here
| - value moved here
LL | return a;
| ^ value used here after move
|
help: consider borrowing instead of transferring ownership
help: consider cloning the value if the performance cost is acceptable
|
LL | dbg!(1, 2, &a, 1, 2);
| +
LL | dbg!(1, 2, a.clone(), 1, 2);
| ++++++++

error[E0382]: use of moved value: `b`
--> $DIR/dbg-issue-120327.rs:16:12
|
LL | let b: String = "".to_string();
| - move occurs because `b` has type `String`, which does not implement the `Copy` trait
LL | dbg!(a, b);
| ---------- value moved here
| - value moved here
LL | return b;
| ^ value used here after move
|
help: consider borrowing instead of transferring ownership
help: consider cloning the value if the performance cost is acceptable
|
LL | dbg!(a, &b);
| +
LL | dbg!(a, b.clone());
| ++++++++

error[E0382]: use of moved value: `a`
--> $DIR/dbg-issue-120327.rs:22:12
Expand All @@ -50,14 +50,14 @@ LL | fn x(a: String) -> String {
| - move occurs because `a` has type `String`, which does not implement the `Copy` trait
LL | let b: String = "".to_string();
LL | dbg!(a, b);
| ---------- value moved here
| - value moved here
LL | return a;
| ^ value used here after move
|
help: consider borrowing instead of transferring ownership
help: consider cloning the value if the performance cost is acceptable
|
LL | dbg!(&a, b);
| +
LL | dbg!(a.clone(), b);
| ++++++++

error[E0382]: use of moved value: `b`
--> $DIR/dbg-issue-120327.rs:46:12
Expand Down Expand Up @@ -103,14 +103,14 @@ error[E0382]: borrow of moved value: `a`
LL | let a: String = "".to_string();
| - move occurs because `a` has type `String`, which does not implement the `Copy` trait
LL | let _res = get_expr(dbg!(a));
| ------- value moved here
| - value moved here
LL | let _l = a.len();
| ^ value borrowed here after move
|
help: consider borrowing instead of transferring ownership
help: consider cloning the value if the performance cost is acceptable
|
LL | let _res = get_expr(dbg!(&a));
| +
LL | let _res = get_expr(dbg!(a.clone()));
| ++++++++

error: aborting due to 7 previous errors

Expand Down
2 changes: 1 addition & 1 deletion tests/ui/liveness/liveness-upvars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ pub fn g<T: Default>(mut v: T) {
}

pub fn h<T: Copy + Default + std::fmt::Debug>() {
let mut z = T::default();
let mut z = T::default(); //~ WARN unused variable: `z`
let _ = move |b| {
loop {
if b {
Expand Down
10 changes: 9 additions & 1 deletion tests/ui/liveness/liveness-upvars.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ LL | z = T::default();
|
= help: maybe it is overwritten before being read?

warning: unused variable: `z`
--> $DIR/liveness-upvars.rs:101:9
|
LL | let mut z = T::default();
| ^^^^^ help: if this is intentional, prefix it with an underscore: `_z`
|
= note: `#[warn(unused_variables)]` implied by `#[warn(unused)]`

warning: value captured by `state` is never read
--> $DIR/liveness-upvars.rs:131:9
|
Expand Down Expand Up @@ -196,5 +204,5 @@ LL | s = yield ();
|
= help: maybe it is overwritten before being read?

warning: 24 warnings emitted
warning: 25 warnings emitted

16 changes: 10 additions & 6 deletions tests/ui/rfcs/rfc-2361-dbg-macro/dbg-macro-move-semantics.stderr
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
error[E0382]: use of moved value: `a`
--> $DIR/dbg-macro-move-semantics.rs:9:13
--> $DIR/dbg-macro-move-semantics.rs:9:18
|
LL | let a = NoCopy(0);
| - move occurs because `a` has type `NoCopy`, which does not implement the `Copy` trait
LL | let _ = dbg!(a);
| ------- value moved here
| - value moved here
LL | let _ = dbg!(a);
| ^^^^^^^ value used here after move
| ^ value used here after move
|
help: consider borrowing instead of transferring ownership
note: if `NoCopy` implemented `Clone`, you could clone the value
--> $DIR/dbg-macro-move-semantics.rs:4:1
|
LL | let _ = dbg!(&a);
| +
LL | struct NoCopy(usize);
| ^^^^^^^^^^^^^ consider implementing `Clone` for this type
...
LL | let _ = dbg!(a);
| - you could clone this value

error: aborting due to 1 previous error

Expand Down