Skip to main content

rustc_lint/
invalid_from_utf8.rs

1use std::str::Utf8Error;
2
3use rustc_ast::LitKind;
4use rustc_hir::{Expr, ExprKind};
5use rustc_session::{declare_lint, declare_lint_pass};
6use rustc_span::{Spanned, sym};
7
8use crate::lints::InvalidFromUtf8Diag;
9use crate::{LateContext, LateLintPass, LintContext};
10
11#[doc = r" The `invalid_from_utf8_unchecked` lint checks for calls to"]
#[doc =
r" `std::str::from_utf8_unchecked` and `std::str::from_utf8_unchecked_mut`"]
#[doc = r" with a known invalid UTF-8 value."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust,compile_fail"]
#[doc = r" # #[allow(unused)]"]
#[doc = r" unsafe {"]
#[doc = r#"     std::str::from_utf8_unchecked(b"Ru\x82st");"#]
#[doc = r" }"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" Creating such a `str` would result in undefined behavior as per documentation"]
#[doc =
r" for `std::str::from_utf8_unchecked` and `std::str::from_utf8_unchecked_mut`."]
pub static INVALID_FROM_UTF8_UNCHECKED: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "INVALID_FROM_UTF8_UNCHECKED",
            default_level: ::rustc_lint_defs::Deny,
            desc: "using a non UTF-8 literal in `std::str::from_utf8_unchecked`",
            is_externally_loaded: false,
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_lint! {
12    /// The `invalid_from_utf8_unchecked` lint checks for calls to
13    /// `std::str::from_utf8_unchecked` and `std::str::from_utf8_unchecked_mut`
14    /// with a known invalid UTF-8 value.
15    ///
16    /// ### Example
17    ///
18    /// ```rust,compile_fail
19    /// # #[allow(unused)]
20    /// unsafe {
21    ///     std::str::from_utf8_unchecked(b"Ru\x82st");
22    /// }
23    /// ```
24    ///
25    /// {{produces}}
26    ///
27    /// ### Explanation
28    ///
29    /// Creating such a `str` would result in undefined behavior as per documentation
30    /// for `std::str::from_utf8_unchecked` and `std::str::from_utf8_unchecked_mut`.
31    pub INVALID_FROM_UTF8_UNCHECKED,
32    Deny,
33    "using a non UTF-8 literal in `std::str::from_utf8_unchecked`"
34}
35
36#[doc = r" The `invalid_from_utf8` lint checks for calls to"]
#[doc = r" `std::str::from_utf8` and `std::str::from_utf8_mut`"]
#[doc = r" with a known invalid UTF-8 value."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust"]
#[doc = r" # #[allow(unused)]"]
#[doc = r#" std::str::from_utf8(b"Ru\x82st");"#]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" Trying to create such a `str` would always return an error as per documentation"]
#[doc = r" for `std::str::from_utf8` and `std::str::from_utf8_mut`."]
pub static INVALID_FROM_UTF8: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "INVALID_FROM_UTF8",
            default_level: ::rustc_lint_defs::Warn,
            desc: "using a non UTF-8 literal in `std::str::from_utf8`",
            is_externally_loaded: false,
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_lint! {
37    /// The `invalid_from_utf8` lint checks for calls to
38    /// `std::str::from_utf8` and `std::str::from_utf8_mut`
39    /// with a known invalid UTF-8 value.
40    ///
41    /// ### Example
42    ///
43    /// ```rust
44    /// # #[allow(unused)]
45    /// std::str::from_utf8(b"Ru\x82st");
46    /// ```
47    ///
48    /// {{produces}}
49    ///
50    /// ### Explanation
51    ///
52    /// Trying to create such a `str` would always return an error as per documentation
53    /// for `std::str::from_utf8` and `std::str::from_utf8_mut`.
54    pub INVALID_FROM_UTF8,
55    Warn,
56    "using a non UTF-8 literal in `std::str::from_utf8`"
57}
58
59pub struct InvalidFromUtf8;
#[automatically_derived]
impl ::core::marker::Copy for InvalidFromUtf8 { }
#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for InvalidFromUtf8 { }
#[automatically_derived]
impl ::core::clone::Clone for InvalidFromUtf8 {
    #[inline]
    fn clone(&self) -> InvalidFromUtf8 { *self }
}
impl ::rustc_lint_defs::LintPass for InvalidFromUtf8 {
    fn name(&self) -> &'static str { "InvalidFromUtf8" }
    fn get_lints(&self) -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [INVALID_FROM_UTF8_UNCHECKED, INVALID_FROM_UTF8]))
    }
}
impl InvalidFromUtf8 {
    #[allow(unused)]
    pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [INVALID_FROM_UTF8_UNCHECKED, INVALID_FROM_UTF8]))
    }
}declare_lint_pass!(InvalidFromUtf8 => [INVALID_FROM_UTF8_UNCHECKED, INVALID_FROM_UTF8]);
60
61impl<'tcx> LateLintPass<'tcx> for InvalidFromUtf8 {
62    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
63        if let ExprKind::Call(path, [arg]) = expr.kind
64            && let ExprKind::Path(ref qpath) = path.kind
65            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
66            && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
67            && [
68                sym::str_from_utf8,
69                sym::str_from_utf8_mut,
70                sym::str_from_utf8_unchecked,
71                sym::str_from_utf8_unchecked_mut,
72                sym::str_inherent_from_utf8,
73                sym::str_inherent_from_utf8_mut,
74                sym::str_inherent_from_utf8_unchecked,
75                sym::str_inherent_from_utf8_unchecked_mut,
76            ]
77            .contains(&diag_item)
78        {
79            let lint = |label, utf8_error: Utf8Error| {
80                let method = diag_item.as_str().strip_prefix("str_").unwrap();
81                let method = if let Some(method) = method.strip_prefix("inherent_") {
82                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("str::{0}", method))
    })format!("str::{method}")
83                } else {
84                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("std::str::{0}", method))
    })format!("std::str::{method}")
