about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2021-11-22 18:18:16 +0100
committerGeorg Brandl <georg@python.org>2021-11-22 21:00:19 +0100
commit0bc25d04c6aed8a40f415b9a8eaaeb136feda0e3 (patch)
tree2f21f2bb205702faf45ce3d5c22762b53ed8be35
parent850e7f533e8dc8ed37957550e88be0b0e34d7170 (diff)
downloadrust-0bc25d04c6aed8a40f415b9a8eaaeb136feda0e3.tar.gz
rust-0bc25d04c6aed8a40f415b9a8eaaeb136feda0e3.zip
octal_escapes: emit only one lint for all cases found each literal
-rw-r--r--clippy_lints/src/octal_escapes.rs69
-rw-r--r--tests/ui/octal_escapes.stderr46
2 files changed, 55 insertions, 60 deletions
diff --git a/clippy_lints/src/octal_escapes.rs b/clippy_lints/src/octal_escapes.rs
index b3a4ab121e1..0e27be25985 100644
--- a/clippy_lints/src/octal_escapes.rs
+++ b/clippy_lints/src/octal_escapes.rs
@@ -6,6 +6,7 @@ use rustc_lint::{EarlyContext, EarlyLintPass};
 use rustc_middle::lint::in_external_macro;
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 use rustc_span::Span;
+use std::fmt::Write;
 
 declare_clippy_lint! {
     /// ### What it does
@@ -13,9 +14,14 @@ declare_clippy_lint! {
     /// character escapes in C.
     ///
     /// ### Why is this bad?
-    /// Rust does not support octal notation for character escapes. `\0` is always a
-    /// null byte/character, and any following digits do not form part of the escape
-    /// sequence.
+    ///
+    /// C and other languages support octal character escapes in strings, where
+    /// a backslash is followed by up to three octal digits. For example, `\033`
+    /// stands for the ASCII character 27 (ESC). Rust does not support this
+    /// notation, but has the escape code `\0` which stands for a null
+    /// byte/character, and any following digits do not form part of the escape
+    /// sequence. Therefore, `\033` is not a compiler error but the result may
+    /// be surprising.
     ///
     /// ### Known problems
     /// The actual meaning can be the intended one. `\x00` can be used in these
@@ -58,8 +64,9 @@ impl EarlyLintPass for OctalEscapes {
 fn check_lit(cx: &EarlyContext<'tcx>, lit: &Lit, span: Span, is_string: bool) {
     let contents = lit.symbol.as_str();
     let mut iter = contents.char_indices().peekable();
+    let mut found = vec![];
 
-    // go through the string, looking for \0[0-7]
+    // go through the string, looking for \0[0-7][0-7]?
     while let Some((from, ch)) = iter.next() {
         if ch == '\\' {
             if let Some((_, '0')) = iter.next() {
@@ -68,19 +75,41 @@ fn check_lit(cx: &EarlyContext<'tcx>, lit: &Lit, span: Span, is_string: bool) {
                     if let Some((_, '0'..='7')) = iter.peek() {
                         to += 1;
                     }
-                    emit(cx, &contents, from, to + 1, span, is_string);
+                    found.push((from, to + 1));
                 }
             }
         }
     }
-}
 
-fn emit(cx: &EarlyContext<'tcx>, contents: &str, from: usize, to: usize, span: Span, is_string: bool) {
-    // construct a replacement escape for that case that octal was intended
-    let escape = &contents[from + 1..to];
-    // the maximum value is \077, or \x3f
-    let literal_suggestion = u8::from_str_radix(escape, 8).ok().map(|n| format!("\\x{:02x}", n));
-    let prefix = if is_string { "" } else { "b" };
+    if found.is_empty() {
+        return;
+    }
+
+    // construct two suggestion strings, one with \x escapes with octal meaning
+    // as in C, and one with \x00 for null bytes.
+    let mut suggest_1 = if is_string { "\"" } else { "b\"" }.to_string();
+    let mut suggest_2 = suggest_1.clone();
+    let mut index = 0;
+    for (from, to) in found {
+        suggest_1.push_str(&contents[index..from]);
+        suggest_2.push_str(&contents[index..from]);
+
+        // construct a replacement escape
+        // the maximum value is \077, or \x3f, so u8 is sufficient here
+        if let Ok(n) = u8::from_str_radix(&contents[from + 1..to], 8) {
+            write!(&mut suggest_1, "\\x{:02x}", n).unwrap();
+        }
+
+        // append the null byte as \x00 and the following digits literally
+        suggest_2.push_str("\\x00");
+        suggest_2.push_str(&contents[from + 2..to]);
+
+        index = to;
+    }
+    suggest_1.push_str(&contents[index..]);
+    suggest_1.push('"');
+    suggest_2.push_str(&contents[index..]);
+    suggest_2.push('"');
 
     span_lint_and_then(
         cx,
@@ -96,14 +125,12 @@ fn emit(cx: &EarlyContext<'tcx>, contents: &str, from: usize, to: usize, span: S
                 if is_string { "character" } else { "byte" }
             ));
             // suggestion 1: equivalent hex escape
-            if let Some(sugg) = literal_suggestion {
-                diag.span_suggestion(
-                    span,
-                    "if an octal escape was intended, use the hexadecimal representation instead",
-                    format!("{}\"{}{}{}\"", prefix, &contents[..from], sugg, &contents[to..]),
-                    Applicability::MaybeIncorrect,
-                );
-            }
+            diag.span_suggestion(
+                span,
+                "if an octal escape was intended, use the hexadecimal representation instead",
+                suggest_1,
+                Applicability::MaybeIncorrect,
+            );
             // suggestion 2: unambiguous null byte
             diag.span_suggestion(
                 span,
@@ -111,7 +138,7 @@ fn emit(cx: &EarlyContext<'tcx>, contents: &str, from: usize, to: usize, span: S
                     "if the null {} is intended, disambiguate using",
                     if is_string { "character" } else { "byte" }
                 ),
-                format!("{}\"{}\\x00{}\"", prefix, &contents[..from], &contents[from + 2..]),
+                suggest_2,
                 Applicability::MaybeIncorrect,
             );
         },
diff --git a/tests/ui/octal_escapes.stderr b/tests/ui/octal_escapes.stderr
index 8c27dc0cf05..54f5bbb0fc4 100644
--- a/tests/ui/octal_escapes.stderr
+++ b/tests/ui/octal_escapes.stderr
@@ -72,28 +72,12 @@ LL |     let _bad6 = "Text-/055/077-MoreText";
    = help: octal escapes are not supported, `/0` is always a null character
 help: if an octal escape was intended, use the hexadecimal representation instead
    |
-LL |     let _bad6 = "Text-/x2d/077-MoreText";
+LL |     let _bad6 = "Text-/x2d/x3f-MoreText";
    |                 ~~~~~~~~~~~~~~~~~~~~~~~~
 help: if the null character is intended, disambiguate using
    |
-LL |     let _bad6 = "Text-/x0055/077-MoreText";
-   |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-error: octal-looking escape in string literal
-  --> $DIR/octal_escapes.rs:10:17
-   |
-LL |     let _bad6 = "Text-/055/077-MoreText";
-   |                 ^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = help: octal escapes are not supported, `/0` is always a null character
-help: if an octal escape was intended, use the hexadecimal representation instead
-   |
-LL |     let _bad6 = "Text-/055/x3f-MoreText";
-   |                 ~~~~~~~~~~~~~~~~~~~~~~~~
-help: if the null character is intended, disambiguate using
-   |
-LL |     let _bad6 = "Text-/055/x0077-MoreText";
-   |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~
+LL |     let _bad6 = "Text-/x0055/x0077-MoreText";
+   |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 error: octal-looking escape in string literal
   --> $DIR/octal_escapes.rs:11:17
@@ -104,28 +88,12 @@ LL |     let _bad7 = "EvenMoreText-/01/02-ShortEscapes";
    = help: octal escapes are not supported, `/0` is always a null character
 help: if an octal escape was intended, use the hexadecimal representation instead
    |
-LL |     let _bad7 = "EvenMoreText-/x01/02-ShortEscapes";
-   |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-help: if the null character is intended, disambiguate using
-   |
-LL |     let _bad7 = "EvenMoreText-/x001/02-ShortEscapes";
+LL |     let _bad7 = "EvenMoreText-/x01/x02-ShortEscapes";
    |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-error: octal-looking escape in string literal
-  --> $DIR/octal_escapes.rs:11:17
-   |
-LL |     let _bad7 = "EvenMoreText-/01/02-ShortEscapes";
-   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = help: octal escapes are not supported, `/0` is always a null character
-help: if an octal escape was intended, use the hexadecimal representation instead
-   |
-LL |     let _bad7 = "EvenMoreText-/01/x02-ShortEscapes";
-   |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 help: if the null character is intended, disambiguate using
    |
-LL |     let _bad7 = "EvenMoreText-/01/x002-ShortEscapes";
-   |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+LL |     let _bad7 = "EvenMoreText-/x001/x002-ShortEscapes";
+   |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 error: octal-looking escape in string literal
   --> $DIR/octal_escapes.rs:12:17
@@ -159,5 +127,5 @@ help: if the null character is intended, disambiguate using
 LL |     let _bad9 = "锈/x0011锈";
    |                 ~~~~~~~~~~~~
 
-error: aborting due to 10 previous errors
+error: aborting due to 8 previous errors