rustc_lint/
invalid_from_utf8.rs1use 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 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 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}