Skip to content

Commit a7c2eef

Browse files
authored
Rollup merge of #66059 - RalfJung:panic-on-non-zero, r=eddyb
mem::zeroed/uninit: panic on types that do not permit zero-initialization r? @eddyb @oli-obk Cc #62825 Also see [this summary comment](#66059 (comment))
2 parents 1581278 + a09c33e commit a7c2eef

File tree

10 files changed

+370
-169
lines changed

10 files changed

+370
-169
lines changed

src/libcore/intrinsics.rs

+10
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,16 @@ extern "rust-intrinsic" {
10071007
/// This will statically either panic, or do nothing.
10081008
pub fn panic_if_uninhabited<T>();
10091009

1010+
/// A guard for unsafe functions that cannot ever be executed if `T` does not permit
1011+
/// zero-initialization: This will statically either panic, or do nothing.
1012+
#[cfg(not(bootstrap))]
1013+
pub fn panic_if_zero_invalid<T>();
1014+
1015+
/// A guard for unsafe functions that cannot ever be executed if `T` has invalid
1016+
/// bit patterns: This will statically either panic, or do nothing.
1017+
#[cfg(not(bootstrap))]
1018+
pub fn panic_if_any_invalid<T>();
1019+
10101020
/// Gets a reference to a static `Location` indicating where it was called.
10111021
#[rustc_const_unstable(feature = "const_caller_location", issue = "47809")]
10121022
pub fn caller_location() -> &'static crate::panic::Location<'static>;

src/libcore/mem/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,9 @@ pub const fn needs_drop<T>() -> bool {
496496
#[allow(deprecated)]
497497
#[rustc_diagnostic_item = "mem_zeroed"]
498498
pub unsafe fn zeroed<T>() -> T {
499+
#[cfg(not(bootstrap))]
500+
intrinsics::panic_if_zero_invalid::<T>();
501+
#[cfg(bootstrap)]
499502
intrinsics::panic_if_uninhabited::<T>();
500503
intrinsics::init()
501504
}
@@ -529,6 +532,9 @@ pub unsafe fn zeroed<T>() -> T {
529532
#[allow(deprecated)]
530533
#[rustc_diagnostic_item = "mem_uninitialized"]
531534
pub unsafe fn uninitialized<T>() -> T {
535+
#[cfg(not(bootstrap))]
536+
intrinsics::panic_if_any_invalid::<T>();
537+
#[cfg(bootstrap)]
532538
intrinsics::panic_if_uninhabited::<T>();
533539
intrinsics::uninit()
534540
}

src/librustc/ty/layout.rs

-30
Original file line numberDiff line numberDiff line change
@@ -1904,36 +1904,6 @@ impl<'tcx, T: HasTyCtxt<'tcx>> HasTyCtxt<'tcx> for LayoutCx<'tcx, T> {
19041904
}
19051905
}
19061906

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

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

src/librustc_codegen_ssa/mir/block.rs

+92-35
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,89 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
434434
helper.do_call(self, &mut bx, fn_abi, llfn, &args, None, cleanup);
435435
}
436436

437+
/// Returns `true` if this is indeed a panic intrinsic and codegen is done.
438+
fn codegen_panic_intrinsic(
439+
&mut self,
440+
helper: &TerminatorCodegenHelper<'tcx>,
441+
bx: &mut Bx,
442+
intrinsic: Option<&str>,
443+
instance: Option<Instance<'tcx>>,
444+
span: Span,
445+
destination: &Option<(mir::Place<'tcx>, mir::BasicBlock)>,
446+
cleanup: Option<mir::BasicBlock>,
447+
) -> bool {
448+
// Emit a panic or a no-op for `panic_if_uninhabited`.
449+
// These are intrinsics that compile to panics so that we can get a message
450+
// which mentions the offending type, even from a const context.
451+
#[derive(Debug, PartialEq)]
452+
enum PanicIntrinsic {
453+
IfUninhabited,
454+
IfZeroInvalid,
455+
IfAnyInvalid,
456+
};
457+
let panic_intrinsic = intrinsic.and_then(|i| match i {
458+
// FIXME: Move to symbols instead of strings.
459+
"panic_if_uninhabited" => Some(PanicIntrinsic::IfUninhabited),
460+
"panic_if_zero_invalid" => Some(PanicIntrinsic::IfZeroInvalid),
461+
"panic_if_any_invalid" => Some(PanicIntrinsic::IfAnyInvalid),
462+
_ => None,
463+
});
464+
if let Some(intrinsic) = panic_intrinsic {
465+
use PanicIntrinsic::*;
466+
let ty = instance.unwrap().substs.type_at(0);
467+
let layout = bx.layout_of(ty);
468+
let do_panic = match intrinsic {
469+
IfUninhabited => layout.abi.is_uninhabited(),
470+
// We unwrap as the error type is `!`.
471+
IfZeroInvalid => !layout.might_permit_raw_init(bx, /*zero:*/ true).unwrap(),
472+
// We unwrap as the error type is `!`.
473+
IfAnyInvalid => !layout.might_permit_raw_init(bx, /*zero:*/ false).unwrap(),
474+
};
475+
if do_panic {
476+
let msg_str = if layout.abi.is_uninhabited() {
477+
// Use this error even for the other intrinsics as it is more precise.
478+
format!("attempted to instantiate uninhabited type `{}`", ty)
479+
} else if intrinsic == IfZeroInvalid {
480+
format!("attempted to zero-initialize type `{}`, which is invalid", ty)
481+
} else {
482+
format!("attempted to leave type `{}` uninitialized, which is invalid", ty)
483+
};
484+
let msg = bx.const_str(Symbol::intern(&msg_str));
485+
let location = self.get_caller_location(bx, span).immediate();
486+
487+
// Obtain the panic entry point.
488+
// FIXME: dedup this with `codegen_assert_terminator` above.
489+
let def_id =
490+
common::langcall(bx.tcx(), Some(span), "", lang_items::PanicFnLangItem);
491+
let instance = ty::Instance::mono(bx.tcx(), def_id);
492+
let fn_abi = FnAbi::of_instance(bx, instance, &[]);
493+
let llfn = bx.get_fn_addr(instance);
494+
495+
if let Some((_, target)) = destination.as_ref() {
496+
helper.maybe_sideeffect(self.mir, bx, &[*target]);
497+
}
498+
// Codegen the actual panic invoke/call.
499+
helper.do_call(
500+
self,
501+
bx,
502+
fn_abi,
503+
llfn,
504+
&[msg.0, msg.1, location],
505+
destination.as_ref().map(|(_, bb)| (ReturnDest::Nothing, *bb)),
506+
cleanup,
507+
);
508+
} else {
509+
// a NOP
510+
let target = destination.as_ref().unwrap().1;
511+
helper.maybe_sideeffect(self.mir, bx, &[target]);
512+
helper.funclet_br(self, bx, target)
513+
}
514+
true
515+
} else {
516+
false
517+
}
518+
}
519+
437520
fn codegen_call_terminator(
438521
&mut self,
439522
helper: TerminatorCodegenHelper<'tcx>,
@@ -520,41 +603,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
520603
bug!("`miri_start_panic` should never end up in compiled code");
521604
}
522605

523-
// Emit a panic or a no-op for `panic_if_uninhabited`.
524-
if intrinsic == Some("panic_if_uninhabited") {
525-
let ty = instance.unwrap().substs.type_at(0);
526-
let layout = bx.layout_of(ty);
527-
if layout.abi.is_uninhabited() {
528-
let msg_str = format!("Attempted to instantiate uninhabited type {}", ty);
529-
let msg = bx.const_str(Symbol::intern(&msg_str));
530-
let location = self.get_caller_location(&mut bx, span).immediate();
531-
532-
// Obtain the panic entry point.
533-
let def_id =
534-
common::langcall(bx.tcx(), Some(span), "", lang_items::PanicFnLangItem);
535-
let instance = ty::Instance::mono(bx.tcx(), def_id);
536-
let fn_abi = FnAbi::of_instance(&bx, instance, &[]);
537-
let llfn = bx.get_fn_addr(instance);
538-
539-
if let Some((_, target)) = destination.as_ref() {
540-
helper.maybe_sideeffect(self.mir, &mut bx, &[*target]);
541-
}
542-
// Codegen the actual panic invoke/call.
543-
helper.do_call(
544-
self,
545-
&mut bx,
546-
fn_abi,
547-
llfn,
548-
&[msg.0, msg.1, location],
549-
destination.as_ref().map(|(_, bb)| (ReturnDest::Nothing, *bb)),
550-
cleanup,
551-
);
552-
} else {
553-
// a NOP
554-
let target = destination.as_ref().unwrap().1;
555-
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
556-
helper.funclet_br(self, &mut bx, target)
557-
}
606+
if self.codegen_panic_intrinsic(
607+
&helper,
608+
&mut bx,
609+
intrinsic,
610+
instance,
611+
span,
612+
destination,
613+
cleanup,
614+
) {
558615
return;
559616
}
560617

src/librustc_index/vec.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ macro_rules! newtype_index {
196196

197197
#[inline]
198198
fn index(self) -> usize {
199-
usize::from(self)
199+
self.as_usize()
200200
}
201201
}
202202

src/librustc_target/abi/mod.rs

+85
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,7 @@ impl<'a, Ty> Deref for TyLayout<'a, Ty> {
937937
}
938938
}
939939

940+
/// Trait for context types that can compute layouts of things.
940941
pub trait LayoutOf {
941942
type Ty;
942943
type TyLayout;
@@ -947,6 +948,38 @@ pub trait LayoutOf {
947948
}
948949
}
949950

951+
/// The `TyLayout` above will always be a `MaybeResult<TyLayout<'_, Self>>`.
952+
/// We can't add the bound due to the lifetime, but this trait is still useful when
953+
/// writing code that's generic over the `LayoutOf` impl.
954+
pub trait MaybeResult<T> {
955+
type Error;
956+
957+
fn from(x: Result<T, Self::Error>) -> Self;
958+
fn to_result(self) -> Result<T, Self::Error>;
959+
}
960+
961+
impl<T> MaybeResult<T> for T {
962+
type Error = !;
963+
964+
fn from(Ok(x): Result<T, Self::Error>) -> Self {
965+
x
966+
}
967+
fn to_result(self) -> Result<T, Self::Error> {
968+
Ok(self)
969+
}
970+
}
971+
972+
impl<T, E> MaybeResult<T> for Result<T, E> {
973+
type Error = E;
974+
975+
fn from(x: Result<T, Self::Error>) -> Self {
976+
x
977+
}
978+
fn to_result(self) -> Result<T, Self::Error> {
979+
self
980+
}
981+
}
982+
950983
#[derive(Copy, Clone, PartialEq, Eq)]
951984
pub enum PointerKind {
952985
/// Most general case, we know no restrictions to tell LLVM.
@@ -987,13 +1020,17 @@ impl<'a, Ty> TyLayout<'a, Ty> {
9871020
{
9881021
Ty::for_variant(self, cx, variant_index)
9891022
}
1023+
1024+
/// Callers might want to use `C: LayoutOf<Ty=Ty, TyLayout: MaybeResult<Self>>`
1025+
/// to allow recursion (see `might_permit_zero_init` below for an example).
9901026
pub fn field<C>(self, cx: &C, i: usize) -> C::TyLayout
9911027
where
9921028
Ty: TyLayoutMethods<'a, C>,
9931029
C: LayoutOf<Ty = Ty>,
9941030
{
9951031
Ty::field(self, cx, i)
9961032
}
1033+
9971034
pub fn pointee_info_at<C>(self, cx: &C, offset: Size) -> Option<PointeeInfo>
9981035
where
9991036
Ty: TyLayoutMethods<'a, C>,
@@ -1017,4 +1054,52 @@ impl<'a, Ty> TyLayout<'a, Ty> {
10171054
Abi::Aggregate { sized } => sized && self.size.bytes() == 0,
10181055
}
10191056
}
1057+
1058+
/// Determines if this type permits "raw" initialization by just transmuting some
1059+
/// memory into an instance of `T`.
1060+
/// `zero` indicates if the memory is zero-initialized, or alternatively
1061+
/// left entirely uninitialized.
1062+
/// This is conservative: in doubt, it will answer `true`.
1063+
///
1064+
/// FIXME: Once we removed all the conservatism, we could alternatively
1065+
/// create an all-0/all-undef constant and run the const value validator to see if
1066+
/// this is a valid value for the given type.
1067+
pub fn might_permit_raw_init<C, E>(self, cx: &C, zero: bool) -> Result<bool, E>
1068+
where
1069+
Self: Copy,
1070+
Ty: TyLayoutMethods<'a, C>,
1071+
C: LayoutOf<Ty = Ty, TyLayout: MaybeResult<Self, Error = E>> + HasDataLayout,
1072+
{
1073+
let scalar_allows_raw_init = move |s: &Scalar| -> bool {
1074+
if zero {
1075+
let range = &s.valid_range;
1076+
// The range must contain 0.
1077+
range.contains(&0) || (*range.start() > *range.end()) // wrap-around allows 0
1078+
} else {
1079+
// The range must include all values. `valid_range_exclusive` handles
1080+
// the wrap-around using target arithmetic; with wrap-around then the full
1081+
// range is one where `start == end`.
1082+
let range = s.valid_range_exclusive(cx);
1083+
range.start == range.end
1084+
}
1085+
};
1086+
1087+
// Check the ABI.
1088+
let valid = match &self.abi {
1089+
Abi::Uninhabited => false, // definitely UB
1090+
Abi::Scalar(s) => scalar_allows_raw_init(s),
1091+
Abi::ScalarPair(s1, s2) => scalar_allows_raw_init(s1) && scalar_allows_raw_init(s2),
1092+
Abi::Vector { element: s, count } => *count == 0 || scalar_allows_raw_init(s),
1093+
Abi::Aggregate { .. } => true, // Cannot be excluded *right now*.
1094+
};
1095+
if !valid {
1096+
// This is definitely not okay.
1097+
trace!("might_permit_raw_init({:?}, zero={}): not valid", self.details, zero);
1098+
return Ok(false);
1099+
}
1100+
1101+
// If we have not found an error yet, we need to recursively descend.
1102+
// FIXME(#66151): For now, we are conservative and do not do this.
1103+
Ok(true)
1104+
}
10201105
}

src/librustc_target/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/")]
1111
#![feature(bool_to_option)]
1212
#![feature(nll)]
13+
#![feature(never_type)]
14+
#![feature(associated_type_bounds)]
15+
#![feature(exhaustive_patterns)]
1316

1417
#[macro_use]
1518
extern crate log;

src/librustc_typeck/check/intrinsic.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) {
147147
),
148148
"rustc_peek" => (1, vec![param(0)], param(0)),
149149
"caller_location" => (0, vec![], tcx.caller_location_ty()),
150-
"panic_if_uninhabited" => (1, Vec::new(), tcx.mk_unit()),
150+
"panic_if_uninhabited" | "panic_if_zero_invalid" | "panic_if_any_invalid" => {
151+
(1, Vec::new(), tcx.mk_unit())
152+
}
151153
"init" => (1, Vec::new(), param(0)),
152154
"uninit" => (1, Vec::new(), param(0)),
153155
"forget" => (1, vec![param(0)], tcx.mk_unit()),

0 commit comments

Comments
 (0)