• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::str::Utf8Error;
2 
3 use rustc_ast::{BorrowKind, LitKind};
4 use rustc_hir::{Expr, ExprKind};
5 use rustc_span::source_map::Spanned;
6 use rustc_span::sym;
7 
8 use crate::lints::InvalidFromUtf8Diag;
9 use crate::{LateContext, LateLintPass, LintContext};
10 
11 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 an invalid UTF-8 literal.
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 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 an invalid UTF-8 literal.
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 
59 declare_lint_pass!(InvalidFromUtf8 => [INVALID_FROM_UTF8_UNCHECKED, INVALID_FROM_UTF8]);
60 
61 impl<'tcx> LateLintPass<'tcx> for InvalidFromUtf8 {
check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>)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             && [sym::str_from_utf8, sym::str_from_utf8_mut,
68                 sym::str_from_utf8_unchecked, sym::str_from_utf8_unchecked_mut].contains(&diag_item)
69         {
70             let lint = |utf8_error: Utf8Error| {
71                 let label = arg.span;
72                 let method = diag_item.as_str().strip_prefix("str_").unwrap();
73                 let method = format!("std::str::{method}");
74                 let valid_up_to = utf8_error.valid_up_to();
75                 let is_unchecked_variant = diag_item.as_str().contains("unchecked");
76 
77                 cx.emit_spanned_lint(
78                     if is_unchecked_variant { INVALID_FROM_UTF8_UNCHECKED } else { INVALID_FROM_UTF8 },
79                     expr.span,
80                     if is_unchecked_variant {
81                         InvalidFromUtf8Diag::Unchecked { method,  valid_up_to, label }
82                     } else {
83                         InvalidFromUtf8Diag::Checked { method,  valid_up_to, label }
84                     }
85                 )
86             };
87 
88             match &arg.kind {
89                 ExprKind::Lit(Spanned { node: lit, .. }) => {
90                     if let LitKind::ByteStr(bytes, _) = &lit
91                         && let Err(utf8_error) = std::str::from_utf8(bytes)
92                     {
93                         lint(utf8_error);
94                     }
95                 },
96                 ExprKind::AddrOf(BorrowKind::Ref, _, Expr { kind: ExprKind::Array(args), .. }) => {
97                     let elements = args.iter().map(|e|{
98                         match &e.kind {
99                             ExprKind::Lit(Spanned { node: lit, .. }) => match lit {
100                                 LitKind::Byte(b) => Some(*b),
101                                 LitKind::Int(b, _) => Some(*b as u8),
102                                 _ => None
103                             }
104                             _ => None
105                         }
106                     }).collect::<Option<Vec<_>>>();
107 
108                     if let Some(elements) = elements
109                         && let Err(utf8_error) = std::str::from_utf8(&elements)
110                     {
111                         lint(utf8_error);
112                     }
113                 }
114                 _ => {}
115             }
116         }
117     }
118 }
119