-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This checks for sequences in strings that would be octal character escapes in C, but are not supported in Rust. It suggests either to use the `\x00` escape, or an equivalent hex escape if the octal was intended.
- Loading branch information
1 parent
38bd251
commit b524984
Showing
8 changed files
with
219 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
use clippy_utils::diagnostics::span_lint_and_then; | ||
use rustc_ast::ast::{Expr, ExprKind}; | ||
use rustc_ast::token::{Lit, LitKind}; | ||
use rustc_errors::Applicability; | ||
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; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
/// Checks for `\0` escapes in string and byte literals that look like octal 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. | ||
/// | ||
/// ### Known problems | ||
/// The actual meaning can be the intended one. `\x00` can be used in these | ||
/// cases to be unambigious. | ||
/// | ||
/// # Example | ||
/// ```rust | ||
/// // Bad | ||
/// let one = "\033[1m Bold? \033[0m"; // \033 intended as escape | ||
/// let two = "\033\0"; // \033 intended as null-3-3 | ||
/// | ||
/// // Good | ||
/// let one = "\x1b[1mWill this be bold?\x1b[0m"; | ||
/// let two = "\x0033\x00"; | ||
/// ``` | ||
#[clippy::version = "1.58.0"] | ||
pub OCTAL_ESCAPES, | ||
suspicious, | ||
"string escape sequences looking like octal characters" | ||
} | ||
|
||
declare_lint_pass!(OctalEscapes => [OCTAL_ESCAPES]); | ||
|
||
impl EarlyLintPass for OctalEscapes { | ||
fn check_expr(&mut self, cx: &EarlyContext<'tcx>, expr: &Expr) { | ||
if in_external_macro(cx.sess, expr.span) { | ||
return; | ||
} | ||
|
||
if let ExprKind::Lit(lit) = &expr.kind { | ||
if matches!(lit.token.kind, LitKind::Str) { | ||
self.check_lit(cx, &lit.token, lit.span, true); | ||
} else if matches!(lit.token.kind, LitKind::ByteStr) { | ||
self.check_lit(cx, &lit.token, lit.span, false); | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl OctalEscapes { | ||
fn check_lit(&mut self, cx: &EarlyContext<'tcx>, lit: &Lit, span: Span, is_string: bool) { | ||
let contents = lit.symbol.as_str(); | ||
let mut iter = contents.char_indices(); | ||
|
||
// go through the string, looking for \0[0-7] | ||
while let Some((start, ch)) = iter.next() { | ||
if ch == '\\' { | ||
if let Some((mut end, '0')) = iter.next() { | ||
// collect all further potentially octal digits | ||
while let Some((j, '0'..='7')) = iter.next() { | ||
end = j + 1; | ||
} | ||
// if it's more than just `\0` we have a match | ||
if end > start + 2 { | ||
self.emit(cx, &contents, start, end, span, is_string); | ||
return; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn emit(&mut self, 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]; | ||
let literal_suggestion = if is_string { | ||
u32::from_str_radix(escape, 8).ok().and_then(|n| { | ||
if n < 256 { | ||
Some(format!("\\x{:02x}", n)) | ||
} else if n <= std::char::MAX as u32 { | ||
Some(format!("\\u{{{:x}}}", n)) | ||
} else { | ||
None | ||
} | ||
}) | ||
} else { | ||
u8::from_str_radix(escape, 8).ok().map(|n| format!("\\x{:02x}", n)) | ||
}; | ||
|
||
span_lint_and_then( | ||
cx, | ||
OCTAL_ESCAPES, | ||
span, | ||
&format!( | ||
"octal-looking escape in {} literal", | ||
if is_string { "string" } else { "byte string" } | ||
), | ||
|diag| { | ||
diag.help(&format!( | ||
"Rust does not support octal escapes, \ | ||
`\\0` is always a null {}", | ||
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 is intended, use", | ||
format!("\"{}{}{}\"", &contents[..from], sugg, &contents[to..]), | ||
Applicability::MaybeIncorrect, | ||
); | ||
} | ||
// suggestion 2: unambiguous null byte | ||
diag.span_suggestion( | ||
span, | ||
&format!( | ||
"if the null {} is intended, disambiguate using", | ||
if is_string { "character" } else { "byte" } | ||
), | ||
format!("\"{}\\x00{}\"", &contents[..from], &contents[from + 2..]), | ||
Applicability::MaybeIncorrect, | ||
); | ||
}, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#![warn(clippy::octal_escapes)] | ||
|
||
fn main() { | ||
let _bad1 = "\033[0m"; | ||
let _bad2 = b"\033[0m"; | ||
let _bad3 = "\\\033[0m"; | ||
let _bad4 = "\01234567"; | ||
let _bad5 = "\0\03"; | ||
|
||
let _good1 = "\\033[0m"; | ||
let _good2 = "\0\\0"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
error: octal-looking escape in string literal | ||
--> $DIR/octal_escapes.rs:4:17 | ||
| | ||
LL | let _bad1 = "/033[0m"; | ||
| ^^^^^^^^^ | ||
| | ||
= note: `-D clippy::octal-escapes` implied by `-D warnings` | ||
= help: Rust does not support octal escapes, `/0` is always a null character | ||
help: if an octal escape is intended, use | ||
| | ||
LL | let _bad1 = "/x1b[0m"; | ||
| ~~~~~~~~~ | ||
help: if the null character is intended, disambiguate using | ||
| | ||
LL | let _bad1 = "/x0033[0m"; | ||
| ~~~~~~~~~~~ | ||
|
||
error: octal-looking escape in byte string literal | ||
--> $DIR/octal_escapes.rs:5:17 | ||
| | ||
LL | let _bad2 = b"/033[0m"; | ||
| ^^^^^^^^^^ | ||
| | ||
= help: Rust does not support octal escapes, `/0` is always a null byte | ||
help: if an octal escape is intended, use | ||
| | ||
LL | let _bad2 = "/x1b[0m"; | ||
| ~~~~~~~~~ | ||
help: if the null byte is intended, disambiguate using | ||
| | ||
LL | let _bad2 = "/x0033[0m"; | ||
| ~~~~~~~~~~~ | ||
|
||
error: octal-looking escape in string literal | ||
--> $DIR/octal_escapes.rs:6:17 | ||
| | ||
LL | let _bad3 = "//033[0m"; | ||
| ^^^^^^^^^^^ | ||
| | ||
= help: Rust does not support octal escapes, `/0` is always a null character | ||
help: if an octal escape is intended, use | ||
| | ||
LL | let _bad3 = "//x1b[0m"; | ||
| ~~~~~~~~~~~ | ||
help: if the null character is intended, disambiguate using | ||
| | ||
LL | let _bad3 = "//x0033[0m"; | ||
| ~~~~~~~~~~~~~ | ||
|
||
error: octal-looking escape in string literal | ||
--> $DIR/octal_escapes.rs:7:17 | ||
| | ||
LL | let _bad4 = "/01234567"; | ||
| ^^^^^^^^^^^ | ||
| | ||
= help: Rust does not support octal escapes, `/0` is always a null character | ||
help: if an octal escape is intended, use | ||
| | ||
LL | let _bad4 = "/u{53977}"; | ||
| ~~~~~~~~~~~ | ||
help: if the null character is intended, disambiguate using | ||
| | ||
LL | let _bad4 = "/x001234567"; | ||
| ~~~~~~~~~~~~~ | ||
|
||
error: aborting due to 4 previous errors | ||
|