Skip to content

Commit 0bc25d0

Browse files
committed
octal_escapes: emit only one lint for all cases found each literal
1 parent 850e7f5 commit 0bc25d0

File tree

2 files changed

+55
-60
lines changed

2 files changed

+55
-60
lines changed

clippy_lints/src/octal_escapes.rs

+48-21
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,22 @@ use rustc_lint::{EarlyContext, EarlyLintPass};
66
use rustc_middle::lint::in_external_macro;
77
use rustc_session::{declare_lint_pass, declare_tool_lint};
88
use rustc_span::Span;
9+
use std::fmt::Write;
910

1011
declare_clippy_lint! {
1112
/// ### What it does
1213
/// Checks for `\0` escapes in string and byte literals that look like octal
1314
/// character escapes in C.
1415
///
1516
/// ### 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
18-
/// sequence.
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.
1925
///
2026
/// ### Known problems
2127
/// The actual meaning can be the intended one. `\x00` can be used in these
@@ -58,8 +64,9 @@ impl EarlyLintPass for OctalEscapes {
5864
fn check_lit(cx: &EarlyContext<'tcx>, lit: &Lit, span: Span, is_string: bool) {
5965
let contents = lit.symbol.as_str();
6066
let mut iter = contents.char_indices().peekable();
67+
let mut found = vec![];
6168

62-
// go through the string, looking for \0[0-7]
69+
// go through the string, looking for \0[0-7][0-7]?
6370
while let Some((from, ch)) = iter.next() {
6471
if ch == '\\' {
6572
if let Some((_, '0')) = iter.next() {
@@ -68,19 +75,41 @@ fn check_lit(cx: &EarlyContext<'tcx>, lit: &Lit, span: Span, is_string: bool) {
6875
if let Some((_, '0'..='7')) = iter.peek() {
6976
to += 1;
7077
}
71-
emit(cx, &contents, from, to + 1, span, is_string);
78+
found.push((from, to + 1));
7279
}
7380
}
7481
}
7582
}
76-
}
7783

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" };
84+
if found.is_empty() {
85+
return;
86+
}
87+
88+
// construct two suggestion strings, one with \x escapes with octal meaning
89+
// as in C, and one with \x00 for null bytes.
90+
let mut suggest_1 = if is_string { "\"" } else { "b\"" }.to_string();
91+
let mut suggest_2 = suggest_1.clone();
92+
let mut index = 0;
93+
for (from, to) in found {
94+
suggest_1.push_str(&contents[index..from]);
95+
suggest_2.push_str(&contents[index..from]);
96+
97+
// construct a replacement escape
98+
// the maximum value is \077, or \x3f, so u8 is sufficient here
99+
if let Ok(n) = u8::from_str_radix(&contents[from + 1..to], 8) {
100+
write!(&mut suggest_1, "\\x{:02x}", n).unwrap();
101+
}
102+
103+
// append the null byte as \x00 and the following digits literally
104+
suggest_2.push_str("\\x00");
105+
suggest_2.push_str(&contents[from + 2..to]);
106+
107+
index = to;
108+
}
109+
suggest_1.push_str(&contents[index..]);
110+
suggest_1.push('"');
111+
suggest_2.push_str(&contents[index..]);
112+
suggest_2.push('"');
84113

85114
span_lint_and_then(
86115
cx,
@@ -96,22 +125,20 @@ fn emit(cx: &EarlyContext<'tcx>, contents: &str, from: usize, to: usize, span: S
96125
if is_string { "character" } else { "byte" }
97126
));
98127
// suggestion 1: equivalent hex escape
99-
if let Some(sugg) = literal_suggestion {
100-
diag.span_suggestion(
101-
span,
102-
"if an octal escape was intended, use the hexadecimal representation instead",
103-
format!("{}\"{}{}{}\"", prefix, &contents[..from], sugg, &contents[to..]),
104-
Applicability::MaybeIncorrect,
105-
);
106-
}
128+
diag.span_suggestion(
129+
span,
130+
"if an octal escape was intended, use the hexadecimal representation instead",
131+
suggest_1,
132+
Applicability::MaybeIncorrect,
133+
);
107134
// suggestion 2: unambiguous null byte
108135
diag.span_suggestion(
109136
span,
110137
&format!(
111138
"if the null {} is intended, disambiguate using",
112139
if is_string { "character" } else { "byte" }
113140
),
114-
format!("{}\"{}\\x00{}\"", prefix, &contents[..from], &contents[from + 2..]),
141+
suggest_2,
115142
Applicability::MaybeIncorrect,
116143
);
117144
},

tests/ui/octal_escapes.stderr

+7-39
Original file line numberDiff line numberDiff line change
@@ -72,28 +72,12 @@ LL | let _bad6 = "Text-/055/077-MoreText";
7272
= help: octal escapes are not supported, `/0` is always a null character
7373
help: if an octal escape was intended, use the hexadecimal representation instead
7474
|
75-
LL | let _bad6 = "Text-/x2d/077-MoreText";
75+
LL | let _bad6 = "Text-/x2d/x3f-MoreText";
7676
| ~~~~~~~~~~~~~~~~~~~~~~~~
7777
help: if the null character is intended, disambiguate using
7878
|
79-
LL | let _bad6 = "Text-/x0055/077-MoreText";
80-
| ~~~~~~~~~~~~~~~~~~~~~~~~~~
81-
82-
error: octal-looking escape in string literal
83-
--> $DIR/octal_escapes.rs:10:17
84-
|
85-
LL | let _bad6 = "Text-/055/077-MoreText";
86-
| ^^^^^^^^^^^^^^^^^^^^^^^^
87-
|
88-
= help: octal escapes are not supported, `/0` is always a null character
89-
help: if an octal escape was intended, use the hexadecimal representation instead
90-
|
91-
LL | let _bad6 = "Text-/055/x3f-MoreText";
92-
| ~~~~~~~~~~~~~~~~~~~~~~~~
93-
help: if the null character is intended, disambiguate using
94-
|
95-
LL | let _bad6 = "Text-/055/x0077-MoreText";
96-
| ~~~~~~~~~~~~~~~~~~~~~~~~~~
79+
LL | let _bad6 = "Text-/x0055/x0077-MoreText";
80+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9781

9882
error: octal-looking escape in string literal
9983
--> $DIR/octal_escapes.rs:11:17
@@ -104,28 +88,12 @@ LL | let _bad7 = "EvenMoreText-/01/02-ShortEscapes";
10488
= help: octal escapes are not supported, `/0` is always a null character
10589
help: if an octal escape was intended, use the hexadecimal representation instead
10690
|
107-
LL | let _bad7 = "EvenMoreText-/x01/02-ShortEscapes";
108-
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
109-
help: if the null character is intended, disambiguate using
110-
|
111-
LL | let _bad7 = "EvenMoreText-/x001/02-ShortEscapes";
91+
LL | let _bad7 = "EvenMoreText-/x01/x02-ShortEscapes";
11292
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
113-
114-
error: octal-looking escape in string literal
115-
--> $DIR/octal_escapes.rs:11:17
116-
|
117-
LL | let _bad7 = "EvenMoreText-/01/02-ShortEscapes";
118-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
119-
|
120-
= help: octal escapes are not supported, `/0` is always a null character
121-
help: if an octal escape was intended, use the hexadecimal representation instead
122-
|
123-
LL | let _bad7 = "EvenMoreText-/01/x02-ShortEscapes";
124-
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
12593
help: if the null character is intended, disambiguate using
12694
|
127-
LL | let _bad7 = "EvenMoreText-/01/x002-ShortEscapes";
128-
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
95+
LL | let _bad7 = "EvenMoreText-/x001/x002-ShortEscapes";
96+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
12997

13098
error: octal-looking escape in string literal
13199
--> $DIR/octal_escapes.rs:12:17
@@ -159,5 +127,5 @@ help: if the null character is intended, disambiguate using
159127
LL | let _bad9 = "锈/x0011锈";
160128
| ~~~~~~~~~~~~
161129

162-
error: aborting due to 10 previous errors
130+
error: aborting due to 8 previous errors
163131

0 commit comments

Comments
 (0)