• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #![allow(clippy::similar_names)] // `expr` and `expn`
2 
3 use crate::visitors::{for_each_expr, Descend};
4 
5 use arrayvec::ArrayVec;
6 use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
7 use rustc_data_structures::fx::FxHashMap;
8 use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
9 use rustc_lint::LateContext;
10 use rustc_span::def_id::DefId;
11 use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
12 use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, SpanData, Symbol};
13 use std::cell::RefCell;
14 use std::ops::ControlFlow;
15 use std::sync::atomic::{AtomicBool, Ordering};
16 
17 const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
18     sym::assert_eq_macro,
19     sym::assert_macro,
20     sym::assert_ne_macro,
21     sym::debug_assert_eq_macro,
22     sym::debug_assert_macro,
23     sym::debug_assert_ne_macro,
24     sym::eprint_macro,
25     sym::eprintln_macro,
26     sym::format_args_macro,
27     sym::format_macro,
28     sym::print_macro,
29     sym::println_macro,
30     sym::std_panic_macro,
31     sym::write_macro,
32     sym::writeln_macro,
33 ];
34 
35 /// Returns true if a given Macro `DefId` is a format macro (e.g. `println!`)
is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool36 pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool {
37     if let Some(name) = cx.tcx.get_diagnostic_name(macro_def_id) {
38         FORMAT_MACRO_DIAG_ITEMS.contains(&name)
39     } else {
40         false
41     }
42 }
43 
44 /// A macro call, like `vec![1, 2, 3]`.
45 ///
46 /// Use `tcx.item_name(macro_call.def_id)` to get the macro name.
47 /// Even better is to check if it is a diagnostic item.
48 ///
49 /// This structure is similar to `ExpnData` but it precludes desugaring expansions.
50 #[derive(Debug)]
51 pub struct MacroCall {
52     /// Macro `DefId`
53     pub def_id: DefId,
54     /// Kind of macro
55     pub kind: MacroKind,
56     /// The expansion produced by the macro call
57     pub expn: ExpnId,
58     /// Span of the macro call site
59     pub span: Span,
60 }
61 
62 impl MacroCall {
is_local(&self) -> bool63     pub fn is_local(&self) -> bool {
64         span_is_local(self.span)
65     }
66 }
67 
68 /// Returns an iterator of expansions that created the given span
expn_backtrace(mut span: Span) -> impl Iterator<Item = (ExpnId, ExpnData)>69 pub fn expn_backtrace(mut span: Span) -> impl Iterator<Item = (ExpnId, ExpnData)> {
70     std::iter::from_fn(move || {
71         let ctxt = span.ctxt();
72         if ctxt == SyntaxContext::root() {
73             return None;
74         }
75         let expn = ctxt.outer_expn();
76         let data = expn.expn_data();
77         span = data.call_site;
78         Some((expn, data))
79     })
80 }
81 
82 /// Checks whether the span is from the root expansion or a locally defined macro
span_is_local(span: Span) -> bool83 pub fn span_is_local(span: Span) -> bool {
84     !span.from_expansion() || expn_is_local(span.ctxt().outer_expn())
85 }
86 
87 /// Checks whether the expansion is the root expansion or a locally defined macro
expn_is_local(expn: ExpnId) -> bool88 pub fn expn_is_local(expn: ExpnId) -> bool {
89     if expn == ExpnId::root() {
90         return true;
91     }
92     let data = expn.expn_data();
93     let backtrace = expn_backtrace(data.call_site);
94     std::iter::once((expn, data))
95         .chain(backtrace)
96         .find_map(|(_, data)| data.macro_def_id)
97         .map_or(true, DefId::is_local)
98 }
99 
100 /// Returns an iterator of macro expansions that created the given span.
101 /// Note that desugaring expansions are skipped.
macro_backtrace(span: Span) -> impl Iterator<Item = MacroCall>102 pub fn macro_backtrace(span: Span) -> impl Iterator<Item = MacroCall> {
103     expn_backtrace(span).filter_map(|(expn, data)| match data {
104         ExpnData {
105             kind: ExpnKind::Macro(kind, _),
106             macro_def_id: Some(def_id),
107             call_site: span,
108             ..
109         } => Some(MacroCall {
110             def_id,
111             kind,
112             expn,
113             span,
114         }),
115         _ => None,
116     })
117 }
118 
119 /// If the macro backtrace of `span` has a macro call at the root expansion
120 /// (i.e. not a nested macro call), returns `Some` with the `MacroCall`
root_macro_call(span: Span) -> Option<MacroCall>121 pub fn root_macro_call(span: Span) -> Option<MacroCall> {
122     macro_backtrace(span).last()
123 }
124 
125 /// Like [`root_macro_call`], but only returns `Some` if `node` is the "first node"
126 /// produced by the macro call, as in [`first_node_in_macro`].
root_macro_call_first_node(cx: &LateContext<'_>, node: &impl HirNode) -> Option<MacroCall>127 pub fn root_macro_call_first_node(cx: &LateContext<'_>, node: &impl HirNode) -> Option<MacroCall> {
128     if first_node_in_macro(cx, node) != Some(ExpnId::root()) {
129         return None;
130     }
131     root_macro_call(node.span())
132 }
133 
134 /// Like [`macro_backtrace`], but only returns macro calls where `node` is the "first node" of the
135 /// macro call, as in [`first_node_in_macro`].
first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> impl Iterator<Item = MacroCall>136 pub fn first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> impl Iterator<Item = MacroCall> {
137     let span = node.span();
138     first_node_in_macro(cx, node)
139         .into_iter()
140         .flat_map(move |expn| macro_backtrace(span).take_while(move |macro_call| macro_call.expn != expn))
141 }
142 
143 /// If `node` is the "first node" in a macro expansion, returns `Some` with the `ExpnId` of the
144 /// macro call site (i.e. the parent of the macro expansion). This generally means that `node`
145 /// is the outermost node of an entire macro expansion, but there are some caveats noted below.
146 /// This is useful for finding macro calls while visiting the HIR without processing the macro call
147 /// at every node within its expansion.
148 ///
149 /// If you already have immediate access to the parent node, it is simpler to
150 /// just check the context of that span directly (e.g. `parent.span.from_expansion()`).
151 ///
152 /// If a macro call is in statement position, it expands to one or more statements.
153 /// In that case, each statement *and* their immediate descendants will all yield `Some`
154 /// with the `ExpnId` of the containing block.
155 ///
156 /// A node may be the "first node" of multiple macro calls in a macro backtrace.
157 /// The expansion of the outermost macro call site is returned in such cases.
first_node_in_macro(cx: &LateContext<'_>, node: &impl HirNode) -> Option<ExpnId>158 pub fn first_node_in_macro(cx: &LateContext<'_>, node: &impl HirNode) -> Option<ExpnId> {
159     // get the macro expansion or return `None` if not found
160     // `macro_backtrace` importantly ignores desugaring expansions
161     let expn = macro_backtrace(node.span()).next()?.expn;
162 
163     // get the parent node, possibly skipping over a statement
164     // if the parent is not found, it is sensible to return `Some(root)`
165     let hir = cx.tcx.hir();
166     let mut parent_iter = hir.parent_iter(node.hir_id());
167     let (parent_id, _) = match parent_iter.next() {
168         None => return Some(ExpnId::root()),
169         Some((_, Node::Stmt(_))) => match parent_iter.next() {
170             None => return Some(ExpnId::root()),
171             Some(next) => next,
172         },
173         Some(next) => next,
174     };
175 
176     // get the macro expansion of the parent node
177     let parent_span = hir.span(parent_id);
178     let Some(parent_macro_call) = macro_backtrace(parent_span).next() else {
179         // the parent node is not in a macro
180         return Some(ExpnId::root());
181     };
182 
183     if parent_macro_call.expn.is_descendant_of(expn) {
184         // `node` is input to a macro call
185         return None;
186     }
187 
188     Some(parent_macro_call.expn)
189 }
190 
191 /* Specific Macro Utils */
192 
193 /// Is `def_id` of `std::panic`, `core::panic` or any inner implementation macros
is_panic(cx: &LateContext<'_>, def_id: DefId) -> bool194 pub fn is_panic(cx: &LateContext<'_>, def_id: DefId) -> bool {
195     let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return false };
196     matches!(
197         name,
198         sym::core_panic_macro
199             | sym::std_panic_macro
200             | sym::core_panic_2015_macro
201             | sym::std_panic_2015_macro
202             | sym::core_panic_2021_macro
203     )
204 }
205 
206 /// Is `def_id` of `assert!` or `debug_assert!`
is_assert_macro(cx: &LateContext<'_>, def_id: DefId) -> bool207 pub fn is_assert_macro(cx: &LateContext<'_>, def_id: DefId) -> bool {
208     let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return false };
209     matches!(name, sym::assert_macro | sym::debug_assert_macro)
210 }
211 
212 #[derive(Debug)]
213 pub enum PanicExpn<'a> {
214     /// No arguments - `panic!()`
215     Empty,
216     /// A string literal or any `&str` - `panic!("message")` or `panic!(message)`
217     Str(&'a Expr<'a>),
218     /// A single argument that implements `Display` - `panic!("{}", object)`
219     Display(&'a Expr<'a>),
220     /// Anything else - `panic!("error {}: {}", a, b)`
221     Format(&'a Expr<'a>),
222 }
223 
224 impl<'a> PanicExpn<'a> {
parse(expr: &'a Expr<'a>) -> Option<Self>225     pub fn parse(expr: &'a Expr<'a>) -> Option<Self> {
226         let ExprKind::Call(callee, [arg, rest @ ..]) = &expr.kind else { return None };
227         let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else { return None };
228         let result = match path.segments.last().unwrap().ident.as_str() {
229             "panic" if arg.span.ctxt() == expr.span.ctxt() => Self::Empty,
230             "panic" | "panic_str" => Self::Str(arg),
231             "panic_display" => {
232                 let ExprKind::AddrOf(_, _, e) = &arg.kind else { return None };
233                 Self::Display(e)
234             },
235             "panic_fmt" => Self::Format(arg),
236             // Since Rust 1.52, `assert_{eq,ne}` macros expand to use:
237             // `core::panicking::assert_failed(.., left_val, right_val, None | Some(format_args!(..)));`
238             "assert_failed" => {
239                 // It should have 4 arguments in total (we already matched with the first argument,
240                 // so we're just checking for 3)
241                 if rest.len() != 3 {
242                     return None;
243                 }
244                 // `msg_arg` is either `None` (no custom message) or `Some(format_args!(..))` (custom message)
245                 let msg_arg = &rest[2];
246                 match msg_arg.kind {
247                     ExprKind::Call(_, [fmt_arg]) => Self::Format(fmt_arg),
248                     _ => Self::Empty,
249                 }
250             },
251             _ => return None,
252         };
253         Some(result)
254     }
255 }
256 
257 /// Finds the arguments of an `assert!` or `debug_assert!` macro call within the macro expansion
find_assert_args<'a>( cx: &LateContext<'_>, expr: &'a Expr<'a>, expn: ExpnId, ) -> Option<(&'a Expr<'a>, PanicExpn<'a>)>258 pub fn find_assert_args<'a>(
259     cx: &LateContext<'_>,
260     expr: &'a Expr<'a>,
261     expn: ExpnId,
262 ) -> Option<(&'a Expr<'a>, PanicExpn<'a>)> {
263     find_assert_args_inner(cx, expr, expn).map(|([e], mut p)| {
264         // `assert!(..)` expands to `core::panicking::panic("assertion failed: ...")` (which we map to
265         // `PanicExpn::Str(..)`) and `assert!(.., "..")` expands to
266         // `core::panicking::panic_fmt(format_args!(".."))` (which we map to `PanicExpn::Format(..)`).
267         // So even we got `PanicExpn::Str(..)` that means there is no custom message provided
268         if let PanicExpn::Str(_) = p {
269             p = PanicExpn::Empty;
270         }
271 
272         (e, p)
273     })
274 }
275 
276 /// Finds the arguments of an `assert_eq!` or `debug_assert_eq!` macro call within the macro
277 /// expansion
find_assert_eq_args<'a>( cx: &LateContext<'_>, expr: &'a Expr<'a>, expn: ExpnId, ) -> Option<(&'a Expr<'a>, &'a Expr<'a>, PanicExpn<'a>)>278 pub fn find_assert_eq_args<'a>(
279     cx: &LateContext<'_>,
280     expr: &'a Expr<'a>,
281     expn: ExpnId,
282 ) -> Option<(&'a Expr<'a>, &'a Expr<'a>, PanicExpn<'a>)> {
283     find_assert_args_inner(cx, expr, expn).map(|([a, b], p)| (a, b, p))
284 }
285 
find_assert_args_inner<'a, const N: usize>( cx: &LateContext<'_>, expr: &'a Expr<'a>, expn: ExpnId, ) -> Option<([&'a Expr<'a>; N], PanicExpn<'a>)>286 fn find_assert_args_inner<'a, const N: usize>(
287     cx: &LateContext<'_>,
288     expr: &'a Expr<'a>,
289     expn: ExpnId,
290 ) -> Option<([&'a Expr<'a>; N], PanicExpn<'a>)> {
291     let macro_id = expn.expn_data().macro_def_id?;
292     let (expr, expn) = match cx.tcx.item_name(macro_id).as_str().strip_prefix("debug_") {
293         None => (expr, expn),
294         Some(inner_name) => find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?,
295     };
296     let mut args = ArrayVec::new();
297     let panic_expn = for_each_expr(expr, |e| {
298         if args.is_full() {
299             match PanicExpn::parse(e) {
300                 Some(expn) => ControlFlow::Break(expn),
301                 None => ControlFlow::Continue(Descend::Yes),
302             }
303         } else if is_assert_arg(cx, e, expn) {
304             args.push(e);
305             ControlFlow::Continue(Descend::No)
306         } else {
307             ControlFlow::Continue(Descend::Yes)
308         }
309     });
310     let args = args.into_inner().ok()?;
311     // if no `panic!(..)` is found, use `PanicExpn::Empty`
312     // to indicate that the default assertion message is used
313     let panic_expn = panic_expn.unwrap_or(PanicExpn::Empty);
314     Some((args, panic_expn))
315 }
316 
find_assert_within_debug_assert<'a>( cx: &LateContext<'_>, expr: &'a Expr<'a>, expn: ExpnId, assert_name: Symbol, ) -> Option<(&'a Expr<'a>, ExpnId)>317 fn find_assert_within_debug_assert<'a>(
318     cx: &LateContext<'_>,
319     expr: &'a Expr<'a>,
320     expn: ExpnId,
321     assert_name: Symbol,
322 ) -> Option<(&'a Expr<'a>, ExpnId)> {
323     for_each_expr(expr, |e| {
324         if !e.span.from_expansion() {
325             return ControlFlow::Continue(Descend::No);
326         }
327         let e_expn = e.span.ctxt().outer_expn();
328         if e_expn == expn {
329             ControlFlow::Continue(Descend::Yes)
330         } else if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
331             ControlFlow::Break((e, e_expn))
332         } else {
333             ControlFlow::Continue(Descend::No)
334         }
335     })
336 }
337 
is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool338 fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool {
339     if !expr.span.from_expansion() {
340         return true;
341     }
342     let result = macro_backtrace(expr.span).try_for_each(|macro_call| {
343         if macro_call.expn == assert_expn {
344             ControlFlow::Break(false)
345         } else {
346             match cx.tcx.item_name(macro_call.def_id) {
347                 // `cfg!(debug_assertions)` in `debug_assert!`
348                 sym::cfg => ControlFlow::Continue(()),
349                 // assert!(other_macro!(..))
350                 _ => ControlFlow::Break(true),
351             }
352         }
353     });
354     match result {
355         ControlFlow::Break(is_assert_arg) => is_assert_arg,
356         ControlFlow::Continue(()) => true,
357     }
358 }
359 
360 thread_local! {
361     /// We preserve the [`FormatArgs`] structs from the early pass for use in the late pass to be
362     /// able to access the many features of a [`LateContext`].
363     ///
364     /// A thread local is used because [`FormatArgs`] is `!Send` and `!Sync`, we are making an
365     /// assumption that the early pass that populates the map and the later late passes will all be
366     /// running on the same thread.
367     static AST_FORMAT_ARGS: RefCell<FxHashMap<Span, FormatArgs>> = {
368         static CALLED: AtomicBool = AtomicBool::new(false);
369         debug_assert!(
370             !CALLED.swap(true, Ordering::SeqCst),
371             "incorrect assumption: `AST_FORMAT_ARGS` should only be accessed by a single thread",
372         );
373 
374         RefCell::default()
375     };
376 }
377 
378 /// Record [`rustc_ast::FormatArgs`] for use in late lint passes, this should only be called by
379 /// `FormatArgsCollector`
collect_ast_format_args(span: Span, format_args: &FormatArgs)380 pub fn collect_ast_format_args(span: Span, format_args: &FormatArgs) {
381     AST_FORMAT_ARGS.with(|ast_format_args| {
382         ast_format_args.borrow_mut().insert(span, format_args.clone());
383     });
384 }
385 
386 /// Calls `callback` with an AST [`FormatArgs`] node if a `format_args` expansion is found as a
387 /// descendant of `expn_id`
find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId, callback: impl FnOnce(&FormatArgs))388 pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId, callback: impl FnOnce(&FormatArgs)) {
389     let format_args_expr = for_each_expr(start, |expr| {
390         let ctxt = expr.span.ctxt();
391         if ctxt.outer_expn().is_descendant_of(expn_id) {
392             if macro_backtrace(expr.span)
393                 .map(|macro_call| cx.tcx.item_name(macro_call.def_id))
394                 .any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))
395             {
396                 ControlFlow::Break(expr)
397             } else {
398                 ControlFlow::Continue(Descend::Yes)
399             }
400         } else {
401             ControlFlow::Continue(Descend::No)
402         }
403     });
404 
405     if let Some(expr) = format_args_expr {
406         AST_FORMAT_ARGS.with(|ast_format_args| {
407             ast_format_args.borrow().get(&expr.span).map(callback);
408         });
409     }
410 }
411 
412 /// Attempt to find the [`rustc_hir::Expr`] that corresponds to the [`FormatArgument`]'s value, if
413 /// it cannot be found it will return the [`rustc_ast::Expr`].
find_format_arg_expr<'hir, 'ast>( start: &'hir Expr<'hir>, target: &'ast FormatArgument, ) -> Result<&'hir rustc_hir::Expr<'hir>, &'ast rustc_ast::Expr>414 pub fn find_format_arg_expr<'hir, 'ast>(
415     start: &'hir Expr<'hir>,
416     target: &'ast FormatArgument,
417 ) -> Result<&'hir rustc_hir::Expr<'hir>, &'ast rustc_ast::Expr> {
418     let SpanData {
419         lo,
420         hi,
421         ctxt,
422         parent: _,
423     } = target.expr.span.data();
424 
425     for_each_expr(start, |expr| {
426         // When incremental compilation is enabled spans gain a parent during AST to HIR lowering,
427         // since we're comparing an AST span to a HIR one we need to ignore the parent field
428         let data = expr.span.data();
429         if data.lo == lo && data.hi == hi && data.ctxt == ctxt {
430             ControlFlow::Break(expr)
431         } else {
432             ControlFlow::Continue(())
433         }
434     })
435     .ok_or(&target.expr)
436 }
437 
438 /// Span of the `:` and format specifiers
439 ///
440 /// ```ignore
441 /// format!("{:.}"), format!("{foo:.}")
442 ///           ^^                  ^^
443 /// ```
format_placeholder_format_span(placeholder: &FormatPlaceholder) -> Option<Span>444 pub fn format_placeholder_format_span(placeholder: &FormatPlaceholder) -> Option<Span> {
445     let base = placeholder.span?.data();
446 
447     // `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing
448     // brace `{...|}`
449     Some(Span::new(
450         placeholder.argument.span?.hi(),
451         base.hi - BytePos(1),
452         base.ctxt,
453         base.parent,
454     ))
455 }
456 
457 /// Span covering the format string and values
458 ///
459 /// ```ignore
460 /// format("{}.{}", 10, 11)
461 /// //     ^^^^^^^^^^^^^^^
462 /// ```
format_args_inputs_span(format_args: &FormatArgs) -> Span463 pub fn format_args_inputs_span(format_args: &FormatArgs) -> Span {
464     match format_args.arguments.explicit_args() {
465         [] => format_args.span,
466         [.., last] => format_args
467             .span
468             .to(hygiene::walk_chain(last.expr.span, format_args.span.ctxt())),
469     }
470 }
471 
472 /// Returns the [`Span`] of the value at `index` extended to the previous comma, e.g. for the value
473 /// `10`
474 ///
475 /// ```ignore
476 /// format("{}.{}", 10, 11)
477 /// //            ^^^^
478 /// ```
format_arg_removal_span(format_args: &FormatArgs, index: usize) -> Option<Span>479 pub fn format_arg_removal_span(format_args: &FormatArgs, index: usize) -> Option<Span> {
480     let ctxt = format_args.span.ctxt();
481 
482     let current = hygiene::walk_chain(format_args.arguments.by_index(index)?.expr.span, ctxt);
483 
484     let prev = if index == 0 {
485         format_args.span
486     } else {
487         hygiene::walk_chain(format_args.arguments.by_index(index - 1)?.expr.span, ctxt)
488     };
489 
490     Some(current.with_lo(prev.hi()))
491 }
492 
493 /// Where a format parameter is being used in the format string
494 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
495 pub enum FormatParamUsage {
496     /// Appears as an argument, e.g. `format!("{}", foo)`
497     Argument,
498     /// Appears as a width, e.g. `format!("{:width$}", foo, width = 1)`
499     Width,
500     /// Appears as a precision, e.g. `format!("{:.precision$}", foo, precision = 1)`
501     Precision,
502 }
503 
504 /// A node with a `HirId` and a `Span`
505 pub trait HirNode {
hir_id(&self) -> HirId506     fn hir_id(&self) -> HirId;
span(&self) -> Span507     fn span(&self) -> Span;
508 }
509 
510 macro_rules! impl_hir_node {
511     ($($t:ident),*) => {
512         $(impl HirNode for hir::$t<'_> {
513             fn hir_id(&self) -> HirId {
514                 self.hir_id
515             }
516             fn span(&self) -> Span {
517                 self.span
518             }
519         })*
520     };
521 }
522 
523 impl_hir_node!(Expr, Pat);
524 
525 impl HirNode for hir::Item<'_> {
hir_id(&self) -> HirId526     fn hir_id(&self) -> HirId {
527         self.hir_id()
528     }
529 
span(&self) -> Span530     fn span(&self) -> Span {
531         self.span
532     }
533 }
534