2021-11-20 10:23:49 +00:00
|
|
|
use clippy_utils::diagnostics::span_lint_and_then;
|
|
|
|
use rustc_ast::ast::{Expr, ExprKind};
|
|
|
|
use rustc_ast::token::{Lit, LitKind};
|
|
|
|
use rustc_errors::Applicability;
|
|
|
|
use rustc_lint::{EarlyContext, EarlyLintPass};
|
|
|
|
use rustc_middle::lint::in_external_macro;
|
|
|
|
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
|
|
|
use rustc_span::Span;
|
|
|
|
|
|
|
|
declare_clippy_lint! {
|
|
|
|
/// ### What it does
|
2021-11-21 15:06:19 +00:00
|
|
|
/// Checks for `\0` escapes in string and byte literals that look like octal
|
|
|
|
/// character escapes in C.
|
2021-11-20 10:23:49 +00:00
|
|
|
///
|
|
|
|
/// ### Why is this bad?
|
|
|
|
/// Rust does not support octal notation for character escapes. `\0` is always a
|
|
|
|
/// null byte/character, and any following digits do not form part of the escape
|
|
|
|
/// sequence.
|
|
|
|
///
|
|
|
|
/// ### Known problems
|
|
|
|
/// The actual meaning can be the intended one. `\x00` can be used in these
|
|
|
|
/// cases to be unambigious.
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
/// ```rust
|
|
|
|
/// // Bad
|
|
|
|
/// let one = "\033[1m Bold? \033[0m"; // \033 intended as escape
|
|
|
|
/// let two = "\033\0"; // \033 intended as null-3-3
|
|
|
|
///
|
|
|
|
/// // Good
|
|
|
|
/// let one = "\x1b[1mWill this be bold?\x1b[0m";
|
|
|
|
/// let two = "\x0033\x00";
|
|
|
|
/// ```
|
|
|
|
#[clippy::version = "1.58.0"]
|
|
|
|
pub OCTAL_ESCAPES,
|
|
|
|
suspicious,
|
|
|
|
"string escape sequences looking like octal characters"
|
|
|
|
}
|
|
|
|
|
|
|
|
declare_lint_pass!(OctalEscapes => [OCTAL_ESCAPES]);
|
|
|
|
|
|
|
|
impl EarlyLintPass for OctalEscapes {
|
|
|
|
fn check_expr(&mut self, cx: &EarlyContext<'tcx>, expr: &Expr) {
|
|
|
|
if in_external_macro(cx.sess, expr.span) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let ExprKind::Lit(lit) = &expr.kind {
|
|
|
|
if matches!(lit.token.kind, LitKind::Str) {
|
|
|
|
check_lit(cx, &lit.token, lit.span, true);
|
|
|
|
} else if matches!(lit.token.kind, LitKind::ByteStr) {
|
|
|
|
check_lit(cx, &lit.token, lit.span, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_lit(cx: &EarlyContext<'tcx>, lit: &Lit, span: Span, is_string: bool) {
|
|
|
|
let contents = lit.symbol.as_str();
|
2021-11-21 15:06:19 +00:00
|
|
|
let mut iter = contents.char_indices().peekable();
|
2021-11-20 10:23:49 +00:00
|
|
|
|
|
|
|
// go through the string, looking for \0[0-7]
|
|
|
|
while let Some((from, ch)) = iter.next() {
|
|
|
|
if ch == '\\' {
|
2021-11-21 15:06:19 +00:00
|
|
|
if let Some((_, '0')) = iter.next() {
|
|
|
|
// collect up to two further octal digits
|
|
|
|
if let Some((mut to, '0'..='7')) = iter.next() {
|
|
|
|
if let Some((_, '0'..='7')) = iter.peek() {
|
|
|
|
to += 1;
|
|
|
|
}
|
|
|
|
emit(cx, &contents, from, to + 1, span, is_string);
|
2021-11-20 10:23:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn emit(cx: &EarlyContext<'tcx>, contents: &str, from: usize, to: usize, span: Span, is_string: bool) {
|
|
|
|
// construct a replacement escape for that case that octal was intended
|
|
|
|
let escape = &contents[from + 1..to];
|
2021-11-21 15:06:19 +00:00
|
|
|
// the maximum value is \077, or \x3f
|
|
|
|
let literal_suggestion = u8::from_str_radix(escape, 8).ok().map(|n| format!("\\x{:02x}", n));
|
|
|
|
let prefix = if is_string { "" } else { "b" };
|
2021-11-20 10:23:49 +00:00
|
|
|
|
|
|
|
span_lint_and_then(
|
|
|
|
cx,
|
|
|
|
OCTAL_ESCAPES,
|
|
|
|
span,
|
|
|
|
&format!(
|
|
|
|
"octal-looking escape in {} literal",
|
|
|
|
if is_string { "string" } else { "byte string" }
|
|
|
|
),
|
|
|
|
|diag| {
|
|
|
|
diag.help(&format!(
|
|
|
|
"octal escapes are not supported, `\\0` is always a null {}",
|
|
|
|
if is_string { "character" } else { "byte" }
|
|
|
|
));
|
|
|
|
// suggestion 1: equivalent hex escape
|
|
|
|
if let Some(sugg) = literal_suggestion {
|
|
|
|
diag.span_suggestion(
|
|
|
|
span,
|
2021-11-21 15:06:19 +00:00
|
|
|
"if an octal escape was intended, use the hexadecimal representation instead",
|
|
|
|
format!("{}\"{}{}{}\"", prefix, &contents[..from], sugg, &contents[to..]),
|
2021-11-20 10:23:49 +00:00
|
|
|
Applicability::MaybeIncorrect,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
// suggestion 2: unambiguous null byte
|
|
|
|
diag.span_suggestion(
|
|
|
|
span,
|
|
|
|
&format!(
|
|
|
|
"if the null {} is intended, disambiguate using",
|
|
|
|
if is_string { "character" } else { "byte" }
|
|
|
|
),
|
2021-11-21 15:06:19 +00:00
|
|
|
format!("{}\"{}\\x00{}\"", prefix, &contents[..from], &contents[from + 2..]),
|
2021-11-20 10:23:49 +00:00
|
|
|
Applicability::MaybeIncorrect,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|