85                };
86                let valid_up_to = utf8_error.valid_up_to();
87                let is_unchecked_variant = diag_item.as_str().contains("unchecked");
88
89                cx.emit_span_lint(
90                    if is_unchecked_variant {
91                        INVALID_FROM_UTF8_UNCHECKED
92                    } else {
93                        INVALID_FROM_UTF8
94                    },
95                    expr.span,
96                    if is_unchecked_variant {
97                        InvalidFromUtf8Diag::Unchecked { method, valid_up_to, label }
98                    } else {
99                        InvalidFromUtf8Diag::Checked { method, valid_up_to, label }
100                    },
101                )
102            };
103
104            let mut init = cx.expr_or_init_with_outside_body(arg);
105            while let ExprKind::AddrOf(.., inner) = init.kind {
106                init = cx.expr_or_init_with_outside_body(inner);
107            }
108            match init.kind {
109                ExprKind::Lit(Spanned { node: lit, .. }) => {
110                    if let LitKind::ByteStr(byte_sym, _) = &lit
111                        && let Err(utf8_error) = std::str::from_utf8(byte_sym.as_byte_str())
112                    {
113                        lint(init.span, utf8_error);
114                    }
115                }
116                ExprKind::Array(args) => {
117                    let elements = args
118                        .iter()
119                        .map(|e| match &e.kind {
120                            ExprKind::Lit(Spanned { node: lit, .. }) => match lit {
121                                LitKind::Byte(b) => Some(*b),
122                                LitKind::Int(b, _) => Some(b.get() as u8),
123                                _ => None,
124                            },
125                            _ => None,
126                        })
127                        .collect::<Option<Vec<_>>>();
128
129                    if let Some(elements) = elements
130                        && let Err(utf8_error) = std::str::from_utf8(&elements)
131                    {
132                        lint(init.span, utf8_error);
133                    }
134                }
135                _ => {}
136            }
137        }
138    }
139}