1#![allow(clippy::similar_names)] use std::cell::Cell;
4use std::sync::{Arc, OnceLock};
5
6use crate::visitors::{Descend, for_each_expr_without_closures};
7use crate::{get_unique_builtin_attr, sym};
8
9use arrayvec::ArrayVec;
10use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
11use rustc_data_structures::fx::FxHashMap;
12use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
13use rustc_lint::{LateContext, LintContext};
14use rustc_span::def_id::DefId;
15use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
16use rustc_span::{BytePos, ExpnData, ExpnId, ExpnKind, Span, SpanData, Symbol};
17use std::ops::ControlFlow;
18
19const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
20 sym::assert_eq_macro,
21 sym::assert_macro,
22 sym::assert_ne_macro,
23 sym::core_panic_macro,
24 sym::debug_assert_eq_macro,
25 sym::debug_assert_macro,
26 sym::debug_assert_ne_macro,
27 sym::eprint_macro,
28 sym::eprintln_macro,
29 sym::format_args_macro,
30 sym::format_macro,
31 sym::print_macro,
32 sym::println_macro,
33 sym::std_panic_macro,
34 sym::todo_macro,
35 sym::unimplemented_macro,
36 sym::write_macro,
37 sym::writeln_macro,
38];
39
40pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool {
42 if let Some(name) = cx.tcx.get_diagnostic_name(macro_def_id) {
43 FORMAT_MACRO_DIAG_ITEMS.contains(&name)
44 } else {
45 get_unique_builtin_attr(
48 cx.sess(),
49 #[allow(deprecated)]
50 cx.tcx.get_all_attrs(macro_def_id),
51 sym::format_args,
52 )
53 .is_some()
54 }
55}
56
57#[derive(Debug)]
64pub struct MacroCall {
65 pub def_id: DefId,
67 pub kind: MacroKind,
69 pub expn: ExpnId,
71 pub span: Span,
73}
74
75impl MacroCall {
76 pub fn is_local(&self) -> bool {
77 span_is_local(self.span)
78 }
79}
80
81pub fn expn_backtrace(mut span: Span) -> impl Iterator<Item = (ExpnId, ExpnData)> {
83 std::iter::from_fn(move || {
84 let ctxt = span.ctxt();
85 if ctxt == SyntaxContext::root() {
86 return None;
87 }
88 let expn = ctxt.outer_expn();
89 let data = expn.expn_data();
90 span = data.call_site;
91 Some((expn, data))
92 })
93}
94
95pub fn span_is_local(span: Span) -> bool {
97 !span.from_expansion() || expn_is_local(span.ctxt().outer_expn())
98}
99
100pub fn expn_is_local(expn: ExpnId) -> bool {
102 if expn == ExpnId::root() {
103 return true;
104 }
105 let data = expn.expn_data();
106 let backtrace = expn_backtrace(data.call_site);
107 std::iter::once((expn, data))
108 .chain(backtrace)
109 .find_map(|(_, data)| data.macro_def_id)
110 .is_none_or(DefId::is_local)
111}
112
113pub fn macro_backtrace(span: Span) -> impl Iterator<Item = MacroCall> {
116 expn_backtrace(span).filter_map(|(expn, data)| match data {
117 ExpnData {
118 kind: ExpnKind::Macro(kind, _),
119 macro_def_id: Some(def_id),
120 call_site: span,
121 ..
122 } => Some(MacroCall {
123 def_id,
124 kind,
125 expn,
126 span,
127 }),
128 _ => None,
129 })
130}
131
132pub fn root_macro_call(span: Span) -> Option<MacroCall> {
138 macro_backtrace(span).last()
139}
140
141pub fn matching_root_macro_call(cx: &LateContext<'_>, span: Span, name: Symbol) -> Option<MacroCall> {
145 root_macro_call(span).filter(|mc| cx.tcx.is_diagnostic_item(name, mc.def_id))
146}
147
148pub fn root_macro_call_first_node(cx: &LateContext<'_>, node: &impl HirNode) -> Option<MacroCall> {
151 if first_node_in_macro(cx, node) != Some(ExpnId::root()) {
152 return None;
153 }
154 root_macro_call(node.span())
155}
156
157pub fn first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> impl Iterator<Item = MacroCall> {
160 let span = node.span();
161 first_node_in_macro(cx, node)
162 .into_iter()
163 .flat_map(move |expn| macro_backtrace(span).take_while(move |macro_call| macro_call.expn != expn))
164}
165
166pub fn first_node_in_macro(cx: &LateContext<'_>, node: &impl HirNode) -> Option<ExpnId> {
183 if !node.span().from_expansion() {
186 return None;
187 }
188
189 let hir_id = node.hir_id();
195 if let Some((cached_id, cached)) = LAST_FIRST_NODE_IN_MACRO.get()
196 && cached_id == hir_id
197 {
198 return cached;
199 }
200 let result = first_node_in_macro_uncached(cx, node);
201 LAST_FIRST_NODE_IN_MACRO.set(Some((hir_id, result)));
202 result
203}
204
205thread_local! {
206 static LAST_FIRST_NODE_IN_MACRO: Cell<Option<(HirId, Option<ExpnId>)>> = const { Cell::new(None) };
207}
208
209fn first_node_in_macro_uncached(cx: &LateContext<'_>, node: &impl HirNode) -> Option<ExpnId> {
210 let expn = macro_backtrace(node.span()).next()?.expn;
213
214 let mut parent_iter = cx.tcx.hir_parent_iter(node.hir_id());
217 let (parent_id, _) = match parent_iter.next() {
218 None => return Some(ExpnId::root()),
219 Some((_, Node::Stmt(_))) => match parent_iter.next() {
220 None => return Some(ExpnId::root()),
221 Some(next) => next,
222 },
223 Some(next) => next,
224 };
225
226 let parent_span = cx.tcx.hir_span(parent_id);
228 let Some(parent_macro_call) = macro_backtrace(parent_span).next() else {
229 return Some(ExpnId::root());
231 };
232
233 if parent_macro_call.expn.is_descendant_of(expn) {
234 return None;
236 }
237
238 Some(parent_macro_call.expn)
239}
240
241pub fn is_panic(cx: &LateContext<'_>, def_id: DefId) -> bool {
245 let Some(name) = cx.tcx.get_diagnostic_name(def_id) else {
246 return false;
247 };
248 matches!(
249 name,
250 sym::core_panic_macro
251 | sym::std_panic_macro
252 | sym::core_panic_2015_macro
253 | sym::std_panic_2015_macro
254 | sym::core_panic_2021_macro
255 )
256}
257
258pub fn is_assert_macro(cx: &LateContext<'_>, def_id: DefId) -> bool {
260 let Some(name) = cx.tcx.get_diagnostic_name(def_id) else {
261 return false;
262 };
263 matches!(name, sym::assert_macro | sym::debug_assert_macro)
264}
265
266#[derive(Debug)]
270pub enum PanicCall<'a> {
271 DefaultMessage,
273 Str2015(&'a Expr<'a>),
279 Display(&'a Expr<'a>),
283 Format(&'a Expr<'a>),
287}
288
289impl<'a> PanicCall<'a> {
290 pub fn parse(expr: &'a Expr<'a>) -> Option<Self> {
291 let ExprKind::Call(callee, args) = &expr.kind else {
292 return None;
293 };
294 let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else {
295 return None;
296 };
297 let name = path.segments.last().unwrap().ident.name;
298
299 let [arg, rest @ ..] = args else {
300 return None;
301 };
302 let result = match name {
303 sym::panic | sym::begin_panic | sym::panic_str_2015 => {
304 if arg.span.eq_ctxt(expr.span) || arg.span.is_dummy() {
305 Self::DefaultMessage
306 } else {
307 Self::Str2015(arg)
308 }
309 },
310 sym::panic_display => {
311 let ExprKind::AddrOf(_, _, e) = &arg.kind else {
312 return None;
313 };
314 Self::Display(e)
315 },
316 sym::panic_fmt => Self::Format(arg),
317 sym::assert_failed => {
320 if rest.len() != 3 {
323 return None;
324 }
325 let msg_arg = &rest[2];
327 match msg_arg.kind {
328 ExprKind::Call(_, [fmt_arg]) => Self::Format(fmt_arg),
329 _ => Self::DefaultMessage,
330 }
331 },
332 _ => return None,
333 };
334 Some(result)
335 }
336
337 pub fn is_default_message(&self) -> bool {
338 matches!(self, Self::DefaultMessage)
339 }
340}
341
342pub fn find_assert_args<'a>(
344 cx: &LateContext<'_>,
345 expr: &'a Expr<'a>,
346 expn: ExpnId,
347) -> Option<(&'a Expr<'a>, PanicCall<'a>)> {
348 find_assert_args_inner(cx, expr, expn).map(|([e], p)| (e, p))
349}
350
351pub fn find_assert_eq_args<'a>(
354 cx: &LateContext<'_>,
355 expr: &'a Expr<'a>,
356 expn: ExpnId,
357) -> Option<(&'a Expr<'a>, &'a Expr<'a>, PanicCall<'a>)> {
358 find_assert_args_inner(cx, expr, expn).map(|([a, b], p)| (a, b, p))
359}
360
361fn find_assert_args_inner<'a, const N: usize>(
362 cx: &LateContext<'_>,
363 expr: &'a Expr<'a>,
364 expn: ExpnId,
365) -> Option<([&'a Expr<'a>; N], PanicCall<'a>)> {
366 let macro_id = expn.expn_data().macro_def_id?;
367 let (expr, expn) = match cx.tcx.item_name(macro_id).as_str().strip_prefix("debug_") {
368 None => (expr, expn),
369 Some(inner_name) => find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?,
370 };
371 let mut args = ArrayVec::new();
372 let panic_expn = for_each_expr_without_closures(expr, |e| {
373 if args.is_full() {
374 match PanicCall::parse(e) {
375 Some(expn) => ControlFlow::Break(expn),
376 None => ControlFlow::Continue(Descend::Yes),
377 }
378 } else if is_assert_arg(cx, e, expn) {
379 args.push(e);
380 ControlFlow::Continue(Descend::No)
381 } else {
382 ControlFlow::Continue(Descend::Yes)
383 }
384 });
385 let args = args.into_inner().ok()?;
386 Some((args, panic_expn?))
387}
388
389fn find_assert_within_debug_assert<'a>(
390 cx: &LateContext<'_>,
391 expr: &'a Expr<'a>,
392 expn: ExpnId,
393 assert_name: Symbol,
394) -> Option<(&'a Expr<'a>, ExpnId)> {
395 for_each_expr_without_closures(expr, |e| {
396 if !e.span.from_expansion() {
397 return ControlFlow::Continue(Descend::No);
398 }
399 let e_expn = e.span.ctxt().outer_expn();
400 if e_expn == expn {
401 ControlFlow::Continue(Descend::Yes)
402 } else if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
403 ControlFlow::Break((e, e_expn))
404 } else {
405 ControlFlow::Continue(Descend::No)
406 }
407 })
408}
409
410fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool {
411 if !expr.span.from_expansion() {
412 return true;
413 }
414 let result = macro_backtrace(expr.span).try_for_each(|macro_call| {
415 if macro_call.expn == assert_expn {
416 ControlFlow::Break(false)
417 } else {
418 match cx.tcx.item_name(macro_call.def_id) {
419 sym::cfg => ControlFlow::Continue(()),
421 _ => ControlFlow::Break(true),
423 }
424 }
425 });
426 match result {
427 ControlFlow::Break(is_assert_arg) => is_assert_arg,
428 ControlFlow::Continue(()) => true,
429 }
430}
431
432#[derive(Default, Clone)]
435pub struct FormatArgsStorage(Arc<OnceLock<FxHashMap<Span, FormatArgs>>>);
436
437impl FormatArgsStorage {
438 pub fn get(&self, cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId) -> Option<&FormatArgs> {
443 let format_args_expr = for_each_expr_without_closures(start, |expr| {
444 let ctxt = expr.span.ctxt();
445 if ctxt.outer_expn().is_descendant_of(expn_id) {
446 if macro_backtrace(expr.span)
447 .map(|macro_call| cx.tcx.item_name(macro_call.def_id))
448 .any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))
449 {
450 ControlFlow::Break(expr)
451 } else {
452 ControlFlow::Continue(Descend::Yes)
453 }
454 } else {
455 ControlFlow::Continue(Descend::No)
456 }
457 })?;
458
459 debug_assert!(self.0.get().is_some(), "`FormatArgsStorage` not yet populated");
460
461 self.0.get()?.get(&format_args_expr.span.with_parent(None))
462 }
463
464 pub fn set(&self, format_args: FxHashMap<Span, FormatArgs>) {
466 self.0
467 .set(format_args)
468 .expect("`FormatArgsStorage::set` should only be called once");
469 }
470}
471
472pub fn find_format_arg_expr<'hir>(start: &'hir Expr<'hir>, target: &FormatArgument) -> Option<&'hir Expr<'hir>> {
474 let SpanData {
475 lo,
476 hi,
477 ctxt,
478 parent: _,
479 } = target.expr.span.data();
480
481 for_each_expr_without_closures(start, |expr| {
482 let data = expr.span.data();
485 if data.lo == lo && data.hi == hi && data.ctxt == ctxt {
486 ControlFlow::Break(expr)
487 } else {
488 ControlFlow::Continue(())
489 }
490 })
491}
492
493pub fn format_placeholder_format_span(placeholder: &FormatPlaceholder) -> Option<Span> {
500 let base = placeholder.span?.data();
501
502 Some(Span::new(
505 placeholder.argument.span?.hi(),
506 base.hi - BytePos(1),
507 base.ctxt,
508 base.parent,
509 ))
510}
511
512pub fn format_args_inputs_span(format_args: &FormatArgs) -> Span {
519 match format_args.arguments.explicit_args() {
520 [] => format_args.span,
521 [.., last] => format_args
522 .span
523 .to(hygiene::walk_chain(last.expr.span, format_args.span.ctxt())),
524 }
525}
526
527pub fn format_arg_removal_span(format_args: &FormatArgs, index: usize) -> Option<Span> {
535 let ctxt = format_args.span.ctxt();
536
537 let current = hygiene::walk_chain(format_args.arguments.by_index(index)?.expr.span, ctxt);
538
539 let prev = if index == 0 {
540 format_args.span
541 } else {
542 hygiene::walk_chain(format_args.arguments.by_index(index - 1)?.expr.span, ctxt)
543 };
544
545 Some(current.with_lo(prev.hi()))
546}
547
548#[derive(Debug, Copy, Clone, PartialEq, Eq)]
550pub enum FormatParamUsage {
551 Argument,
553 Width,
555 Precision,
557}
558
559pub trait HirNode {
561 fn hir_id(&self) -> HirId;
562 fn span(&self) -> Span;
563}
564
565macro_rules! impl_hir_node {
566 ($($t:ident),*) => {
567 $(impl HirNode for hir::$t<'_> {
568 fn hir_id(&self) -> HirId {
569 self.hir_id
570 }
571 fn span(&self) -> Span {
572 self.span
573 }
574 })*
575 };
576}
577
578impl_hir_node!(Expr, Pat);
579
580impl HirNode for hir::Item<'_> {
581 fn hir_id(&self) -> HirId {
582 self.hir_id()
583 }
584
585 fn span(&self) -> Span {
586 self.span
587 }
588}