Skip to content

Commit 8045dc6

Browse files
committed
Auto merge of #66059 - RalfJung:panic-on-non-zero, r=<try>
mem::zeroed/uninit: panic on types that do not permit zero-initialization r? @eddyb @oli-obk Cc #62825
2 parents 2e4da3c + 321f9e2 commit 8045dc6

File tree

10 files changed

+237
-138
lines changed

10 files changed

+237
-138
lines changed

src/libcore/intrinsics.rs

+5
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,11 @@ extern "rust-intrinsic" {
696696
/// This will statically either panic, or do nothing.
697697
pub fn panic_if_uninhabited<T>();
698698

699+
/// A guard for unsafe functions that cannot ever be executed if `T` does not permit
700+
/// zero-initialization: This will statically either panic, or do nothing.
701+
#[cfg(not(bootstrap))]
702+
pub fn panic_if_non_zero<T>();
703+
699704
/// Gets a reference to a static `Location` indicating where it was called.
700705
#[cfg(not(bootstrap))]
701706
pub fn caller_location() -> &'static crate::panic::Location<'static>;

src/libcore/mem/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,9 @@ pub const fn needs_drop<T>() -> bool {
458458
#[allow(deprecated_in_future)]
459459
#[allow(deprecated)]
460460
pub unsafe fn zeroed<T>() -> T {
461+
#[cfg(not(bootstrap))]
462+
intrinsics::panic_if_non_zero::<T>();
463+
#[cfg(bootstrap)]
461464
intrinsics::panic_if_uninhabited::<T>();
462465
intrinsics::init()
463466
}
@@ -486,6 +489,9 @@ pub unsafe fn zeroed<T>() -> T {
486489
#[allow(deprecated_in_future)]
487490
#[allow(deprecated)]
488491
pub unsafe fn uninitialized<T>() -> T {
492+
#[cfg(not(bootstrap))]
493+
intrinsics::panic_if_non_zero::<T>();
494+
#[cfg(bootstrap)]
489495
intrinsics::panic_if_uninhabited::<T>();
490496
intrinsics::uninit()
491497
}

src/librustc/ty/layout.rs

-30
Original file line numberDiff line numberDiff line change
@@ -1907,36 +1907,6 @@ impl<'tcx, T: HasTyCtxt<'tcx>> HasTyCtxt<'tcx> for LayoutCx<'tcx, T> {
19071907
}
19081908
}
19091909

1910-
pub trait MaybeResult<T> {
1911-
type Error;
1912-
1913-
fn from(x: Result<T, Self::Error>) -> Self;
1914-
fn to_result(self) -> Result<T, Self::Error>;
1915-
}
1916-
1917-
impl<T> MaybeResult<T> for T {
1918-
type Error = !;
1919-
1920-
fn from(x: Result<T, Self::Error>) -> Self {
1921-
let Ok(x) = x;
1922-
x
1923-
}
1924-
fn to_result(self) -> Result<T, Self::Error> {
1925-
Ok(self)
1926-
}
1927-
}
1928-
1929-
impl<T, E> MaybeResult<T> for Result<T, E> {
1930-
type Error = E;
1931-
1932-
fn from(x: Result<T, Self::Error>) -> Self {
1933-
x
1934-
}
1935-
fn to_result(self) -> Result<T, Self::Error> {
1936-
self
1937-
}
1938-
}
1939-
19401910
pub type TyLayout<'tcx> = ::rustc_target::abi::TyLayout<'tcx, Ty<'tcx>>;
19411911

19421912
impl<'tcx> LayoutOf for LayoutCx<'tcx, TyCtxt<'tcx>> {

src/librustc_codegen_ssa/mir/block.rs

+22-3
Original file line numberDiff line numberDiff line change
@@ -529,11 +529,30 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
529529
};
530530

531531
// Emit a panic or a no-op for `panic_if_uninhabited`.
532-
if intrinsic == Some("panic_if_uninhabited") {
532+
// These are intrinsics that compile to panics so that we can get a message
533+
// which mentions the offending type, even from a const context.
534+
#[derive(Debug, PartialEq)]
535+
enum PanicIntrinsic { IfUninhabited, IfNonZero };
536+
let panic_intrinsic = intrinsic.and_then(|i| match i {
537+
"panic_if_uninhabited" => Some(PanicIntrinsic::IfUninhabited),
538+
"panic_if_non_zero" => Some(PanicIntrinsic::IfNonZero),
539+
_ => None
540+
});
541+
if let Some(intrinsic) = panic_intrinsic {
542+
use PanicIntrinsic::*;
533543
let ty = instance.unwrap().substs.type_at(0);
534544
let layout = bx.layout_of(ty);
535-
if layout.abi.is_uninhabited() {
536-
let msg_str = format!("Attempted to instantiate uninhabited type {}", ty);
545+
let do_panic = match intrinsic {
546+
IfUninhabited => layout.abi.is_uninhabited(),
547+
IfNonZero => !layout.might_permit_zero_init(&bx).unwrap(), // error type is `!`
548+
};
549+
if do_panic {
550+
let msg_str = if layout.abi.is_uninhabited() {
551+
// Use this error even for IfNonZero as it is more precise.
552+
format!("attempted to instantiate uninhabited type `{}`", ty)
553+
} else {
554+
format!("attempted to zero-initialize non-zero type `{}`", ty)
555+
};
537556
let msg = bx.const_str(Symbol::intern(&msg_str));
538557
let location = self.get_caller_location(&mut bx, span).immediate();
539558

src/librustc_index/vec.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,12 @@ macro_rules! newtype_index {
182182
impl Idx for $type {
183183
#[inline]
184184
fn new(value: usize) -> Self {
185-
Self::from(value)
185+
Self::from_usize(value)
186186
}
187187

188188
#[inline]
189189
fn index(self) -> usize {
190-
usize::from(self)
190+
self.as_usize()
191191
}
192192
}
193193

@@ -400,7 +400,7 @@ macro_rules! newtype_index {
400400
(@decodable $type:ident) => (
401401
impl ::rustc_serialize::Decodable for $type {
402402
fn decode<D: ::rustc_serialize::Decoder>(d: &mut D) -> Result<Self, D::Error> {
403-
d.read_u32().map(Self::from)
403+
d.read_u32().map(Self::from_u32)
404404
}
405405
}
406406
);

src/librustc_target/abi/mod.rs

+91
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,7 @@ impl<'a, Ty> Deref for TyLayout<'a, Ty> {
10041004
}
10051005
}
10061006

1007+
/// Trait for context types that can compute layouts of things.
10071008
pub trait LayoutOf {
10081009
type Ty;
10091010
type TyLayout;
@@ -1014,6 +1015,39 @@ pub trait LayoutOf {
10141015
}
10151016
}
10161017

1018+
/// The `TyLayout` above will always be a `MaybeResult<TyLayout<'_, Self>>`.
1019+
/// We can't add the bound due to the lifetime, but this trait is still useful when
1020+
/// writing code that's generic over the `LayoutOf` impl.
1021+
pub trait MaybeResult<T> {
1022+
type Error;
1023+
1024+
fn from(x: Result<T, Self::Error>) -> Self;
1025+
fn to_result(self) -> Result<T, Self::Error>;
1026+
}
1027+
1028+
impl<T> MaybeResult<T> for T {
1029+
type Error = !;
1030+
1031+
fn from(x: Result<T, Self::Error>) -> Self {
1032+
let Ok(x) = x;
1033+
x
1034+
}
1035+
fn to_result(self) -> Result<T, Self::Error> {
1036+
Ok(self)
1037+
}
1038+
}
1039+
1040+
impl<T, E> MaybeResult<T> for Result<T, E> {
1041+
type Error = E;
1042+
1043+
fn from(x: Result<T, Self::Error>) -> Self {
1044+
x
1045+
}
1046+
fn to_result(self) -> Result<T, Self::Error> {
1047+
self
1048+
}
1049+
}
1050+
10171051
#[derive(Copy, Clone, PartialEq, Eq)]
10181052
pub enum PointerKind {
10191053
/// Most general case, we know no restrictions to tell LLVM.
@@ -1055,10 +1089,14 @@ impl<'a, Ty> TyLayout<'a, Ty> {
10551089
where Ty: TyLayoutMethods<'a, C>, C: LayoutOf<Ty = Ty> {
10561090
Ty::for_variant(self, cx, variant_index)
10571091
}
1092+
1093+
/// Callers might want to use `C: LayoutOf<Ty=Ty, TyLayout: MaybeResult<Self>>`
1094+
/// to allow recursion (see `might_permit_zero_init` below for an example).
10581095
pub fn field<C>(self, cx: &C, i: usize) -> C::TyLayout
10591096
where Ty: TyLayoutMethods<'a, C>, C: LayoutOf<Ty = Ty> {
10601097
Ty::field(self, cx, i)
10611098
}
1099+
10621100
pub fn pointee_info_at<C>(self, cx: &C, offset: Size) -> Option<PointeeInfo>
10631101
where Ty: TyLayoutMethods<'a, C>, C: LayoutOf<Ty = Ty> {
10641102
Ty::pointee_info_at(self, cx, offset)
@@ -1081,4 +1119,57 @@ impl<'a, Ty> TyLayout<'a, Ty> {
10811119
Abi::Aggregate { sized } => sized && self.size.bytes() == 0
10821120
}
10831121
}
1122+
1123+
/// Determines if zero-initializing this type might be okay.
1124+
/// This is conservative: in doubt, it will answer `true`.
1125+
pub fn might_permit_zero_init<C, E>(
1126+
&self,
1127+
cx: &C,
1128+
) -> Result<bool, E>
1129+
where
1130+
Self: Copy,
1131+
Ty: TyLayoutMethods<'a, C>,
1132+
C: LayoutOf<Ty = Ty, TyLayout: MaybeResult<Self, Error = E>>
1133+
{
1134+
fn scalar_allows_zero(s: &Scalar) -> bool {
1135+
(*s.valid_range.start() <= 0) || // `&& *s.valid_range.end() >= 0` would be redundant
1136+
(*s.valid_range.start() > *s.valid_range.end()) // wrap-around allows 0
1137+
}
1138+
1139+
// Abi is the most informative here.
1140+
let res = match &self.abi {
1141+
Abi::Uninhabited => false, // definitely UB
1142+
Abi::Scalar(s) => scalar_allows_zero(s),
1143+
Abi::ScalarPair(s1, s2) =>
1144+
scalar_allows_zero(s1) && scalar_allows_zero(s2),
1145+
Abi::Vector { element: s, count } =>
1146+
*count == 0 || scalar_allows_zero(s),
1147+
Abi::Aggregate { .. } => {
1148+
// For aggregates, recurse.
1149+
let inner = match self.variants {
1150+
Variants::Multiple { .. } => // FIXME: get variant with "0" discriminant.
1151+
return Ok(true),
1152+
Variants::Single { index } => self.for_variant(&cx, index),
1153+
};
1154+
1155+
match inner.fields {
1156+
FieldPlacement::Union(..) => true, // An all-0 unit is fine.
1157+
FieldPlacement::Array { .. } =>
1158+
// FIXME: The widely use smallvec 0.6 creates uninit arrays
1159+
// with any element type, so let us not (yet) complain about that.
1160+
// count == 0 || inner.field(cx, 0).to_result()?.might_permit_zero_init(cx)?
1161+
true,
1162+
FieldPlacement::Arbitrary { ref offsets, .. } =>
1163+
// Check that all fields accept zero-init.
1164+
(0..offsets.len()).try_fold(true, |accu, idx|
1165+
Ok(accu &&
1166+
inner.field(cx, idx).to_result()?.might_permit_zero_init(cx)?
1167+
)
1168+
)?
1169+
}
1170+
}
1171+
};
1172+
trace!("might_permit_zero_init({:?}) = {}", self.details, res);
1173+
Ok(res)
1174+
}
10841175
}

src/librustc_target/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
#![feature(box_syntax)]
1313
#![feature(nll)]
1414
#![feature(slice_patterns)]
15+
#![feature(never_type)]
16+
#![feature(associated_type_bounds)]
17+
#![feature(exhaustive_patterns)]
1518

1619
#[macro_use] extern crate log;
1720

src/librustc_typeck/check/intrinsic.rs

+1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem) {
154154
),
155155
),
156156
"panic_if_uninhabited" => (1, Vec::new(), tcx.mk_unit()),
157+
"panic_if_non_zero" => (1, Vec::new(), tcx.mk_unit()),
157158
"init" => (1, Vec::new(), param(0)),
158159
"uninit" => (1, Vec::new(), param(0)),
159160
"forget" => (1, vec![param(0)], tcx.mk_unit()),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// run-pass
2+
// ignore-wasm32-bare compiled with panic=abort by default
3+
4+
// This test checks panic emitted from `mem::{uninitialized,zeroed}`.
5+
6+
#![feature(never_type)]
7+
#![allow(deprecated, invalid_value)]
8+
9+
use std::{mem, panic};
10+
use std::ptr::NonNull;
11+
12+
#[allow(dead_code)]
13+
struct Foo {
14+
x: u8,
15+
y: !,
16+
}
17+
18+
enum Bar {}
19+
20+
#[allow(dead_code)]
21+
enum OneVariant { Variant(i32) }
22+
23+
fn test_panic_msg<T>(op: impl (FnOnce() -> T) + panic::UnwindSafe, msg: &str) {
24+
let err = panic::catch_unwind(op).err();
25+
assert_eq!(
26+
err.as_ref().and_then(|a| a.downcast_ref::<String>()).map(|s| &**s),
27+
Some(msg)
28+
);
29+
}
30+
31+
fn main() {
32+
unsafe {
33+
// Uninitialized types
34+
test_panic_msg(
35+
|| mem::uninitialized::<!>(),
36+
"attempted to instantiate uninhabited type `!`"
37+
);
38+
test_panic_msg(
39+
|| mem::zeroed::<!>(),
40+
"attempted to instantiate uninhabited type `!`"
41+
);
42+
test_panic_msg(
43+
|| mem::MaybeUninit::<!>::uninit().assume_init(),
44+
"attempted to instantiate uninhabited type `!`"
45+
);
46+
47+
test_panic_msg(
48+
|| mem::uninitialized::<Foo>(),
49+
"attempted to instantiate uninhabited type `Foo`"
50+
);
51+
test_panic_msg(
52+
|| mem::zeroed::<Foo>(),
53+
"attempted to instantiate uninhabited type `Foo`"
54+
);
55+
test_panic_msg(
56+
|| mem::MaybeUninit::<Foo>::uninit().assume_init(),
57+
"attempted to instantiate uninhabited type `Foo`"
58+
);
59+
60+
test_panic_msg(
61+
|| mem::uninitialized::<Bar>(),
62+
"attempted to instantiate uninhabited type `Bar`"
63+
);
64+
test_panic_msg(
65+
|| mem::zeroed::<Bar>(),
66+
"attempted to instantiate uninhabited type `Bar`"
67+
);
68+
test_panic_msg(
69+
|| mem::MaybeUninit::<Bar>::uninit().assume_init(),
70+
"attempted to instantiate uninhabited type `Bar`"
71+
);
72+
73+
// Types that do not like zero-initialziation
74+
test_panic_msg(
75+
|| mem::uninitialized::<fn()>(),
76+
"attempted to zero-initialize non-zero type `fn()`"
77+
);
78+
test_panic_msg(
79+
|| mem::zeroed::<fn()>(),
80+
"attempted to zero-initialize non-zero type `fn()`"
81+
);
82+
83+
test_panic_msg(
84+
|| mem::uninitialized::<*const dyn Send>(),
85+
"attempted to zero-initialize non-zero type `*const dyn std::marker::Send`"
86+
);
87+
test_panic_msg(
88+
|| mem::zeroed::<*const dyn Send>(),
89+
"attempted to zero-initialize non-zero type `*const dyn std::marker::Send`"
90+
);
91+
92+
test_panic_msg(
93+
|| mem::uninitialized::<(NonNull<u32>, u32, u32)>(),
94+
"attempted to zero-initialize non-zero type `(std::ptr::NonNull<u32>, u32, u32)`"
95+
);
96+
test_panic_msg(
97+
|| mem::zeroed::<(NonNull<u32>, u32, u32)>(),
98+
"attempted to zero-initialize non-zero type `(std::ptr::NonNull<u32>, u32, u32)`"
99+
);
100+
101+
// Some things that should work.
102+
let _val = mem::zeroed::<bool>();
103+
let _val = mem::zeroed::<OneVariant>();
104+
let _val = mem::zeroed::<Option<&'static i32>>();
105+
}
106+
}

0 commit comments

Comments
 (0)