1 use clippy_utils::diagnostics::span_lint_and_then;
2 use rustc_ast::ast::{Expr, ExprKind};
3 use rustc_ast::token::{Lit, LitKind};
4 use rustc_errors::Applicability;
5 use rustc_lint::{EarlyContext, EarlyLintPass};
6 use rustc_middle::lint::in_external_macro;
7 use rustc_session::{declare_lint_pass, declare_tool_lint};
10 declare_clippy_lint! {
12 /// Checks for `\0` escapes in string and byte literals that look like octal
13 /// character escapes in C.
15 /// ### Why is this bad?
16 /// Rust does not support octal notation for character escapes. `\0` is always a
17 /// null byte/character, and any following digits do not form part of the escape
20 /// ### Known problems
21 /// The actual meaning can be the intended one. `\x00` can be used in these
22 /// cases to be unambigious.
27 /// let one = "\033[1m Bold? \033[0m"; // \033 intended as escape
28 /// let two = "\033\0"; // \033 intended as null-3-3
31 /// let one = "\x1b[1mWill this be bold?\x1b[0m";
32 /// let two = "\x0033\x00";
34 #[clippy::version = "1.58.0"]
37 "string escape sequences looking like octal characters"
40 declare_lint_pass!(OctalEscapes => [OCTAL_ESCAPES]);
42 impl EarlyLintPass for OctalEscapes {
43 fn check_expr(&mut self, cx: &EarlyContext<'tcx>, expr: &Expr) {
44 if in_external_macro(cx.sess, expr.span) {
48 if let ExprKind::Lit(lit) = &expr.kind {
49 if matches!(lit.token.kind, LitKind::Str) {
50 check_lit(cx, &lit.token, lit.span, true);
51 } else if matches!(lit.token.kind, LitKind::ByteStr) {
52 check_lit(cx, &lit.token, lit.span, false);
58 fn check_lit(cx: &EarlyContext<'tcx>, lit: &Lit, span: Span, is_string: bool) {
59 let contents = lit.symbol.as_str();
60 let mut iter = contents.char_indices().peekable();
62 // go through the string, looking for \0[0-7]
63 while let Some((from, ch)) = iter.next() {
65 if let Some((_, '0')) = iter.next() {
66 // collect up to two further octal digits
67 if let Some((mut to, '0'..='7')) = iter.next() {
68 if let Some((_, '0'..='7')) = iter.peek() {
71 emit(cx, &contents, from, to + 1, span, is_string);
78 fn emit(cx: &EarlyContext<'tcx>, contents: &str, from: usize, to: usize, span: Span, is_string: bool) {
79 // construct a replacement escape for that case that octal was intended
80 let escape = &contents[from + 1..to];
81 // the maximum value is \077, or \x3f
82 let literal_suggestion = u8::from_str_radix(escape, 8).ok().map(|n| format!("\\x{:02x}", n));
83 let prefix = if is_string { "" } else { "b" };
90 "octal-looking escape in {} literal",
91 if is_string { "string" } else { "byte string" }
95 "octal escapes are not supported, `\\0` is always a null {}",
96 if is_string { "character" } else { "byte" }
98 // suggestion 1: equivalent hex escape
99 if let Some(sugg) = literal_suggestion {
100 diag.span_suggestion(
102 "if an octal escape was intended, use the hexadecimal representation instead",
103 format!("{}\"{}{}{}\"", prefix, &contents[..from], sugg, &contents[to..]),
104 Applicability::MaybeIncorrect,
107 // suggestion 2: unambiguous null byte
108 diag.span_suggestion(
111 "if the null {} is intended, disambiguate using",
112 if is_string { "character" } else { "byte" }
114 format!("{}\"{}\\x00{}\"", prefix, &contents[..from], &contents[from + 2..]),
115 Applicability::MaybeIncorrect,