Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mem_uninitialized lint to FCW lint on uses #100342

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 38 additions & 13 deletions compiler/rustc_lint/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2366,7 +2366,20 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
#[derive(Debug, Copy, Clone, PartialEq)]
enum InitKind {
Zeroed,
Uninit,
/// `is_mem_uninit` is true *only* if this is a call to `mem::uninitialized()`, not if
/// this is a `MaybeUninit::uninit().assume_init()`.
///
/// This lets us avoid duplicate errors being shown, for code that matches the
/// mem_uninitialized FCW.
Uninit {
is_mem_uninit: bool,
},
}

impl InitKind {
fn is_uninit(self) -> bool {
matches!(self, InitKind::Uninit { .. })
}
}

/// Information about why a type cannot be initialized this way.
Expand Down Expand Up @@ -2398,7 +2411,9 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
let def_id = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()?;
match cx.tcx.get_diagnostic_name(def_id) {
Some(sym::mem_zeroed) => return Some(InitKind::Zeroed),
Some(sym::mem_uninitialized) => return Some(InitKind::Uninit),
Some(sym::mem_uninitialized) => {
return Some(InitKind::Uninit { is_mem_uninit: true });
}
Some(sym::transmute) if is_zero(&args[0]) => return Some(InitKind::Zeroed),
_ => {}
}
Expand All @@ -2414,7 +2429,9 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
let def_id = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()?;
match cx.tcx.get_diagnostic_name(def_id) {
Some(sym::maybe_uninit_zeroed) => return Some(InitKind::Zeroed),
Some(sym::maybe_uninit_uninit) => return Some(InitKind::Uninit),
Some(sym::maybe_uninit_uninit) => {
return Some(InitKind::Uninit { is_mem_uninit: false });
}
_ => {}
}
}
Expand Down Expand Up @@ -2453,19 +2470,19 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
Some(("the vtable of a wide raw pointer must be non-null".to_string(), None))
}
// Primitive types with other constraints.
Bool if init == InitKind::Uninit => {
Bool if init.is_uninit() => {
Some(("booleans must be either `true` or `false`".to_string(), None))
}
Char if init == InitKind::Uninit => {
Char if init.is_uninit() => {
Some(("characters must be a valid Unicode codepoint".to_string(), None))
}
Int(_) | Uint(_) if init == InitKind::Uninit => {
Int(_) | Uint(_) if init.is_uninit() => {
Some(("integers must not be uninitialized".to_string(), None))
}
Float(_) if init == InitKind::Uninit => {
Float(_) if init.is_uninit() => {
Some(("floats must not be uninitialized".to_string(), None))
}
RawPtr(_) if init == InitKind::Uninit => {
RawPtr(_) if init.is_uninit() => {
Some(("raw pointers must not be uninitialized".to_string(), None))
}
// Recurse and checks for some compound types.
Expand All @@ -2479,9 +2496,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
(Bound::Included(lo), _) if lo > 0 => {
return Some((format!("`{}` must be non-null", ty), None));
}
(Bound::Included(_), _) | (_, Bound::Included(_))
if init == InitKind::Uninit =>
{
(Bound::Included(_), _) | (_, Bound::Included(_)) if init.is_uninit() => {
return Some((
format!(
"`{}` must be initialized inside its custom valid range",
Expand Down Expand Up @@ -2523,7 +2538,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
}
// Multi-variant enum.
_ => {
if init == InitKind::Uninit && is_multi_variant(*adt_def) {
if init.is_uninit() && is_multi_variant(*adt_def) {
let span = cx.tcx.def_span(adt_def.did());
Some((
"enums have to be initialized to a variant".to_string(),
Expand Down Expand Up @@ -2560,6 +2575,16 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
// using zeroed or uninitialized memory.
// We are extremely conservative with what we warn about.
let conjured_ty = cx.typeck_results().expr_ty(expr);

if init == (InitKind::Uninit { is_mem_uninit: true }) {
// We don't want to warn here for things that mem_uninitialized will warn about
if with_no_trimmed_paths!(
crate::mem_uninitialized::ty_find_init_error(cx, conjured_ty).is_some()
) {
return;
}
}

if let Some((msg, span)) =
with_no_trimmed_paths!(ty_find_init_error(cx, conjured_ty, init))
{
Expand All @@ -2570,7 +2595,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
conjured_ty,
match init {
InitKind::Zeroed => "zero-initialization",
InitKind::Uninit => "being left uninitialized",
InitKind::Uninit { .. } => "being left uninitialized",
},
));
err.span_label(expr.span, "this code causes undefined behavior when executed");
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ mod internal;
mod late;
mod let_underscore;
mod levels;
mod mem_uninitialized;
mod methods;
mod non_ascii_idents;
mod non_fmt_panic;
Expand All @@ -69,6 +70,8 @@ mod traits;
mod types;
mod unused;

use mem_uninitialized::MemUninitialized;

pub use array_into_iter::ARRAY_INTO_ITER;

use rustc_ast as ast;
Expand Down Expand Up @@ -211,6 +214,7 @@ macro_rules! late_lint_mod_passes {
UnreachablePub: UnreachablePub,
ExplicitOutlivesRequirements: ExplicitOutlivesRequirements,
InvalidValue: InvalidValue,
MemUninitialized: MemUninitialized,
DerefNullPtr: DerefNullPtr,
// May Depend on constants elsewhere
UnusedBrokenConst: UnusedBrokenConst,
Expand Down
233 changes: 233 additions & 0 deletions compiler/rustc_lint/src/mem_uninitialized.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
use crate::context::LintContext;
use crate::LateContext;
use crate::LateLintPass;
use rustc_hir as hir;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::Ty;
use rustc_session::lint::FutureIncompatibilityReason;
use rustc_span::symbol::sym;
use rustc_span::Span;
use rustc_target::abi::VariantIdx;
use std::fmt::Write;

declare_lint! {
/// The `mem_uninitialized` lint detects all uses of `std::mem::uninitialized` that are not
/// known to be safe.
///
/// This function is extremely dangerous, and nearly all uses of it cause immediate Undefined
/// Behavior.
///
/// ### Example
///
/// ```rust,compile_fail
/// #![deny(mem_uninitialized)]
/// fn main() {
/// let x: [char; 16] = unsafe { std::mem::uninitialized() };
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// Creating an invalid value is undefined behavior, and nearly all types are invalid when left
/// uninitialized.
///
/// To avoid churn, however, this will not lint for types made up entirely of integers, floats,
/// or raw pointers. This is not saying that leaving these types uninitialized is okay,
/// however.
pub MEM_UNINITIALIZED,
Warn,
"use of mem::uninitialized",
@future_incompatible = FutureIncompatibleInfo {
reference: "issue #101570 <https://github.com/rust-lang/rust/issues/101570>",
reason: FutureIncompatibilityReason::FutureReleaseErrorReportNow,
explain_reason: false,
};
}

declare_lint_pass!(MemUninitialized => [MEM_UNINITIALIZED]);

/// Information about why a type cannot be initialized this way.
/// Contains an error message and optionally a span to point at.
pub struct InitError {
msg: String,
span: Option<Span>,
generic: bool,
}

impl InitError {
fn new(msg: impl Into<String>) -> Self {
Self { msg: msg.into(), span: None, generic: false }
}

fn with_span(msg: impl Into<String>, span: Span) -> Self {
Self { msg: msg.into(), span: Some(span), generic: false }
}

fn generic() -> Self {
Self {
msg: "type might not be allowed to be left uninitialized".to_string(),
span: None,
generic: true,
}
}
}

/// Return `None` only if we are sure this type does
/// allow being left uninitialized.
pub fn ty_find_init_error<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<InitError> {
use rustc_type_ir::sty::TyKind::*;
match ty.kind() {
// Primitive types that don't like 0 as a value.
Ref(..) => Some(InitError::new("references must be non-null")),
Adt(..) if ty.is_box() => Some(InitError::new("`Box` must be non-null")),
FnPtr(..) => Some(InitError::new("function pointers must be non-null")),
Never => Some(InitError::new("the `!` type has no valid value")),
RawPtr(tm) if matches!(tm.ty.kind(), Dynamic(..)) =>
// raw ptr to dyn Trait
{
Some(InitError::new("the vtable of a wide raw pointer must be non-null"))
}
// Primitive types with other constraints.
Bool => Some(InitError::new("booleans must be either `true` or `false`")),
Char => Some(InitError::new("characters must be a valid Unicode codepoint")),
Adt(adt_def, _) if adt_def.is_union() => None,
// Recurse and checks for some compound types.
Adt(adt_def, substs) => {
// First check if this ADT has a layout attribute (like `NonNull` and friends).
use std::ops::Bound;
match cx.tcx.layout_scalar_valid_range(adt_def.did()) {
// We exploit here that `layout_scalar_valid_range` will never
// return `Bound::Excluded`. (And we have tests checking that we
// handle the attribute correctly.)
(Bound::Included(lo), _) if lo > 0 => {
return Some(InitError::new(format!("`{ty}` must be non-null")));
}
(Bound::Included(_), _) | (_, Bound::Included(_)) => {
return Some(InitError::new(format!(
"`{ty}` must be initialized inside its custom valid range"
)));
}
_ => {}
}
// Now, recurse.
match adt_def.variants().len() {
0 => Some(InitError::new("enums with no variants have no valid value")),
1 => {
// Struct, or enum with exactly one variant.
// Proceed recursively, check all fields.
let variant = &adt_def.variant(VariantIdx::from_u32(0));
variant.fields.iter().find_map(|field| {
ty_find_init_error(cx, field.ty(cx.tcx, substs)).map(
|InitError { mut msg, span, generic }| {
if span.is_none() {
// Point to this field, should be helpful for figuring
// out where the source of the error is.
let span = cx.tcx.def_span(field.did);
write!(&mut msg, " (in this {} field)", adt_def.descr())
.unwrap();

InitError { msg, span: Some(span), generic }
} else {
// Just forward.
InitError { msg, span, generic }
}
},
)
})
}
// Multi-variant enum.
_ => {
// This will warn on something like Result<MaybeUninit<u32>, !> which
// is not UB under the current enum layout, even ignoring the 0x01
// filling.
//
// That's probably fine though.
let span = cx.tcx.def_span(adt_def.did());
Some(InitError::with_span("enums have to be initialized to a variant", span))
}
}
}
Tuple(..) => {
// Proceed recursively, check all fields.
ty.tuple_fields().iter().find_map(|field| ty_find_init_error(cx, field))
}
Array(ty, len) => {
match len.try_eval_usize(cx.tcx, cx.param_env) {
// Array known to be zero sized, we can't warn.
Some(0) => None,

// Array length known to be nonzero, warn.
Some(1..) => ty_find_init_error(cx, *ty),

// Array length unknown, use the "might not permit" wording.
None => ty_find_init_error(cx, *ty).map(|mut e| {
e.generic = true;
e
}),
}
}
Int(_) | Uint(_) | Float(_) | RawPtr(_) => {
// These are Plain Old Data types that people expect to work if they leave them
// uninitialized.
None
}
// Pessimistic fallback.
_ => Some(InitError::generic()),
}
}

impl<'tcx> LateLintPass<'tcx> for MemUninitialized {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) {
/// Determine if this expression is a "dangerous initialization".
fn is_dangerous_init(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
if let hir::ExprKind::Call(ref path_expr, _) = expr.kind {
// Find calls to `mem::{uninitialized,zeroed}` methods.
if let hir::ExprKind::Path(ref qpath) = path_expr.kind {
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id() {
if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, def_id) {
return true;
}
}
}
}

false
}

if is_dangerous_init(cx, expr) {
// This conjures an instance of a type out of nothing,
// using zeroed or uninitialized memory.
// We are extremely conservative with what we warn about.
let conjured_ty = cx.typeck_results().expr_ty(expr);
if let Some(init_error) = with_no_trimmed_paths!(ty_find_init_error(cx, conjured_ty)) {
let main_msg = with_no_trimmed_paths!(if init_error.generic {
format!(
"the type `{conjured_ty}` is generic, and might not permit being left uninitialized"
)
} else {
format!("the type `{conjured_ty}` does not permit being left uninitialized")
});

// FIXME(davidtwco): make translatable
cx.struct_span_lint(MEM_UNINITIALIZED, expr.span, |lint| {
let mut err = lint.build(&main_msg);

err.span_label(expr.span, "this code causes undefined behavior when executed");
err.span_label(
expr.span,
"help: use `MaybeUninit<T>` instead, \
and only call `assume_init` after initialization is done",
);
if let Some(span) = init_error.span {
err.span_note(span, &init_error.msg);
} else {
err.note(&init_error.msg);
}
err.emit();
});
}
}
}
}
2 changes: 2 additions & 0 deletions library/core/src/mem/maybe_uninit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use crate::slice;
///
/// ```rust,no_run
/// # #![allow(invalid_value)]
/// # #![cfg_attr(not(bootstrap), allow(mem_uninitialized))]
/// use std::mem::{self, MaybeUninit};
///
/// let b: bool = unsafe { mem::uninitialized() }; // undefined behavior! ⚠️
Expand All @@ -48,6 +49,7 @@ use crate::slice;
///
/// ```rust,no_run
/// # #![allow(invalid_value)]
/// # #![cfg_attr(not(bootstrap), allow(mem_uninitialized))]
/// use std::mem::{self, MaybeUninit};
///
/// let x: i32 = unsafe { mem::uninitialized() }; // undefined behavior! ⚠️
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/const-generics/issues/issue-61422.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::mem;

fn foo<const SIZE: usize>() {
let arr: [u8; SIZE] = unsafe {
#[allow(deprecated)]
#[allow(deprecated, mem_uninitialized)]
let array: [u8; SIZE] = mem::uninitialized();
array
};
Expand Down
Loading