@@ -6,16 +6,22 @@ use rustc_lint::{EarlyContext, EarlyLintPass};
6
6
use rustc_middle:: lint:: in_external_macro;
7
7
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
8
8
use rustc_span:: Span ;
9
+ use std:: fmt:: Write ;
9
10
10
11
declare_clippy_lint ! {
11
12
/// ### What it does
12
13
/// Checks for `\0` escapes in string and byte literals that look like octal
13
14
/// character escapes in C.
14
15
///
15
16
/// ### 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.
19
25
///
20
26
/// ### Known problems
21
27
/// The actual meaning can be the intended one. `\x00` can be used in these
@@ -58,8 +64,9 @@ impl EarlyLintPass for OctalEscapes {
58
64
fn check_lit ( cx : & EarlyContext < ' tcx > , lit : & Lit , span : Span , is_string : bool ) {
59
65
let contents = lit. symbol . as_str ( ) ;
60
66
let mut iter = contents. char_indices ( ) . peekable ( ) ;
67
+ let mut found = vec ! [ ] ;
61
68
62
- // go through the string, looking for \0[0-7]
69
+ // go through the string, looking for \0[0-7][0-7]?
63
70
while let Some ( ( from, ch) ) = iter. next ( ) {
64
71
if ch == '\\' {
65
72
if let Some ( ( _, '0' ) ) = iter. next ( ) {
@@ -68,19 +75,41 @@ fn check_lit(cx: &EarlyContext<'tcx>, lit: &Lit, span: Span, is_string: bool) {
68
75
if let Some ( ( _, '0' ..='7' ) ) = iter. peek ( ) {
69
76
to += 1 ;
70
77
}
71
- emit ( cx , & contents , from, to + 1 , span , is_string ) ;
78
+ found . push ( ( from, to + 1 ) ) ;
72
79
}
73
80
}
74
81
}
75
82
}
76
- }
77
83
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 ( '"' ) ;
84
113
85
114
span_lint_and_then (
86
115
cx,
@@ -96,22 +125,20 @@ fn emit(cx: &EarlyContext<'tcx>, contents: &str, from: usize, to: usize, span: S
96
125
if is_string { "character" } else { "byte" }
97
126
) ) ;
98
127
// 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
+ ) ;
107
134
// suggestion 2: unambiguous null byte
108
135
diag. span_suggestion (
109
136
span,
110
137
& format ! (
111
138
"if the null {} is intended, disambiguate using" ,
112
139
if is_string { "character" } else { "byte" }
113
140
) ,
114
- format ! ( "{} \" {} \\ x00{} \" " , prefix , & contents [ ..from ] , & contents [ from + 2 .. ] ) ,
141
+ suggest_2 ,
115
142
Applicability :: MaybeIncorrect ,
116
143
) ;
117
144
} ,
0 commit comments