1 use crate::{LateContext, LateLintPass, LintContext}
;
3 use rustc_errors
::{pluralize, Applicability}
;
6 use rustc_parse_format
::{ParseMode, Parser, Piece}
;
7 use rustc_session
::lint
::FutureIncompatibilityReason
;
8 use rustc_span
::edition
::Edition
;
9 use rustc_span
::{hygiene, sym, symbol::kw, symbol::SymbolStr, InnerSpan, Span, Symbol}
;
12 /// The `non_fmt_panics` lint detects `panic!(..)` invocations where the first
13 /// argument is not a formatting string.
26 /// In Rust 2018 and earlier, `panic!(x)` directly uses `x` as the message.
27 /// That means that `panic!("{}")` panics with the message `"{}"` instead
28 /// of using it as a formatting string, and `panic!(123)` will panic with
29 /// an `i32` as message.
31 /// Rust 2021 always interprets the first argument as format string.
34 "detect single-argument panic!() invocations in which the argument is not a format string",
35 @future_incompatible
= FutureIncompatibleInfo
{
36 reason
: FutureIncompatibilityReason
::EditionSemanticsChange(Edition
::Edition2021
),
37 explain_reason
: false,
39 report_in_external_macro
42 declare_lint_pass
!(NonPanicFmt
=> [NON_FMT_PANICS
]);
44 impl<'tcx
> LateLintPass
<'tcx
> for NonPanicFmt
{
45 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx hir
::Expr
<'tcx
>) {
46 if let hir
::ExprKind
::Call(f
, [arg
]) = &expr
.kind
{
47 if let &ty
::FnDef(def_id
, _
) = cx
.typeck_results().expr_ty(f
).kind() {
48 if Some(def_id
) == cx
.tcx
.lang_items().begin_panic_fn()
49 || Some(def_id
) == cx
.tcx
.lang_items().panic_fn()
50 || Some(def_id
) == cx
.tcx
.lang_items().panic_str()
52 if let Some(id
) = f
.span
.ctxt().outer_expn_data().macro_def_id
{
53 if cx
.tcx
.is_diagnostic_item(sym
::std_panic_2015_macro
, id
)
54 || cx
.tcx
.is_diagnostic_item(sym
::core_panic_2015_macro
, id
)
56 check_panic(cx
, f
, arg
);
65 fn check_panic
<'tcx
>(cx
: &LateContext
<'tcx
>, f
: &'tcx hir
::Expr
<'tcx
>, arg
: &'tcx hir
::Expr
<'tcx
>) {
66 if let hir
::ExprKind
::Lit(lit
) = &arg
.kind
{
67 if let ast
::LitKind
::Str(sym
, _
) = lit
.node
{
68 // The argument is a string literal.
69 check_panic_str(cx
, f
, arg
, &sym
.as_str());
74 // The argument is *not* a string literal.
76 let (span
, panic
, symbol_str
) = panic_call(cx
, f
);
78 // Find the span of the argument to `panic!()`, before expansion in the
79 // case of `panic!(some_macro!())`.
80 // We don't use source_callsite(), because this `panic!(..)` might itself
81 // be expanded from another macro, in which case we want to stop at that
83 let mut arg_span
= arg
.span
;
84 let mut arg_macro
= None
;
85 while !span
.contains(arg_span
) {
86 let expn
= arg_span
.ctxt().outer_expn_data();
90 arg_macro
= expn
.macro_def_id
;
91 arg_span
= expn
.call_site
;
94 cx
.struct_span_lint(NON_FMT_PANICS
, arg_span
, |lint
| {
95 let mut l
= lint
.build("panic message is not a string literal");
96 l
.note("this usage of panic!() is deprecated; it will be a hard error in Rust 2021");
97 l
.note("for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/panic-macro-consistency.html>");
98 if !span
.contains(arg_span
) {
99 // No clue where this argument is coming from.
103 if arg_macro
.map_or(false, |id
| cx
.tcx
.is_diagnostic_item(sym
::format_macro
, id
)) {
104 // A case of `panic!(format!(..))`.
105 l
.note(format
!("the {}!() macro supports formatting, so there's no need for the format!() macro here", symbol_str
).as_str());
106 if let Some((open
, close
, _
)) = find_delimiters(cx
, arg_span
) {
107 l
.multipart_suggestion(
108 "remove the `format!(..)` macro call",
110 (arg_span
.until(open
.shrink_to_hi()), "".into()),
111 (close
.until(arg_span
.shrink_to_hi()), "".into()),
113 Applicability
::MachineApplicable
,
117 l
.span_suggestion_verbose(
118 arg_span
.shrink_to_lo(),
119 "add a \"{}\" format string to Display the message",
121 Applicability
::MaybeIncorrect
,
123 if panic
== sym
::std_panic_macro
{
124 if let Some((open
, close
, del
)) = find_delimiters(cx
, span
) {
125 l
.multipart_suggestion(
126 "or use std::panic::panic_any instead",
128 vec
![(span
.until(open
), "std::panic::panic_any".into())]
131 (span
.until(open
.shrink_to_hi()), "std::panic::panic_any(".into()),
135 Applicability
::MachineApplicable
,
144 fn check_panic_str
<'tcx
>(
145 cx
: &LateContext
<'tcx
>,
146 f
: &'tcx hir
::Expr
<'tcx
>,
147 arg
: &'tcx hir
::Expr
<'tcx
>,
150 if !fmt
.contains(&['{', '}'
][..]) {
151 // No brace, no problem.
155 let fmt_span
= arg
.span
.source_callsite();
157 let (snippet
, style
) = match cx
.sess().parse_sess
.source_map().span_to_snippet(fmt_span
) {
159 // Count the number of `#`s between the `r` and `"`.
160 let style
= snippet
.strip_prefix('r'
).and_then(|s
| s
.find('
"'));
161 (Some(snippet), style)
163 Err(_) => (None, None),
167 Parser::new(fmt.as_ref(), style, snippet.clone(), false, ParseMode::Format);
168 let n_arguments = (&mut fmt_parser).filter(|a| matches!(a, Piece::NextArgument(_))).count();
170 let (span, _, _) = panic_call(cx, f);
172 if n_arguments > 0 && fmt_parser.errors.is_empty() {
173 let arg_spans: Vec<_> = match &fmt_parser.arg_places[..] {
174 [] => vec![fmt_span],
175 v => v.iter().map(|span| fmt_span.from_inner(*span)).collect(),
177 cx.struct_span_lint(NON_FMT_PANICS, arg_spans, |lint| {
178 let mut l = lint.build(match n_arguments {
179 1 => "panic message contains an unused formatting placeholder
",
180 _ => "panic message contains unused formatting placeholders
",
182 l.note("this message is not used
as a format string when given without arguments
, but will be
in Rust
2021");
183 if span.contains(arg.span) {
185 arg.span.shrink_to_hi(),
186 &format!("add the missing argument{}
", pluralize!(n_arguments)),
188 Applicability::HasPlaceholders,
191 arg.span.shrink_to_lo(),
192 "or add a
\"{}
\" format string to
use the message literally
",
194 Applicability::MachineApplicable,
200 let brace_spans: Option<Vec<_>> =
201 snippet.filter(|s| s.starts_with('"'
) || s
.starts_with("r#")).map(|s
| {
203 .filter(|&(_
, c
)| c
== '{' || c == '}'
)
204 .map(|(i
, _
)| fmt_span
.from_inner(InnerSpan { start: i, end: i + 1 }
))
207 let msg
= match &brace_spans
{
208 Some(v
) if v
.len() == 1 => "panic message contains a brace",
209 _
=> "panic message contains braces",
211 cx
.struct_span_lint(NON_FMT_PANICS
, brace_spans
.unwrap_or_else(|| vec
![span
]), |lint
| {
212 let mut l
= lint
.build(msg
);
213 l
.note("this message is not used as a format string, but will be in Rust 2021");
214 if span
.contains(arg
.span
) {
216 arg
.span
.shrink_to_lo(),
217 "add a \"{}\" format string to use the message literally",
219 Applicability
::MachineApplicable
,
227 /// Given the span of `some_macro!(args);`, gives the span of `(` and `)`,
228 /// and the type of (opening) delimiter used.
229 fn find_delimiters
<'tcx
>(cx
: &LateContext
<'tcx
>, span
: Span
) -> Option
<(Span
, Span
, char)> {
230 let snippet
= cx
.sess().parse_sess
.source_map().span_to_snippet(span
).ok()?
;
231 let (open
, open_ch
) = snippet
.char_indices().find(|&(_
, c
)| "([{".contains(c
))?
;
232 let close
= snippet
.rfind(|c
| ")]}".contains(c
))?
;
234 span
.from_inner(InnerSpan { start: open, end: open + 1 }
),
235 span
.from_inner(InnerSpan { start: close, end: close + 1 }
),
240 fn panic_call
<'tcx
>(cx
: &LateContext
<'tcx
>, f
: &'tcx hir
::Expr
<'tcx
>) -> (Span
, Symbol
, SymbolStr
) {
241 let mut expn
= f
.span
.ctxt().outer_expn_data();
243 let mut panic_macro
= kw
::Empty
;
245 // Unwrap more levels of macro expansion, as panic_2015!()
246 // was likely expanded from panic!() and possibly from
247 // [debug_]assert!().
249 &[sym
::std_panic_macro
, sym
::core_panic_macro
, sym
::assert_macro
, sym
::debug_assert_macro
]
251 let parent
= expn
.call_site
.ctxt().outer_expn_data();
252 if parent
.macro_def_id
.map_or(false, |id
| cx
.tcx
.is_diagnostic_item(i
, id
)) {
259 if let hygiene
::ExpnKind
::Macro(_
, symbol
) = expn
.kind { symbol }
else { sym::panic }
;
260 (expn
.call_site
, panic_macro
, macro_symbol
.as_str())