]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_utils/src/macros.rs
Rollup merge of #96650 - tmiasko:global-asm-sym-fn, r=Amanieu
[rust.git] / src / tools / clippy / clippy_utils / src / macros.rs
1 #![allow(clippy::similar_names)] // `expr` and `expn`
2
3 use crate::visitors::expr_visitor_no_bodies;
4
5 use arrayvec::ArrayVec;
6 use if_chain::if_chain;
7 use rustc_ast::ast::LitKind;
8 use rustc_hir::intravisit::Visitor;
9 use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
10 use rustc_lint::LateContext;
11 use rustc_span::def_id::DefId;
12 use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
13 use rustc_span::{sym, ExpnData, ExpnId, ExpnKind, Span, Symbol};
14 use std::ops::ControlFlow;
15
16 const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
17     sym::assert_eq_macro,
18     sym::assert_macro,
19     sym::assert_ne_macro,
20     sym::debug_assert_eq_macro,
21     sym::debug_assert_macro,
22     sym::debug_assert_ne_macro,
23     sym::eprint_macro,
24     sym::eprintln_macro,
25     sym::format_args_macro,
26     sym::format_macro,
27     sym::print_macro,
28     sym::println_macro,
29     sym::std_panic_macro,
30     sym::write_macro,
31     sym::writeln_macro,
32 ];
33
34 /// Returns true if a given Macro `DefId` is a format macro (e.g. `println!`)
35 pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool {
36     if let Some(name) = cx.tcx.get_diagnostic_name(macro_def_id) {
37         FORMAT_MACRO_DIAG_ITEMS.contains(&name)
38     } else {
39         false
40     }
41 }
42
43 /// A macro call, like `vec![1, 2, 3]`.
44 ///
45 /// Use `tcx.item_name(macro_call.def_id)` to get the macro name.
46 /// Even better is to check if it is a diagnostic item.
47 ///
48 /// This structure is similar to `ExpnData` but it precludes desugaring expansions.
49 #[derive(Debug)]
50 pub struct MacroCall {
51     /// Macro `DefId`
52     pub def_id: DefId,
53     /// Kind of macro
54     pub kind: MacroKind,
55     /// The expansion produced by the macro call
56     pub expn: ExpnId,
57     /// Span of the macro call site
58     pub span: Span,
59 }
60
61 impl MacroCall {
62     pub fn is_local(&self) -> bool {
63         span_is_local(self.span)
64     }
65 }
66
67 /// Returns an iterator of expansions that created the given span
68 pub fn expn_backtrace(mut span: Span) -> impl Iterator<Item = (ExpnId, ExpnData)> {
69     std::iter::from_fn(move || {
70         let ctxt = span.ctxt();
71         if ctxt == SyntaxContext::root() {
72             return None;
73         }
74         let expn = ctxt.outer_expn();
75         let data = expn.expn_data();
76         span = data.call_site;
77         Some((expn, data))
78     })
79 }
80
81 /// Checks whether the span is from the root expansion or a locally defined macro
82 pub fn span_is_local(span: Span) -> bool {
83     !span.from_expansion() || expn_is_local(span.ctxt().outer_expn())
84 }
85
86 /// Checks whether the expansion is the root expansion or a locally defined macro
87 pub fn expn_is_local(expn: ExpnId) -> bool {
88     if expn == ExpnId::root() {
89         return true;
90     }
91     let data = expn.expn_data();
92     let backtrace = expn_backtrace(data.call_site);
93     std::iter::once((expn, data))
94         .chain(backtrace)
95         .find_map(|(_, data)| data.macro_def_id)
96         .map_or(true, DefId::is_local)
97 }
98
99 /// Returns an iterator of macro expansions that created the given span.
100 /// Note that desugaring expansions are skipped.
101 pub fn macro_backtrace(span: Span) -> impl Iterator<Item = MacroCall> {
102     expn_backtrace(span).filter_map(|(expn, data)| match data {
103         ExpnData {
104             kind: ExpnKind::Macro(kind, _),
105             macro_def_id: Some(def_id),
106             call_site: span,
107             ..
108         } => Some(MacroCall {
109             def_id,
110             kind,
111             expn,
112             span,
113         }),
114         _ => None,
115     })
116 }
117
118 /// If the macro backtrace of `span` has a macro call at the root expansion
119 /// (i.e. not a nested macro call), returns `Some` with the `MacroCall`
120 pub fn root_macro_call(span: Span) -> Option<MacroCall> {
121     macro_backtrace(span).last()
122 }
123
124 /// Like [`root_macro_call`], but only returns `Some` if `node` is the "first node"
125 /// produced by the macro call, as in [`first_node_in_macro`].
126 pub fn root_macro_call_first_node(cx: &LateContext<'_>, node: &impl HirNode) -> Option<MacroCall> {
127     if first_node_in_macro(cx, node) != Some(ExpnId::root()) {
128         return None;
129     }
130     root_macro_call(node.span())
131 }
132
133 /// Like [`macro_backtrace`], but only returns macro calls where `node` is the "first node" of the
134 /// macro call, as in [`first_node_in_macro`].
135 pub fn first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> impl Iterator<Item = MacroCall> {
136     let span = node.span();
137     first_node_in_macro(cx, node)
138         .into_iter()
139         .flat_map(move |expn| macro_backtrace(span).take_while(move |macro_call| macro_call.expn != expn))
140 }
141
142 /// If `node` is the "first node" in a macro expansion, returns `Some` with the `ExpnId` of the
143 /// macro call site (i.e. the parent of the macro expansion). This generally means that `node`
144 /// is the outermost node of an entire macro expansion, but there are some caveats noted below.
145 /// This is useful for finding macro calls while visiting the HIR without processing the macro call
146 /// at every node within its expansion.
147 ///
148 /// If you already have immediate access to the parent node, it is simpler to
149 /// just check the context of that span directly (e.g. `parent.span.from_expansion()`).
150 ///
151 /// If a macro call is in statement position, it expands to one or more statements.
152 /// In that case, each statement *and* their immediate descendants will all yield `Some`
153 /// with the `ExpnId` of the containing block.
154 ///
155 /// A node may be the "first node" of multiple macro calls in a macro backtrace.
156 /// The expansion of the outermost macro call site is returned in such cases.
157 pub fn first_node_in_macro(cx: &LateContext<'_>, node: &impl HirNode) -> Option<ExpnId> {
158     // get the macro expansion or return `None` if not found
159     // `macro_backtrace` importantly ignores desugaring expansions
160     let expn = macro_backtrace(node.span()).next()?.expn;
161
162     // get the parent node, possibly skipping over a statement
163     // if the parent is not found, it is sensible to return `Some(root)`
164     let hir = cx.tcx.hir();
165     let mut parent_iter = hir.parent_iter(node.hir_id());
166     let (parent_id, _) = match parent_iter.next() {
167         None => return Some(ExpnId::root()),
168         Some((_, Node::Stmt(_))) => match parent_iter.next() {
169             None => return Some(ExpnId::root()),
170             Some(next) => next,
171         },
172         Some(next) => next,
173     };
174
175     // get the macro expansion of the parent node
176     let parent_span = hir.span(parent_id);
177     let Some(parent_macro_call) = macro_backtrace(parent_span).next() else {
178         // the parent node is not in a macro
179         return Some(ExpnId::root());
180     };
181
182     if parent_macro_call.expn.is_descendant_of(expn) {
183         // `node` is input to a macro call
184         return None;
185     }
186
187     Some(parent_macro_call.expn)
188 }
189
190 /* Specific Macro Utils */
191
192 /// Is `def_id` of `std::panic`, `core::panic` or any inner implementation macros
193 pub fn is_panic(cx: &LateContext<'_>, def_id: DefId) -> bool {
194     let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return false };
195     matches!(
196         name.as_str(),
197         "core_panic_macro"
198             | "std_panic_macro"
199             | "core_panic_2015_macro"
200             | "std_panic_2015_macro"
201             | "core_panic_2021_macro"
202     )
203 }
204
205 pub enum PanicExpn<'a> {
206     /// No arguments - `panic!()`
207     Empty,
208     /// A string literal or any `&str` - `panic!("message")` or `panic!(message)`
209     Str(&'a Expr<'a>),
210     /// A single argument that implements `Display` - `panic!("{}", object)`
211     Display(&'a Expr<'a>),
212     /// Anything else - `panic!("error {}: {}", a, b)`
213     Format(FormatArgsExpn<'a>),
214 }
215
216 impl<'a> PanicExpn<'a> {
217     pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Self> {
218         if !macro_backtrace(expr.span).any(|macro_call| is_panic(cx, macro_call.def_id)) {
219             return None;
220         }
221         let ExprKind::Call(callee, [arg]) = &expr.kind else { return None };
222         let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else { return None };
223         let result = match path.segments.last().unwrap().ident.as_str() {
224             "panic" if arg.span.ctxt() == expr.span.ctxt() => Self::Empty,
225             "panic" | "panic_str" => Self::Str(arg),
226             "panic_display" => {
227                 let ExprKind::AddrOf(_, _, e) = &arg.kind else { return None };
228                 Self::Display(e)
229             },
230             "panic_fmt" => Self::Format(FormatArgsExpn::parse(cx, arg)?),
231             _ => return None,
232         };
233         Some(result)
234     }
235 }
236
237 /// Finds the arguments of an `assert!` or `debug_assert!` macro call within the macro expansion
238 pub fn find_assert_args<'a>(
239     cx: &LateContext<'_>,
240     expr: &'a Expr<'a>,
241     expn: ExpnId,
242 ) -> Option<(&'a Expr<'a>, PanicExpn<'a>)> {
243     find_assert_args_inner(cx, expr, expn).map(|([e], p)| (e, p))
244 }
245
246 /// Finds the arguments of an `assert_eq!` or `debug_assert_eq!` macro call within the macro
247 /// expansion
248 pub fn find_assert_eq_args<'a>(
249     cx: &LateContext<'_>,
250     expr: &'a Expr<'a>,
251     expn: ExpnId,
252 ) -> Option<(&'a Expr<'a>, &'a Expr<'a>, PanicExpn<'a>)> {
253     find_assert_args_inner(cx, expr, expn).map(|([a, b], p)| (a, b, p))
254 }
255
256 fn find_assert_args_inner<'a, const N: usize>(
257     cx: &LateContext<'_>,
258     expr: &'a Expr<'a>,
259     expn: ExpnId,
260 ) -> Option<([&'a Expr<'a>; N], PanicExpn<'a>)> {
261     let macro_id = expn.expn_data().macro_def_id?;
262     let (expr, expn) = match cx.tcx.item_name(macro_id).as_str().strip_prefix("debug_") {
263         None => (expr, expn),
264         Some(inner_name) => find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?,
265     };
266     let mut args = ArrayVec::new();
267     let mut panic_expn = None;
268     expr_visitor_no_bodies(|e| {
269         if args.is_full() {
270             if panic_expn.is_none() && e.span.ctxt() != expr.span.ctxt() {
271                 panic_expn = PanicExpn::parse(cx, e);
272             }
273             panic_expn.is_none()
274         } else if is_assert_arg(cx, e, expn) {
275             args.push(e);
276             false
277         } else {
278             true
279         }
280     })
281     .visit_expr(expr);
282     let args = args.into_inner().ok()?;
283     // if no `panic!(..)` is found, use `PanicExpn::Empty`
284     // to indicate that the default assertion message is used
285     let panic_expn = panic_expn.unwrap_or(PanicExpn::Empty);
286     Some((args, panic_expn))
287 }
288
289 fn find_assert_within_debug_assert<'a>(
290     cx: &LateContext<'_>,
291     expr: &'a Expr<'a>,
292     expn: ExpnId,
293     assert_name: Symbol,
294 ) -> Option<(&'a Expr<'a>, ExpnId)> {
295     let mut found = None;
296     expr_visitor_no_bodies(|e| {
297         if found.is_some() || !e.span.from_expansion() {
298             return false;
299         }
300         let e_expn = e.span.ctxt().outer_expn();
301         if e_expn == expn {
302             return true;
303         }
304         if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
305             found = Some((e, e_expn));
306         }
307         false
308     })
309     .visit_expr(expr);
310     found
311 }
312
313 fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool {
314     if !expr.span.from_expansion() {
315         return true;
316     }
317     let result = macro_backtrace(expr.span).try_for_each(|macro_call| {
318         if macro_call.expn == assert_expn {
319             ControlFlow::Break(false)
320         } else {
321             match cx.tcx.item_name(macro_call.def_id) {
322                 // `cfg!(debug_assertions)` in `debug_assert!`
323                 sym::cfg => ControlFlow::CONTINUE,
324                 // assert!(other_macro!(..))
325                 _ => ControlFlow::Break(true),
326             }
327         }
328     });
329     match result {
330         ControlFlow::Break(is_assert_arg) => is_assert_arg,
331         ControlFlow::Continue(()) => true,
332     }
333 }
334
335 /// A parsed `format_args!` expansion
336 #[derive(Debug)]
337 pub struct FormatArgsExpn<'tcx> {
338     /// Span of the first argument, the format string
339     pub format_string_span: Span,
340     /// The format string split by formatted args like `{..}`
341     pub format_string_parts: Vec<Symbol>,
342     /// Values passed after the format string
343     pub value_args: Vec<&'tcx Expr<'tcx>>,
344     /// Each element is a `value_args` index and a formatting trait (e.g. `sym::Debug`)
345     pub formatters: Vec<(usize, Symbol)>,
346     /// List of `fmt::v1::Argument { .. }` expressions. If this is empty,
347     /// then `formatters` represents the format args (`{..}`).
348     /// If this is non-empty, it represents the format args, and the `position`
349     /// parameters within the struct expressions are indexes of `formatters`.
350     pub specs: Vec<&'tcx Expr<'tcx>>,
351 }
352
353 impl<'tcx> FormatArgsExpn<'tcx> {
354     /// Parses an expanded `format_args!` or `format_args_nl!` invocation
355     pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
356         macro_backtrace(expr.span).find(|macro_call| {
357             matches!(
358                 cx.tcx.item_name(macro_call.def_id),
359                 sym::const_format_args | sym::format_args | sym::format_args_nl
360             )
361         })?;
362         let mut format_string_span: Option<Span> = None;
363         let mut format_string_parts: Vec<Symbol> = Vec::new();
364         let mut value_args: Vec<&Expr<'_>> = Vec::new();
365         let mut formatters: Vec<(usize, Symbol)> = Vec::new();
366         let mut specs: Vec<&Expr<'_>> = Vec::new();
367         expr_visitor_no_bodies(|e| {
368             // if we're still inside of the macro definition...
369             if e.span.ctxt() == expr.span.ctxt() {
370                 // ArgumentV1::new_<format_trait>(<value>)
371                 if_chain! {
372                     if let ExprKind::Call(callee, [val]) = e.kind;
373                     if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind;
374                     if let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
375                     if path.segments.last().unwrap().ident.name == sym::ArgumentV1;
376                     if seg.ident.name.as_str().starts_with("new_");
377                     then {
378                         let val_idx = if_chain! {
379                             if val.span.ctxt() == expr.span.ctxt();
380                             if let ExprKind::Field(_, field) = val.kind;
381                             if let Ok(idx) = field.name.as_str().parse();
382                             then {
383                                 // tuple index
384                                 idx
385                             } else {
386                                 // assume the value expression is passed directly
387                                 formatters.len()
388                             }
389                         };
390                         let fmt_trait = match seg.ident.name.as_str() {
391                             "new_display" => "Display",
392                             "new_debug" => "Debug",
393                             "new_lower_exp" => "LowerExp",
394                             "new_upper_exp" => "UpperExp",
395                             "new_octal" => "Octal",
396                             "new_pointer" => "Pointer",
397                             "new_binary" => "Binary",
398                             "new_lower_hex" => "LowerHex",
399                             "new_upper_hex" => "UpperHex",
400                             _ => unreachable!(),
401                         };
402                         formatters.push((val_idx, Symbol::intern(fmt_trait)));
403                     }
404                 }
405                 if let ExprKind::Struct(QPath::Resolved(_, path), ..) = e.kind {
406                     if path.segments.last().unwrap().ident.name == sym::Argument {
407                         specs.push(e);
408                     }
409                 }
410                 // walk through the macro expansion
411                 return true;
412             }
413             // assume that the first expr with a differing context represents
414             // (and has the span of) the format string
415             if format_string_span.is_none() {
416                 format_string_span = Some(e.span);
417                 let span = e.span;
418                 // walk the expr and collect string literals which are format string parts
419                 expr_visitor_no_bodies(|e| {
420                     if e.span.ctxt() != span.ctxt() {
421                         // defensive check, probably doesn't happen
422                         return false;
423                     }
424                     if let ExprKind::Lit(lit) = &e.kind {
425                         if let LitKind::Str(symbol, _s) = lit.node {
426                             format_string_parts.push(symbol);
427                         }
428                     }
429                     true
430                 })
431                 .visit_expr(e);
432             } else {
433                 // assume that any further exprs with a differing context are value args
434                 value_args.push(e);
435             }
436             // don't walk anything not from the macro expansion (e.a. inputs)
437             false
438         })
439         .visit_expr(expr);
440         Some(FormatArgsExpn {
441             format_string_span: format_string_span?,
442             format_string_parts,
443             value_args,
444             formatters,
445             specs,
446         })
447     }
448
449     /// Finds a nested call to `format_args!` within a `format!`-like macro call
450     pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
451         let mut format_args = None;
452         expr_visitor_no_bodies(|e| {
453             if format_args.is_some() {
454                 return false;
455             }
456             let e_ctxt = e.span.ctxt();
457             if e_ctxt == expr.span.ctxt() {
458                 return true;
459             }
460             if e_ctxt.outer_expn().is_descendant_of(expn_id) {
461                 format_args = FormatArgsExpn::parse(cx, e);
462             }
463             false
464         })
465         .visit_expr(expr);
466         format_args
467     }
468
469     /// Returns a vector of `FormatArgsArg`.
470     pub fn args(&self) -> Option<Vec<FormatArgsArg<'tcx>>> {
471         if self.specs.is_empty() {
472             let args = std::iter::zip(&self.value_args, &self.formatters)
473                 .map(|(value, &(_, format_trait))| FormatArgsArg {
474                     value,
475                     format_trait,
476                     spec: None,
477                 })
478                 .collect();
479             return Some(args);
480         }
481         self.specs
482             .iter()
483             .map(|spec| {
484                 if_chain! {
485                     // struct `core::fmt::rt::v1::Argument`
486                     if let ExprKind::Struct(_, fields, _) = spec.kind;
487                     if let Some(position_field) = fields.iter().find(|f| f.ident.name == sym::position);
488                     if let ExprKind::Lit(lit) = &position_field.expr.kind;
489                     if let LitKind::Int(position, _) = lit.node;
490                     if let Ok(i) = usize::try_from(position);
491                     if let Some(&(j, format_trait)) = self.formatters.get(i);
492                     then {
493                         Some(FormatArgsArg {
494                             value: self.value_args[j],
495                             format_trait,
496                             spec: Some(spec),
497                         })
498                     } else {
499                         None
500                     }
501                 }
502             })
503             .collect()
504     }
505
506     /// Source callsite span of all inputs
507     pub fn inputs_span(&self) -> Span {
508         match *self.value_args {
509             [] => self.format_string_span,
510             [.., last] => self
511                 .format_string_span
512                 .to(hygiene::walk_chain(last.span, self.format_string_span.ctxt())),
513         }
514     }
515 }
516
517 /// Type representing a `FormatArgsExpn`'s format arguments
518 pub struct FormatArgsArg<'tcx> {
519     /// An element of `value_args` according to `position`
520     pub value: &'tcx Expr<'tcx>,
521     /// An element of `args` according to `position`
522     pub format_trait: Symbol,
523     /// An element of `specs`
524     pub spec: Option<&'tcx Expr<'tcx>>,
525 }
526
527 impl<'tcx> FormatArgsArg<'tcx> {
528     /// Returns true if any formatting parameters are used that would have an effect on strings,
529     /// like `{:+2}` instead of just `{}`.
530     pub fn has_string_formatting(&self) -> bool {
531         self.spec.map_or(false, |spec| {
532             // `!` because these conditions check that `self` is unformatted.
533             !if_chain! {
534                 // struct `core::fmt::rt::v1::Argument`
535                 if let ExprKind::Struct(_, fields, _) = spec.kind;
536                 if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format);
537                 // struct `core::fmt::rt::v1::FormatSpec`
538                 if let ExprKind::Struct(_, subfields, _) = format_field.expr.kind;
539                 if subfields.iter().all(|field| match field.ident.name {
540                     sym::precision | sym::width => match field.expr.kind {
541                         ExprKind::Path(QPath::Resolved(_, path)) => {
542                             path.segments.last().unwrap().ident.name == sym::Implied
543                         }
544                         _ => false,
545                     }
546                     _ => true,
547                 });
548                 then { true } else { false }
549             }
550         })
551     }
552 }
553
554 /// A node with a `HirId` and a `Span`
555 pub trait HirNode {
556     fn hir_id(&self) -> HirId;
557     fn span(&self) -> Span;
558 }
559
560 macro_rules! impl_hir_node {
561     ($($t:ident),*) => {
562         $(impl HirNode for hir::$t<'_> {
563             fn hir_id(&self) -> HirId {
564                 self.hir_id
565             }
566             fn span(&self) -> Span {
567                 self.span
568             }
569         })*
570     };
571 }
572
573 impl_hir_node!(Expr, Pat);
574
575 impl HirNode for hir::Item<'_> {
576     fn hir_id(&self) -> HirId {
577         self.hir_id()
578     }
579
580     fn span(&self) -> Span {
581         self.span
582     }
583 }