]> git.proxmox.com Git - rustc.git/blob - compiler/rustc_lint/src/panic_fmt.rs
New upstream version 1.50.0+dfsg1
[rustc.git] / compiler / rustc_lint / src / panic_fmt.rs
1 use crate::{LateContext, LateLintPass, LintContext};
2 use rustc_ast as ast;
3 use rustc_errors::{pluralize, Applicability};
4 use rustc_hir as hir;
5 use rustc_middle::ty;
6 use rustc_parse_format::{ParseMode, Parser, Piece};
7 use rustc_span::{sym, InnerSpan};
8
9 declare_lint! {
10 /// The `non_fmt_panic` lint detects `panic!("..")` with `{` or `}` in the string literal
11 /// when it is not used as a format string.
12 ///
13 /// ### Example
14 ///
15 /// ```rust,no_run
16 /// panic!("{}");
17 /// ```
18 ///
19 /// {{produces}}
20 ///
21 /// ### Explanation
22 ///
23 /// `panic!("{}")` panics with the message `"{}"`, as a `panic!()` invocation
24 /// with a single argument does not use `format_args!()`.
25 /// A future edition of Rust will interpret this string as format string,
26 /// which would break this.
27 NON_FMT_PANIC,
28 Warn,
29 "detect braces in single-argument panic!() invocations",
30 report_in_external_macro
31 }
32
33 declare_lint_pass!(PanicFmt => [NON_FMT_PANIC]);
34
35 impl<'tcx> LateLintPass<'tcx> for PanicFmt {
36 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
37 if let hir::ExprKind::Call(f, [arg]) = &expr.kind {
38 if let &ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(f).kind() {
39 if Some(def_id) == cx.tcx.lang_items().begin_panic_fn()
40 || Some(def_id) == cx.tcx.lang_items().panic_fn()
41 {
42 check_panic(cx, f, arg);
43 }
44 }
45 }
46 }
47 }
48
49 fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>) {
50 if let hir::ExprKind::Lit(lit) = &arg.kind {
51 if let ast::LitKind::Str(sym, _) = lit.node {
52 let mut expn = f.span.ctxt().outer_expn_data();
53 if let Some(id) = expn.macro_def_id {
54 if cx.tcx.is_diagnostic_item(sym::std_panic_macro, id)
55 || cx.tcx.is_diagnostic_item(sym::core_panic_macro, id)
56 {
57 let fmt = sym.as_str();
58 if !fmt.contains(&['{', '}'][..]) {
59 return;
60 }
61
62 let fmt_span = arg.span.source_callsite();
63
64 let (snippet, style) =
65 match cx.sess().parse_sess.source_map().span_to_snippet(fmt_span) {
66 Ok(snippet) => {
67 // Count the number of `#`s between the `r` and `"`.
68 let style = snippet.strip_prefix('r').and_then(|s| s.find('"'));
69 (Some(snippet), style)
70 }
71 Err(_) => (None, None),
72 };
73
74 let mut fmt_parser =
75 Parser::new(fmt.as_ref(), style, snippet.clone(), false, ParseMode::Format);
76 let n_arguments =
77 (&mut fmt_parser).filter(|a| matches!(a, Piece::NextArgument(_))).count();
78
79 // Unwrap another level of macro expansion if this panic!()
80 // was expanded from assert!() or debug_assert!().
81 for &assert in &[sym::assert_macro, sym::debug_assert_macro] {
82 let parent = expn.call_site.ctxt().outer_expn_data();
83 if parent
84 .macro_def_id
85 .map_or(false, |id| cx.tcx.is_diagnostic_item(assert, id))
86 {
87 expn = parent;
88 }
89 }
90
91 if n_arguments > 0 && fmt_parser.errors.is_empty() {
92 let arg_spans: Vec<_> = match &fmt_parser.arg_places[..] {
93 [] => vec![fmt_span],
94 v => v.iter().map(|span| fmt_span.from_inner(*span)).collect(),
95 };
96 cx.struct_span_lint(NON_FMT_PANIC, arg_spans, |lint| {
97 let mut l = lint.build(match n_arguments {
98 1 => "panic message contains an unused formatting placeholder",
99 _ => "panic message contains unused formatting placeholders",
100 });
101 l.note("this message is not used as a format string when given without arguments, but will be in a future Rust edition");
102 if expn.call_site.contains(arg.span) {
103 l.span_suggestion(
104 arg.span.shrink_to_hi(),
105 &format!("add the missing argument{}", pluralize!(n_arguments)),
106 ", ...".into(),
107 Applicability::HasPlaceholders,
108 );
109 l.span_suggestion(
110 arg.span.shrink_to_lo(),
111 "or add a \"{}\" format string to use the message literally",
112 "\"{}\", ".into(),
113 Applicability::MachineApplicable,
114 );
115 }
116 l.emit();
117 });
118 } else {
119 let brace_spans: Option<Vec<_>> = snippet
120 .filter(|s| s.starts_with('"') || s.starts_with("r#"))
121 .map(|s| {
122 s.char_indices()
123 .filter(|&(_, c)| c == '{' || c == '}')
124 .map(|(i, _)| {
125 fmt_span.from_inner(InnerSpan { start: i, end: i + 1 })
126 })
127 .collect()
128 });
129 let msg = match &brace_spans {
130 Some(v) if v.len() == 1 => "panic message contains a brace",
131 _ => "panic message contains braces",
132 };
133 cx.struct_span_lint(NON_FMT_PANIC, brace_spans.unwrap_or(vec![expn.call_site]), |lint| {
134 let mut l = lint.build(msg);
135 l.note("this message is not used as a format string, but will be in a future Rust edition");
136 if expn.call_site.contains(arg.span) {
137 l.span_suggestion(
138 arg.span.shrink_to_lo(),
139 "add a \"{}\" format string to use the message literally",
140 "\"{}\", ".into(),
141 Applicability::MachineApplicable,
142 );
143 }
144 l.emit();
145 });
146 }
147 }
148 }
149 }
150 }
151 }