@@ -6,16 +6,22 @@ use rustc_lint::{EarlyContext, EarlyLintPass};
66use rustc_middle:: lint:: in_external_macro;
77use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
88use rustc_span:: Span ;
9+ use std:: fmt:: Write ;
910
1011declare_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 {
5864fn 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 } ,
0 commit comments