Skip to content

Commit

Permalink
Ignore invalid_value lint if mem_uninitialized would lint
Browse files Browse the repository at this point in the history
  • Loading branch information
5225225 committed Sep 7, 2022
1 parent 86a98df commit dc4b6a9
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 692 deletions.
273 changes: 31 additions & 242 deletions compiler/rustc_lint/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2366,7 +2366,13 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
#[derive(Debug, Copy, Clone, PartialEq)]
enum InitKind {
Zeroed,
Uninit,
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 +2404,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 +2422,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 +2463,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 +2489,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 +2531,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 +2568,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 +2588,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 All @@ -2591,235 +2609,6 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
}
}

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: "FIXME: fill this in",
reason: FutureIncompatibilityReason::FutureReleaseErrorReportNow,
explain_reason: false,
};
}

declare_lint_pass!(MemUninitialized => [MEM_UNINITIALIZED]);

impl<'tcx> LateLintPass<'tcx> for MemUninitialized {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) {
/// Information about why a type cannot be initialized this way.
/// Contains an error message and optionally a span to point at.
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,
}
}
}

/// 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
}

/// Return `None` only if we are sure this type does
/// allow being left uninitialized.
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()),
}
}

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();
});
}
}
}
}

declare_lint! {
/// The `clashing_extern_declarations` lint detects when an `extern fn`
/// has been declared with the same name but different types.
Expand Down
3 changes: 3 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
Loading

0 comments on commit dc4b6a9

Please sign in to comment.