Skip to content

Commit a00b4f1

Browse files
authored
Rollup merge of rust-lang#63657 - RalfJung:invalid_value, r=Centril
Crank up invalid value lint * Warn against uninit `bool` and `char`. * Warn against 0-init `NonNull` and friends * Detect transmute-from-0 as zero-initialization ([seen in the wild](glium/glium#1775 (comment)))
2 parents a396434 + 3288be5 commit a00b4f1

File tree

6 files changed

+267
-82
lines changed

6 files changed

+267
-82
lines changed

src/librustc_lint/builtin.rs

+111-43
Original file line numberDiff line numberDiff line change
@@ -1876,25 +1876,101 @@ declare_lint_pass!(InvalidValue => [INVALID_VALUE]);
18761876
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue {
18771877
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &hir::Expr) {
18781878

1879-
const ZEROED_PATH: &[Symbol] = &[sym::core, sym::mem, sym::zeroed];
1880-
const UININIT_PATH: &[Symbol] = &[sym::core, sym::mem, sym::uninitialized];
1879+
#[derive(Debug, Copy, Clone, PartialEq)]
1880+
enum InitKind { Zeroed, Uninit };
18811881

18821882
/// Information about why a type cannot be initialized this way.
18831883
/// Contains an error message and optionally a span to point at.
18841884
type InitError = (String, Option<Span>);
18851885

1886+
/// Test if this constant is all-0.
1887+
fn is_zero(expr: &hir::Expr) -> bool {
1888+
use hir::ExprKind::*;
1889+
use syntax::ast::LitKind::*;
1890+
match &expr.node {
1891+
Lit(lit) =>
1892+
if let Int(i, _) = lit.node {
1893+
i == 0
1894+
} else {
1895+
false
1896+
},
1897+
Tup(tup) =>
1898+
tup.iter().all(is_zero),
1899+
_ =>
1900+
false
1901+
}
1902+
}
1903+
1904+
/// Determine if this expression is a "dangerous initialization".
1905+
fn is_dangerous_init(cx: &LateContext<'_, '_>, expr: &hir::Expr) -> Option<InitKind> {
1906+
const ZEROED_PATH: &[Symbol] = &[sym::core, sym::mem, sym::zeroed];
1907+
const UININIT_PATH: &[Symbol] = &[sym::core, sym::mem, sym::uninitialized];
1908+
// `transmute` is inside an anonymous module (the `extern` block?);
1909+
// `Invalid` represents the empty string and matches that.
1910+
const TRANSMUTE_PATH: &[Symbol] =
1911+
&[sym::core, sym::intrinsics, kw::Invalid, sym::transmute];
1912+
1913+
if let hir::ExprKind::Call(ref path_expr, ref args) = expr.node {
1914+
if let hir::ExprKind::Path(ref qpath) = path_expr.node {
1915+
let def_id = cx.tables.qpath_res(qpath, path_expr.hir_id).opt_def_id()?;
1916+
1917+
if cx.match_def_path(def_id, ZEROED_PATH) {
1918+
return Some(InitKind::Zeroed);
1919+
}
1920+
if cx.match_def_path(def_id, UININIT_PATH) {
1921+
return Some(InitKind::Uninit);
1922+
}
1923+
if cx.match_def_path(def_id, TRANSMUTE_PATH) {
1924+
if is_zero(&args[0]) {
1925+
return Some(InitKind::Zeroed);
1926+
}
1927+
}
1928+
// FIXME: Also detect `MaybeUninit::zeroed().assume_init()` and
1929+
// `MaybeUninit::uninit().assume_init()`.
1930+
}
1931+
}
1932+
1933+
None
1934+
}
1935+
18861936
/// Return `Some` only if we are sure this type does *not*
18871937
/// allow zero initialization.
1888-
fn ty_find_init_error<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<InitError> {
1938+
fn ty_find_init_error<'tcx>(
1939+
tcx: TyCtxt<'tcx>,
1940+
ty: Ty<'tcx>,
1941+
init: InitKind,
1942+
) -> Option<InitError> {
18891943
use rustc::ty::TyKind::*;
18901944
match ty.sty {
18911945
// Primitive types that don't like 0 as a value.
18921946
Ref(..) => Some((format!("References must be non-null"), None)),
18931947
Adt(..) if ty.is_box() => Some((format!("`Box` must be non-null"), None)),
18941948
FnPtr(..) => Some((format!("Function pointers must be non-null"), None)),
18951949
Never => Some((format!("The never type (`!`) has no valid value"), None)),
1896-
// Recurse for some compound types.
1950+
// Primitive types with other constraints.
1951+
Bool if init == InitKind::Uninit =>
1952+
Some((format!("Booleans must be `true` or `false`"), None)),
1953+
Char if init == InitKind::Uninit =>
1954+
Some((format!("Characters must be a valid unicode codepoint"), None)),
1955+
// Recurse and checks for some compound types.
18971956
Adt(adt_def, substs) if !adt_def.is_union() => {
1957+
// First check f this ADT has a layout attribute (like `NonNull` and friends).
1958+
use std::ops::Bound;
1959+
match tcx.layout_scalar_valid_range(adt_def.did) {
1960+
// We exploit here that `layout_scalar_valid_range` will never
1961+
// return `Bound::Excluded`. (And we have tests checking that we
1962+
// handle the attribute correctly.)
1963+
(Bound::Included(lo), _) if lo > 0 =>
1964+
return Some((format!("{} must be non-null", ty), None)),
1965+
(Bound::Included(_), _) | (_, Bound::Included(_))
1966+
if init == InitKind::Uninit =>
1967+
return Some((
1968+
format!("{} must be initialized inside its custom valid range", ty),
1969+
None,
1970+
)),
1971+
_ => {}
1972+
}
1973+
// Now, recurse.
18981974
match adt_def.variants.len() {
18991975
0 => Some((format!("0-variant enums have no valid value"), None)),
19001976
1 => {
@@ -1905,6 +1981,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue {
19051981
ty_find_init_error(
19061982
tcx,
19071983
field.ty(tcx, substs),
1984+
init,
19081985
).map(|(mut msg, span)| if span.is_none() {
19091986
// Point to this field, should be helpful for figuring
19101987
// out where the source of the error is.
@@ -1918,57 +1995,48 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue {
19181995
})
19191996
})
19201997
}
1998+
// Multi-variant enums are tricky: if all but one variant are
1999+
// uninhabited, we might actually do layout like for a single-variant
2000+
// enum, and then even leaving them uninitialized could be okay.
19212001
_ => None, // Conservative fallback for multi-variant enum.
19222002
}
19232003
}
19242004
Tuple(..) => {
19252005
// Proceed recursively, check all fields.
1926-
ty.tuple_fields().find_map(|field| ty_find_init_error(tcx, field))
2006+
ty.tuple_fields().find_map(|field| ty_find_init_error(tcx, field, init))
19272007
}
1928-
// FIXME: Would be nice to also warn for `NonNull`/`NonZero*`.
1929-
// FIXME: *Only for `mem::uninitialized`*, we could also warn for `bool`,
1930-
// `char`, and any multivariant enum.
19312008
// Conservative fallback.
19322009
_ => None,
19332010
}
19342011
}
19352012

1936-
if let hir::ExprKind::Call(ref path_expr, ref _args) = expr.node {
1937-
if let hir::ExprKind::Path(ref qpath) = path_expr.node {
1938-
if let Some(def_id) = cx.tables.qpath_res(qpath, path_expr.hir_id).opt_def_id() {
1939-
if cx.match_def_path(def_id, &ZEROED_PATH) ||
1940-
cx.match_def_path(def_id, &UININIT_PATH)
1941-
{
1942-
// This conjures an instance of a type out of nothing,
1943-
// using zeroed or uninitialized memory.
1944-
// We are extremely conservative with what we warn about.
1945-
let conjured_ty = cx.tables.expr_ty(expr);
1946-
if let Some((msg, span)) = ty_find_init_error(cx.tcx, conjured_ty) {
1947-
let mut err = cx.struct_span_lint(
1948-
INVALID_VALUE,
1949-
expr.span,
1950-
&format!(
1951-
"the type `{}` does not permit {}",
1952-
conjured_ty,
1953-
if cx.match_def_path(def_id, &ZEROED_PATH) {
1954-
"zero-initialization"
1955-
} else {
1956-
"being left uninitialized"
1957-
}
1958-
),
1959-
);
1960-
err.span_label(expr.span,
1961-
"this code causes undefined behavior when executed");
1962-
err.span_label(expr.span, "help: use `MaybeUninit<T>` instead");
1963-
if let Some(span) = span {
1964-
err.span_note(span, &msg);
1965-
} else {
1966-
err.note(&msg);
1967-
}
1968-
err.emit();
1969-
}
1970-
}
2013+
if let Some(init) = is_dangerous_init(cx, expr) {
2014+
// This conjures an instance of a type out of nothing,
2015+
// using zeroed or uninitialized memory.
2016+
// We are extremely conservative with what we warn about.
2017+
let conjured_ty = cx.tables.expr_ty(expr);
2018+
if let Some((msg, span)) = ty_find_init_error(cx.tcx, conjured_ty, init) {
2019+
let mut err = cx.struct_span_lint(
2020+
INVALID_VALUE,
2021+
expr.span,
2022+
&format!(
2023+
"the type `{}` does not permit {}",
2024+
conjured_ty,
2025+
match init {
2026+
InitKind::Zeroed => "zero-initialization",
2027+
InitKind::Uninit => "being left uninitialized",
2028+
},
2029+
),
2030+
);
2031+
err.span_label(expr.span,
2032+
"this code causes undefined behavior when executed");
2033+
err.span_label(expr.span, "help: use `MaybeUninit<T>` instead");
2034+
if let Some(span) = span {
2035+
err.span_note(span, &msg);
2036+
} else {
2037+
err.note(&msg);
19712038
}
2039+
err.emit();
19722040
}
19732041
}
19742042
}

src/test/ui/consts/const-eval/ub-nonnull.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![feature(rustc_attrs, const_transmute)]
2-
#![allow(const_err)] // make sure we cannot allow away the errors tested here
2+
#![allow(const_err, invalid_value)] // make sure we cannot allow away the errors tested here
33

44
use std::mem;
55
use std::ptr::NonNull;

src/test/ui/consts/const-eval/ub-ref.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// ignore-tidy-linelength
22
#![feature(const_transmute)]
3-
#![allow(const_err)] // make sure we cannot allow away the errors tested here
3+
#![allow(const_err, invalid_value)] // make sure we cannot allow away the errors tested here
44

55
use std::mem;
66

src/test/ui/consts/const-eval/ub-upvars.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![feature(const_transmute)]
2-
#![allow(const_err)] // make sure we cannot allow away the errors tested here
2+
#![allow(const_err, invalid_value)] // make sure we cannot allow away the errors tested here
33

44
use std::mem;
55

src/test/ui/lint/uninitialized-zeroed.rs

+28-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
// This test checks that calling `mem::{uninitialized,zeroed}` with certain types results
33
// in a lint.
44

5-
#![feature(never_type)]
5+
#![feature(never_type, rustc_attrs)]
66
#![allow(deprecated)]
77
#![deny(invalid_value)]
88

99
use std::mem::{self, MaybeUninit};
10+
use std::ptr::NonNull;
11+
use std::num::NonZeroU32;
1012

1113
enum Void {}
1214

@@ -16,6 +18,11 @@ struct RefPair((&'static i32, i32));
1618
struct Wrap<T> { wrapped: T }
1719
enum WrapEnum<T> { Wrapped(T) }
1820

21+
#[rustc_layout_scalar_valid_range_start(0)]
22+
#[rustc_layout_scalar_valid_range_end(128)]
23+
#[repr(transparent)]
24+
pub(crate) struct NonBig(u64);
25+
1926
#[allow(unused)]
2027
fn generic<T: 'static>() {
2128
unsafe {
@@ -29,6 +36,7 @@ fn generic<T: 'static>() {
2936

3037
fn main() {
3138
unsafe {
39+
// Things that cannot even be zero.
3240
let _val: ! = mem::zeroed(); //~ ERROR: does not permit zero-initialization
3341
let _val: ! = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
3442

@@ -56,11 +64,28 @@ fn main() {
5664
let _val: Wrap<(RefPair, i32)> = mem::zeroed(); //~ ERROR: does not permit zero-initialization
5765
let _val: Wrap<(RefPair, i32)> = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
5866

59-
// Some types that should work just fine.
67+
let _val: NonNull<i32> = mem::zeroed(); //~ ERROR: does not permit zero-initialization
68+
let _val: NonNull<i32> = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
69+
70+
// Things that can be zero, but not uninit.
71+
let _val: bool = mem::zeroed();
72+
let _val: bool = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
73+
74+
let _val: Wrap<char> = mem::zeroed();
75+
let _val: Wrap<char> = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
76+
77+
let _val: NonBig = mem::zeroed();
78+
let _val: NonBig = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
79+
80+
// Transmute-from-0
81+
let _val: &'static i32 = mem::transmute(0usize); //~ ERROR: does not permit zero-initialization
82+
let _val: &'static [i32] = mem::transmute((0usize, 0usize)); //~ ERROR: does not permit zero-initialization
83+
let _val: NonZeroU32 = mem::transmute(0); //~ ERROR: does not permit zero-initialization
84+
85+
// Some more types that should work just fine.
6086
let _val: Option<&'static i32> = mem::zeroed();
6187
let _val: Option<fn()> = mem::zeroed();
6288
let _val: MaybeUninit<&'static i32> = mem::zeroed();
63-
let _val: bool = mem::zeroed();
6489
let _val: i32 = mem::zeroed();
6590
}
6691
}

0 commit comments

Comments
 (0)