Skip to content

Commit 8c89877

Browse files
committed
Auto merge of rust-lang#9105 - Serial-ATA:lint-invalid-utf8, r=Jarcho
Add `invalid_utf8_in_unchecked` changelog: Add [`invalid_utf8_in_unchecked`] closes: rust-lang#629 Don't know how useful of a lint this is, just saw this was a really old issue 😄.
2 parents be9e35f + de646e1 commit 8c89877

9 files changed

+123
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3641,6 +3641,7 @@ Released 2018-09-13
36413641
[`invalid_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_ref
36423642
[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
36433643
[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons
3644+
[`invalid_utf8_in_unchecked`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_utf8_in_unchecked
36443645
[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
36453646
[`is_digit_ascii_radix`]: https://rust-lang.github.io/rust-clippy/master/index.html#is_digit_ascii_radix
36463647
[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use clippy_utils::diagnostics::span_lint;
2+
use clippy_utils::{match_function_call, paths};
3+
use rustc_ast::{BorrowKind, LitKind};
4+
use rustc_hir::{Expr, ExprKind};
5+
use rustc_lint::{LateContext, LateLintPass};
6+
use rustc_session::{declare_lint_pass, declare_tool_lint};
7+
use rustc_span::source_map::Spanned;
8+
use rustc_span::Span;
9+
10+
declare_clippy_lint! {
11+
/// ### What it does
12+
/// Checks for `std::str::from_utf8_unchecked` with an invalid UTF-8 literal
13+
///
14+
/// ### Why is this bad?
15+
/// Creating such a `str` would result in undefined behavior
16+
///
17+
/// ### Example
18+
/// ```rust
19+
/// # #[allow(unused)]
20+
/// unsafe {
21+
/// std::str::from_utf8_unchecked(b"cl\x82ippy");
22+
/// }
23+
/// ```
24+
#[clippy::version = "1.64.0"]
25+
pub INVALID_UTF8_IN_UNCHECKED,
26+
correctness,
27+
"using a non UTF-8 literal in `std::std::from_utf8_unchecked`"
28+
}
29+
declare_lint_pass!(InvalidUtf8InUnchecked => [INVALID_UTF8_IN_UNCHECKED]);
30+
31+
impl<'tcx> LateLintPass<'tcx> for InvalidUtf8InUnchecked {
32+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
33+
if let Some([arg]) = match_function_call(cx, expr, &paths::STR_FROM_UTF8_UNCHECKED) {
34+
match &arg.kind {
35+
ExprKind::Lit(Spanned { node: lit, .. }) => {
36+
if let LitKind::ByteStr(bytes) = &lit
37+
&& std::str::from_utf8(bytes).is_err()
38+
{
39+
lint(cx, expr.span);
40+
}
41+
},
42+
ExprKind::AddrOf(BorrowKind::Ref, _, Expr { kind: ExprKind::Array(args), .. }) => {
43+
let elements = args.iter().map(|e|{
44+
match &e.kind {
45+
ExprKind::Lit(Spanned { node: lit, .. }) => match lit {
46+
LitKind::Byte(b) => Some(*b),
47+
#[allow(clippy::cast_possible_truncation)]
48+
LitKind::Int(b, _) => Some(*b as u8),
49+
_ => None
50+
}
51+
_ => None
52+
}
53+
}).collect::<Option<Vec<_>>>();
54+
55+
if let Some(elements) = elements
56+
&& std::str::from_utf8(&elements).is_err()
57+
{
58+
lint(cx, expr.span);
59+
}
60+
}
61+
_ => {}
62+
}
63+
}
64+
}
65+
}
66+
67+
fn lint(cx: &LateContext<'_>, span: Span) {
68+
span_lint(
69+
cx,
70+
INVALID_UTF8_IN_UNCHECKED,
71+
span,
72+
"non UTF-8 literal in `std::str::from_utf8_unchecked`",
73+
);
74+
}

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
9292
LintId::of(init_numbered_fields::INIT_NUMBERED_FIELDS),
9393
LintId::of(inline_fn_without_body::INLINE_FN_WITHOUT_BODY),
9494
LintId::of(int_plus_one::INT_PLUS_ONE),
95+
LintId::of(invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED),
9596
LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
9697
LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
9798
LintId::of(len_zero::COMPARISON_TO_EMPTY),

