]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/octal_escapes.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / octal_escapes.rs
CommitLineData
a2a8927a
XL
1use clippy_utils::diagnostics::span_lint_and_then;
2use rustc_ast::ast::{Expr, ExprKind};
3use rustc_ast::token::{Lit, LitKind};
4use rustc_errors::Applicability;
5099ac24 5use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
a2a8927a
XL
6use rustc_middle::lint::in_external_macro;
7use rustc_session::{declare_lint_pass, declare_tool_lint};
8use rustc_span::Span;
9use std::fmt::Write;
10
11declare_clippy_lint! {
12 /// ### What it does
13 /// Checks for `\0` escapes in string and byte literals that look like octal
14 /// character escapes in C.
15 ///
16 /// ### Why is this bad?
17 ///
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
24 /// be surprising.
25 ///
26 /// ### Known problems
27 /// The actual meaning can be the intended one. `\x00` can be used in these
04454e1e 28 /// cases to be unambiguous.
a2a8927a
XL
29 ///
30 /// The lint does not trigger for format strings in `print!()`, `write!()`
31 /// and friends since the string is already preprocessed when Clippy lints
32 /// can see it.
33 ///
34 /// # Example
35 /// ```rust
a2a8927a
XL
36 /// let one = "\033[1m Bold? \033[0m"; // \033 intended as escape
37 /// let two = "\033\0"; // \033 intended as null-3-3
923072b8 38 /// ```
a2a8927a 39 ///
923072b8
FG
40 /// Use instead:
41 /// ```rust
a2a8927a
XL
42 /// let one = "\x1b[1mWill this be bold?\x1b[0m";
43 /// let two = "\x0033\x00";
44 /// ```
923072b8 45 #[clippy::version = "1.59.0"]
a2a8927a
XL
46 pub OCTAL_ESCAPES,
47 suspicious,
48 "string escape sequences looking like octal characters"
49}
50
51declare_lint_pass!(OctalEscapes => [OCTAL_ESCAPES]);
52
53impl EarlyLintPass for OctalEscapes {
5099ac24
FG
54 fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
55 if in_external_macro(cx.sess(), expr.span) {
a2a8927a
XL
56 return;
57 }
58
59 if let ExprKind::Lit(lit) = &expr.kind {
60 if matches!(lit.token.kind, LitKind::Str) {
61 check_lit(cx, &lit.token, lit.span, true);
62 } else if matches!(lit.token.kind, LitKind::ByteStr) {
63 check_lit(cx, &lit.token, lit.span, false);
64 }
65 }
66 }
67}
68
5099ac24 69fn check_lit(cx: &EarlyContext<'_>, lit: &Lit, span: Span, is_string: bool) {
a2a8927a
XL
70 let contents = lit.symbol.as_str();
71 let mut iter = contents.char_indices().peekable();
72 let mut found = vec![];
73
74 // go through the string, looking for \0[0-7][0-7]?
75 while let Some((from, ch)) = iter.next() {
76 if ch == '\\' {
77 if let Some((_, '0')) = iter.next() {
78 // collect up to two further octal digits
79 if let Some((mut to, '0'..='7')) = iter.next() {
80 if let Some((_, '0'..='7')) = iter.peek() {
81 to += 1;
82 }
83 found.push((from, to + 1));
84 }
85 }
86 }
87 }
88
89 if found.is_empty() {
90 return;
91 }
92
93 // construct two suggestion strings, one with \x escapes with octal meaning
94 // as in C, and one with \x00 for null bytes.
95 let mut suggest_1 = if is_string { "\"" } else { "b\"" }.to_string();
96 let mut suggest_2 = suggest_1.clone();
97 let mut index = 0;
98 for (from, to) in found {
99 suggest_1.push_str(&contents[index..from]);
100 suggest_2.push_str(&contents[index..from]);
101
102 // construct a replacement escape
103 // the maximum value is \077, or \x3f, so u8 is sufficient here
104 if let Ok(n) = u8::from_str_radix(&contents[from + 1..to], 8) {
5099ac24 105 write!(suggest_1, "\\x{:02x}", n).unwrap();
a2a8927a
XL
106 }
107
108 // append the null byte as \x00 and the following digits literally
109 suggest_2.push_str("\\x00");
110 suggest_2.push_str(&contents[from + 2..to]);
111
112 index = to;
113 }
114 suggest_1.push_str(&contents[index..]);
115 suggest_1.push('"');
116 suggest_2.push_str(&contents[index..]);
117 suggest_2.push('"');
118
119 span_lint_and_then(
120 cx,
121 OCTAL_ESCAPES,
122 span,
123 &format!(
124 "octal-looking escape in {} literal",
125 if is_string { "string" } else { "byte string" }
126 ),
127 |diag| {
128 diag.help(&format!(
129 "octal escapes are not supported, `\\0` is always a null {}",
130 if is_string { "character" } else { "byte" }
131 ));
132 // suggestion 1: equivalent hex escape
133 diag.span_suggestion(
134 span,
135 "if an octal escape was intended, use the hexadecimal representation instead",
136 suggest_1,
137 Applicability::MaybeIncorrect,
138 );
139 // suggestion 2: unambiguous null byte
140 diag.span_suggestion(
141 span,
142 &format!(
143 "if the null {} is intended, disambiguate using",
144 if is_string { "character" } else { "byte" }
145 ),
146 suggest_2,
147 Applicability::MaybeIncorrect,
148 );
149 },
150 );
151}