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, LintContext}
;
6 use rustc_middle
::lint
::in_external_macro
;
7 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
11 declare_clippy_lint
! {
13 /// Checks for `\0` escapes in string and byte literals that look like octal
14 /// character escapes in C.
16 /// ### Why is this bad?
18 /// C and other languages support octal character escapes in strings, where
19 /// a backslash is followed by up to three octal digits. For example, `\033`
20 /// stands for the ASCII character 27 (ESC). Rust does not support this
21 /// notation, but has the escape code `\0` which stands for a null
22 /// byte/character, and any following digits do not form part of the escape
23 /// sequence. Therefore, `\033` is not a compiler error but the result may
26 /// ### Known problems
27 /// The actual meaning can be the intended one. `\x00` can be used in these
28 /// cases to be unambiguous.
30 /// The lint does not trigger for format strings in `print!()`, `write!()`
31 /// and friends since the string is already preprocessed when Clippy lints
36 /// let one = "\033[1m Bold? \033[0m"; // \033 intended as escape
37 /// let two = "\033\0"; // \033 intended as null-3-3
42 /// let one = "\x1b[1mWill this be bold?\x1b[0m";
43 /// let two = "\x0033\x00";
45 #[clippy::version = "1.59.0"]
48 "string escape sequences looking like octal characters"
51 declare_lint_pass
!(OctalEscapes
=> [OCTAL_ESCAPES
]);
53 impl EarlyLintPass
for OctalEscapes
{
54 fn check_expr(&mut self, cx
: &EarlyContext
<'_
>, expr
: &Expr
) {
55 if in_external_macro(cx
.sess(), expr
.span
) {
59 if let ExprKind
::Lit(token_lit
) = &expr
.kind
{
60 if matches
!(token_lit
.kind
, LitKind
::Str
) {
61 check_lit(cx
, token_lit
, expr
.span
, true);
62 } else if matches
!(token_lit
.kind
, LitKind
::ByteStr
) {
63 check_lit(cx
, token_lit
, expr
.span
, false);
69 fn check_lit(cx
: &EarlyContext
<'_
>, lit
: &Lit
, span
: Span
, is_string
: bool
) {
70 let contents
= lit
.symbol
.as_str();
71 let mut iter
= contents
.char_indices().peekable();
72 let mut found
= vec
![];
74 // go through the string, looking for \0[0-7][0-7]?
75 while let Some((from
, ch
)) = iter
.next() {
77 if let Some((_
, '
0'
)) = iter
.next() {
78 // collect up to two further octal digits
79 if let Some((mut to
, _
)) = iter
.next_if(|(_
, ch
)| matches
!(ch
, '
0'
..='
7'
)) {
80 if iter
.next_if(|(_
, ch
)| matches
!(ch
, '
0'
..='
7'
)).is_some() {
83 found
.push((from
, to
+ 1));
98 "octal-looking escape in {} literal",
99 if is_string { "string" }
else { "byte string" }
103 "octal escapes are not supported, `\\0` is always a null {}",
104 if is_string { "character" }
else { "byte" }
107 // Generate suggestions if the string is not too long (~ 5 lines)
108 if contents
.len() < 400 {
109 // construct two suggestion strings, one with \x escapes with octal meaning
110 // as in C, and one with \x00 for null bytes.
111 let mut suggest_1
= if is_string { "\"" }
else { "b\"" }
.to_string();
112 let mut suggest_2
= suggest_1
.clone();
114 for (from
, to
) in found
{
115 suggest_1
.push_str(&contents
[index
..from
]);
116 suggest_2
.push_str(&contents
[index
..from
]);
118 // construct a replacement escape
119 // the maximum value is \077, or \x3f, so u8 is sufficient here
120 if let Ok(n
) = u8::from_str_radix(&contents
[from
+ 1..to
], 8) {
121 write
!(suggest_1
, "\\x{n:02x}").unwrap();
124 // append the null byte as \x00 and the following digits literally
125 suggest_2
.push_str("\\x00");
126 suggest_2
.push_str(&contents
[from
+ 2..to
]);
130 suggest_1
.push_str(&contents
[index
..]);
131 suggest_2
.push_str(&contents
[index
..]);
135 // suggestion 1: equivalent hex escape
136 diag
.span_suggestion(
138 "if an octal escape was intended, use the hexadecimal representation instead",
140 Applicability
::MaybeIncorrect
,
142 // suggestion 2: unambiguous null byte
143 diag
.span_suggestion(
146 "if the null {} is intended, disambiguate using",
147 if is_string { "character" }
else { "byte" }
150 Applicability
::MaybeIncorrect
,