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