Skip to content

Commit 10672f8

Browse files
committed
Add mem_uninitialized lint to FCW on most usages
1 parent a4d0341 commit 10672f8

13 files changed

+2046
-4
lines changed

compiler/rustc_lint/src/builtin.rs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2591,6 +2591,204 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
25912591
}
25922592
}
25932593

2594+
declare_lint! {
2595+
/// The `mem_uninitialized` lint detects all uses of `std::mem::uninitialized` that are not
2596+
/// known to be safe.
2597+
///
2598+
/// This function is extremely dangerous, and nearly all uses of it cause immediate Undefined
2599+
/// Behavior.
2600+
///
2601+
/// ### Example
2602+
///
2603+
/// ```rust,compile_fail
2604+
/// #![deny(mem_uninitialized)]
2605+
/// fn main() {
2606+
/// let x: [char; 16] = unsafe { std::mem::uninitialized() };
2607+
/// }
2608+
/// ```
2609+
///
2610+
/// {{produces}}
2611+
///
2612+
/// ### Explanation
2613+
///
2614+
/// Creating an invalid value is undefined behavior, and nearly all types are invalid when left
2615+
/// uninitialized.
2616+
///
2617+
/// To avoid churn, however, this will not lint for types made up entirely of integers, floats,
2618+
/// or raw pointers. This is not saying that leaving these types uninitialized is okay,
2619+
/// however.
2620+
pub MEM_UNINITIALIZED,
2621+
Warn,
2622+
"use of mem::uninitialized",
2623+
@future_incompatible = FutureIncompatibleInfo {
2624+
reference: "FIXME: fill this in",
2625+
reason: FutureIncompatibilityReason::FutureReleaseErrorReportNow,
2626+
explain_reason: false,
2627+
};
2628+
}
2629+
2630+
declare_lint_pass!(MemUninitialized => [MEM_UNINITIALIZED]);
2631+
2632+
impl<'tcx> LateLintPass<'tcx> for MemUninitialized {
2633+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) {
2634+
/// Information about why a type cannot be initialized this way.
2635+
/// Contains an error message and optionally a span to point at.
2636+
type InitError = (String, Option<Span>);
2637+
2638+
/// Determine if this expression is a "dangerous initialization".
2639+
fn is_dangerous_init(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
2640+
if let hir::ExprKind::Call(ref path_expr, _) = expr.kind {
2641+
// Find calls to `mem::{uninitialized,zeroed}` methods.
2642+
if let hir::ExprKind::Path(ref qpath) = path_expr.kind {
2643+
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id() {
2644+
if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, def_id) {
2645+
return true;
2646+
}
2647+
}
2648+
}
2649+
}
2650+
2651+
false
2652+
}
2653+
2654+
/// Return `None` only if we are sure this type does
2655+
/// allow being left uninitialized.
2656+
fn ty_find_init_error<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<InitError> {
2657+
use rustc_type_ir::sty::TyKind::*;
2658+
match ty.kind() {
2659+
// Primitive types that don't like 0 as a value.
2660+
Ref(..) => Some(("references must be non-null".to_string(), None)),
2661+
Adt(..) if ty.is_box() => Some(("`Box` must be non-null".to_string(), None)),
2662+
FnPtr(..) => Some(("function pointers must be non-null".to_string(), None)),
2663+
Never => Some(("the `!` type has no valid value".to_string(), None)),
2664+
RawPtr(tm) if matches!(tm.ty.kind(), Dynamic(..)) =>
2665+
// raw ptr to dyn Trait
2666+
{
2667+
Some(("the vtable of a wide raw pointer must be non-null".to_string(), None))
2668+
}
2669+
// Primitive types with other constraints.
2670+
Bool => Some(("booleans must be either `true` or `false`".to_string(), None)),
2671+
Char => Some(("characters must be a valid Unicode codepoint".to_string(), None)),
2672+
Adt(adt_def, _) if adt_def.is_union() => None,
2673+
// Recurse and checks for some compound types.
2674+
Adt(adt_def, substs) => {
2675+
// First check if this ADT has a layout attribute (like `NonNull` and friends).
2676+
use std::ops::Bound;
2677+
match cx.tcx.layout_scalar_valid_range(adt_def.did()) {
2678+
// We exploit here that `layout_scalar_valid_range` will never
2679+
// return `Bound::Excluded`. (And we have tests checking that we
2680+
// handle the attribute correctly.)
2681+
(Bound::Included(lo), _) if lo > 0 => {
2682+
return Some((format!("`{}` must be non-null", ty), None));
2683+
}
2684+
(Bound::Included(_), _) | (_, Bound::Included(_)) => {
2685+
return Some((
2686+
format!(
2687+
"`{}` must be initialized inside its custom valid range",
2688+
ty,
2689+
),
2690+
None,
2691+
));
2692+
}
2693+
_ => {}
2694+
}
2695+
// Now, recurse.
2696+
match adt_def.variants().len() {
2697+
0 => Some(("enums with no variants have no valid value".to_string(), None)),
2698+
1 => {
2699+
// Struct, or enum with exactly one variant.
2700+
// Proceed recursively, check all fields.
2701+
let variant = &adt_def.variant(VariantIdx::from_u32(0));
2702+
variant.fields.iter().find_map(|field| {
2703+
ty_find_init_error(cx, field.ty(cx.tcx, substs)).map(
2704+
|(mut msg, span)| {
2705+
if span.is_none() {
2706+
// Point to this field, should be helpful for figuring
2707+
// out where the source of the error is.
2708+
let span = cx.tcx.def_span(field.did);
2709+
write!(
2710+
&mut msg,
2711+
" (in this {} field)",
2712+
adt_def.descr()
2713+
)
2714+
.unwrap();
2715+
(msg, Some(span))
2716+
} else {
2717+
// Just forward.
2718+
(msg, span)
2719+
}
2720+
},
2721+
)
2722+
})
2723+
}
2724+
// Multi-variant enum.
2725+
_ => {
2726+
// This will warn on something like Result<MaybeUninit<u32>, !> which
2727+
// is not UB under the current enum layout, even ignoring the 0x01
2728+
// filling.
2729+
//
2730+
// That's probably fine though.
2731+
let span = cx.tcx.def_span(adt_def.did());
2732+
Some((
2733+
"enums have to be initialized to a variant".to_string(),
2734+
Some(span),
2735+
))
2736+
}
2737+
}
2738+
}
2739+
Tuple(..) => {
2740+
// Proceed recursively, check all fields.
2741+
ty.tuple_fields().iter().find_map(|field| ty_find_init_error(cx, field))
2742+
}
2743+
Array(ty, len) => {
2744+
match len.try_eval_usize(cx.tcx, cx.param_env) {
2745+
// Array known to be zero sized, we can't warn.
2746+
Some(0) => None,
2747+
2748+
Some(_) | None => ty_find_init_error(cx, *ty),
2749+
}
2750+
}
2751+
Int(_) | Uint(_) | Float(_) | RawPtr(_) => {
2752+
// These are Plain Old Data types that people expect to work if they leave them
2753+
// uninitialized.
2754+
None
2755+
}
2756+
// Pessimistic fallback.
2757+
_ => Some(("type might not be allowed to be left uninitialized".to_string(), None)),
2758+
}
2759+
}
2760+
2761+
if is_dangerous_init(cx, expr) {
2762+
// This conjures an instance of a type out of nothing,
2763+
// using zeroed or uninitialized memory.
2764+
// We are extremely conservative with what we warn about.
2765+
let conjured_ty = cx.typeck_results().expr_ty(expr);
2766+
if let Some((msg, span)) = with_no_trimmed_paths!(ty_find_init_error(cx, conjured_ty)) {
2767+
// FIXME(davidtwco): make translatable
2768+
cx.struct_span_lint(MEM_UNINITIALIZED, expr.span, |lint| {
2769+
let mut err = with_no_trimmed_paths!(lint.build(&format!(
2770+
"the type `{}` does not definitely permit being left uninitialized",
2771+
conjured_ty,
2772+
)));
2773+
2774+
err.span_label(expr.span, "this code causes undefined behavior when executed");
2775+
err.span_label(
2776+
expr.span,
2777+
"help: use `MaybeUninit<T>` instead, \
2778+
and only call `assume_init` after initialization is done",
2779+
);
2780+
if let Some(span) = span {
2781+
err.span_note(span, &msg);
2782+
} else {
2783+
err.note(&msg);
2784+
}
2785+
err.emit();
2786+
});
2787+
}
2788+
}
2789+
}
2790+
}
2791+
25942792
declare_lint! {
25952793
/// The `clashing_extern_declarations` lint detects when an `extern fn`
25962794
/// has been declared with the same name but different types.

compiler/rustc_lint/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ macro_rules! late_lint_mod_passes {
211211
UnreachablePub: UnreachablePub,
212212
ExplicitOutlivesRequirements: ExplicitOutlivesRequirements,
213213
InvalidValue: InvalidValue,
214+
MemUninitialized: MemUninitialized,
214215
DerefNullPtr: DerefNullPtr,
215216
// May Depend on constants elsewhere
216217
UnusedBrokenConst: UnusedBrokenConst,

library/core/src/mem/maybe_uninit.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use crate::slice;
3333
///
3434
/// ```rust,no_run
3535
/// # #![allow(invalid_value)]
36+
/// # #![cfg_attr(not(bootstrap), allow(mem_uninitialized))]
3637
/// use std::mem::{self, MaybeUninit};
3738
///
3839
/// let b: bool = unsafe { mem::uninitialized() }; // undefined behavior! ⚠️
@@ -48,6 +49,7 @@ use crate::slice;
4849
///
4950
/// ```rust,no_run
5051
/// # #![allow(invalid_value)]
52+
/// # #![cfg_attr(not(bootstrap), allow(mem_uninitialized))]
5153
/// use std::mem::{self, MaybeUninit};
5254
///
5355
/// let x: i32 = unsafe { mem::uninitialized() }; // undefined behavior! ⚠️

src/test/ui/const-generics/issues/issue-61422.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::mem;
77

88
fn foo<const SIZE: usize>() {
99
let arr: [u8; SIZE] = unsafe {
10-
#[allow(deprecated)]
10+
#[allow(deprecated, mem_uninitialized)]
1111
let array: [u8; SIZE] = mem::uninitialized();
1212
array
1313
};
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#![feature(never_type)]
2+
#![deny(mem_uninitialized)]
3+
#![allow(deprecated, invalid_value, dead_code)]
4+
5+
use std::mem::MaybeUninit;
6+
7+
struct UninitStruct {
8+
a: u32,
9+
b: char,
10+
}
11+
12+
unsafe fn unknown_type<T, const N: usize>() {
13+
std::mem::uninitialized::<T>();
14+
//~^ ERROR the type `T` does not definitely permit being left uninitialized
15+
16+
std::mem::uninitialized::<[T; N]>();
17+
//~^ ERROR the type `[T; N]` does not definitely permit being left uninitialized
18+
19+
std::mem::uninitialized::<[char; N]>();
20+
//~^ ERROR the type `[char; N]` does not definitely permit being left uninitialized
21+
22+
std::mem::uninitialized::<[T; 0]>();
23+
std::mem::uninitialized::<[char; 0]>();
24+
}
25+
26+
fn main() {
27+
unsafe {
28+
std::mem::uninitialized::<&'static u32>();
29+
//~^ ERROR the type `&u32` does not definitely permit being left uninitialized
30+
31+
std::mem::uninitialized::<Box<u32>>();
32+
//~^ ERROR the type `std::boxed::Box<u32>` does not definitely permit being left uninitialized
33+
34+
std::mem::uninitialized::<fn()>();
35+
//~^ ERROR the type `fn()` does not definitely permit being left uninitialized
36+
37+
std::mem::uninitialized::<!>();
38+
//~^ ERROR the type `!` does not definitely permit being left uninitialized
39+
40+
std::mem::uninitialized::<*mut dyn std::io::Write>();
41+
//~^ ERROR the type `*mut dyn std::io::Write` does not definitely permit being left uninitialized
42+
43+
std::mem::uninitialized::<bool>();
44+
//~^ ERROR the type `bool` does not definitely permit being left uninitialized
45+
46+
std::mem::uninitialized::<char>();
47+
//~^ ERROR the type `char` does not definitely permit being left uninitialized
48+
49+
std::mem::uninitialized::<UninitStruct>();
50+
//~^ ERROR the type `UninitStruct` does not definitely permit being left uninitialized
51+
52+
std::mem::uninitialized::<(u32, char)>();
53+
//~^ ERROR the type `(u32, char)` does not definitely permit being left uninitialized
54+
55+
std::mem::uninitialized::<MaybeUninit<Box<u32>>>();
56+
std::mem::uninitialized::<usize>();
57+
std::mem::uninitialized::<f32>();
58+
std::mem::uninitialized::<*const u8>();
59+
}
60+
}

0 commit comments

Comments
 (0)