clippy_lints/src/lib.register_correctness.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
2929
LintId::of(infinite_iter::INFINITE_ITER),
3030
LintId::of(inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY),
3131
LintId::of(inline_fn_without_body::INLINE_FN_WITHOUT_BODY),
32+
LintId::of(invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED),
3233
LintId::of(let_underscore::LET_UNDERSCORE_LOCK),
3334
LintId::of(literal_representation::MISTYPED_LITERAL_SUFFIXES),
3435
LintId::of(loops::ITER_NEXT_LOOP),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ store.register_lints(&[
196196
inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
197197
int_plus_one::INT_PLUS_ONE,
198198
invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS,
199+
invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED,
199200
items_after_statements::ITEMS_AFTER_STATEMENTS,
200201
iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR,
201202
large_const_arrays::LARGE_CONST_ARRAYS,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ mod init_numbered_fields;
255255
mod inline_fn_without_body;
256256
mod int_plus_one;
257257
mod invalid_upcast_comparisons;
258+
mod invalid_utf8_in_unchecked;
258259
mod items_after_statements;
259260
mod iter_not_returning_iterator;
260261
mod large_const_arrays;
@@ -913,6 +914,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
913914
store.register_late_pass(move || Box::new(manual_retain::ManualRetain::new(msrv)));
914915
let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold;
915916
store.register_late_pass(move || Box::new(operators::Operators::new(verbose_bit_mask_threshold)));
917+
store.register_late_pass(|| Box::new(invalid_utf8_in_unchecked::InvalidUtf8InUnchecked));
916918
// add lints here, do not remove this comment, it's used in `new_lint`
917919
}
918920

clippy_utils/src/paths.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ pub const STR_BYTES: [&str; 4] = ["core", "str", "<impl str>", "bytes"];
163163
pub const STR_CHARS: [&str; 4] = ["core", "str", "<impl str>", "chars"];
164164
pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "<impl str>", "ends_with"];
165165
pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"];
166+
pub const STR_FROM_UTF8_UNCHECKED: [&str; 4] = ["core", "str", "converts", "from_utf8_unchecked"];
166167
pub const STR_LEN: [&str; 4] = ["core", "str", "<impl str>", "len"];
167168
pub const STR_STARTS_WITH: [&str; 4] = ["core", "str", "<impl str>", "starts_with"];
168169
#[cfg(feature = "internal")]

tests/ui/invalid_utf8_in_unchecked.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#![warn(clippy::invalid_utf8_in_unchecked)]
2+
3+
fn main() {
4+
// Valid
5+
unsafe {
6+
std::str::from_utf8_unchecked(&[99, 108, 105, 112, 112, 121]);
7+
std::str::from_utf8_unchecked(&[b'c', b'l', b'i', b'p', b'p', b'y']);
8+
std::str::from_utf8_unchecked(b"clippy");
9+
10+
let x = 0xA0;
11+
std::str::from_utf8_unchecked(&[0xC0, x]);
12+
}
13+
14+
// Invalid
15+
unsafe {
16+
std::str::from_utf8_unchecked(&[99, 108, 130, 105, 112, 112, 121]);
17+
std::str::from_utf8_unchecked(&[b'c', b'l', b'\x82', b'i', b'p', b'p', b'y']);
18+
std::str::from_utf8_unchecked(b"cl\x82ippy");
19+
}
20+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: non UTF-8 literal in `std::str::from_utf8_unchecked`
2+
--> $DIR/invalid_utf8_in_unchecked.rs:16:9
3+
|
4+
LL | std::str::from_utf8_unchecked(&[99, 108, 130, 105, 112, 112, 121]);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::invalid-utf8-in-unchecked` implied by `-D warnings`
8+
9+
error: non UTF-8 literal in `std::str::from_utf8_unchecked`
10+
--> $DIR/invalid_utf8_in_unchecked.rs:17:9
11+
|
12+
LL | std::str::from_utf8_unchecked(&[b'c', b'l', b'/x82', b'i', b'p', b'p', b'y']);
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
15+
error: non UTF-8 literal in `std::str::from_utf8_unchecked`
16+
--> $DIR/invalid_utf8_in_unchecked.rs:18:9
17+
|
18+
LL | std::str::from_utf8_unchecked(b"cl/x82ippy");
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
20+
21+
error: aborting due to 3 previous errors
22+

0 commit comments

Comments
 (0)