diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index d34a3afcba53a..e707ac41a050d 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -304,6 +304,14 @@ lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[re
lint_improper_ctypes_union_layout_reason = this union has unspecified layout
lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive
+# FIXME: we should ordinalize $valid_up_to when we add support for doing so
+lint_invalid_from_utf8_checked = calls to `{$method}` with a invalid literal always return an error
+ .label = the literal was valid UTF-8 up to the {$valid_up_to} bytes
+
+# FIXME: we should ordinalize $valid_up_to when we add support for doing so
+lint_invalid_from_utf8_unchecked = calls to `{$method}` with a invalid literal are undefined behavior
+ .label = the literal was valid UTF-8 up to the {$valid_up_to} bytes
+
lint_lintpass_by_hand = implementing `LintPass` by hand
.help = try using `declare_lint_pass!` or `impl_lint_pass!` instead
diff --git a/compiler/rustc_lint/src/invalid_from_utf8.rs b/compiler/rustc_lint/src/invalid_from_utf8.rs
new file mode 100644
index 0000000000000..3291286ad679b
--- /dev/null
+++ b/compiler/rustc_lint/src/invalid_from_utf8.rs
@@ -0,0 +1,118 @@
+use std::str::Utf8Error;
+
+use rustc_ast::{BorrowKind, LitKind};
+use rustc_hir::{Expr, ExprKind};
+use rustc_span::source_map::Spanned;
+use rustc_span::sym;
+
+use crate::lints::InvalidFromUtf8Diag;
+use crate::{LateContext, LateLintPass, LintContext};
+
+declare_lint! {
+ /// The `invalid_from_utf8_unchecked` lint checks for calls to
+ /// `std::str::from_utf8_unchecked` and `std::str::from_utf8_unchecked_mut`
+ /// with an invalid UTF-8 literal.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// # #[allow(unused)]
+ /// unsafe {
+ /// std::str::from_utf8_unchecked(b"Ru\x82st");
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Creating such a `str` would result in undefined behavior as per documentation
+ /// for `std::str::from_utf8_unchecked` and `std::str::from_utf8_unchecked_mut`.
+ pub INVALID_FROM_UTF8_UNCHECKED,
+ Deny,
+ "using a non UTF-8 literal in `std::str::from_utf8_unchecked`"
+}
+
+declare_lint! {
+ /// The `invalid_from_utf8` lint checks for calls to
+ /// `std::str::from_utf8` and `std::str::from_utf8_mut`
+ /// with an invalid UTF-8 literal.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// # #[allow(unused)]
+ /// std::str::from_utf8(b"Ru\x82st");
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Trying to create such a `str` would always return an error as per documentation
+ /// for `std::str::from_utf8` and `std::str::from_utf8_mut`.
+ pub INVALID_FROM_UTF8,
+ Warn,
+ "using a non UTF-8 literal in `std::str::from_utf8`"
+}
+
+declare_lint_pass!(InvalidFromUtf8 => [INVALID_FROM_UTF8_UNCHECKED, INVALID_FROM_UTF8]);
+
+impl<'tcx> LateLintPass<'tcx> for InvalidFromUtf8 {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if let ExprKind::Call(path, [arg]) = expr.kind
+ && let ExprKind::Path(ref qpath) = path.kind
+ && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
+ && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
+ && [sym::str_from_utf8, sym::str_from_utf8_mut,
+ sym::str_from_utf8_unchecked, sym::str_from_utf8_unchecked_mut].contains(&diag_item)
+ {
+ let lint = |utf8_error: Utf8Error| {
+ let label = arg.span;
+ let method = diag_item.as_str().strip_prefix("str_").unwrap();
+ let method = format!("std::str::{method}");
+ let valid_up_to = utf8_error.valid_up_to();
+ let is_unchecked_variant = diag_item.as_str().contains("unchecked");
+
+ cx.emit_spanned_lint(
+ if is_unchecked_variant { INVALID_FROM_UTF8_UNCHECKED } else { INVALID_FROM_UTF8 },
+ expr.span,
+ if is_unchecked_variant {
+ InvalidFromUtf8Diag::Unchecked { method, valid_up_to, label }
+ } else {
+ InvalidFromUtf8Diag::Checked { method, valid_up_to, label }
+ }
+ )
+ };
+
+ match &arg.kind {
+ ExprKind::Lit(Spanned { node: lit, .. }) => {
+ if let LitKind::ByteStr(bytes, _) = &lit
+ && let Err(utf8_error) = std::str::from_utf8(bytes)
+ {
+ lint(utf8_error);
+ }
+ },
+ ExprKind::AddrOf(BorrowKind::Ref, _, Expr { kind: ExprKind::Array(args), .. }) => {
+ let elements = args.iter().map(|e|{
+ match &e.kind {
+ ExprKind::Lit(Spanned { node: lit, .. }) => match lit {
+ LitKind::Byte(b) => Some(*b),
+ LitKind::Int(b, _) => Some(*b as u8),
+ _ => None
+ }
+ _ => None
+ }
+ }).collect::