diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index e4865ece63b6c..1b9e2579fc691 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -422,14 +422,15 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) { source_info.span, ) } - AssertKind::InvalidEnumConstruction(source) => { + AssertKind::InvalidEnumConstruction(ty, source) => { + let ty = codegen_operand(fx, ty).load_scalar(fx); let source = codegen_operand(fx, source).load_scalar(fx); let location = fx.get_caller_location(source_info).load_scalar(fx); codegen_panic_inner( fx, rustc_hir::LangItem::PanicInvalidEnumConstruction, - &[source, location], + &[ty, source, location], *unwind, source_info.span, ) diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 35de8b5e1486b..6210c84073a37 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -24,6 +24,7 @@ use super::{CachedLlbb, FunctionCx, LocalRef}; use crate::base::{self, is_call_from_compiler_builtins_to_upstream_monomorphization}; use crate::common::{self, IntPredicate}; use crate::errors::CompilerBuiltinsCannotCall; +use crate::mir::operand::OperandValue; use crate::traits::*; use crate::{MemFlags, meth}; @@ -761,11 +762,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // `#[track_caller]` adds an implicit argument. (LangItem::PanicNullPointerDereference, vec![location]) } - AssertKind::InvalidEnumConstruction(source) => { + AssertKind::InvalidEnumConstruction(ty, source) => { let source = self.codegen_operand(bx, source).immediate(); + let OperandValue::Pair(ptr, len) = self.codegen_operand(bx, ty).val else { + panic!("What the hell?"); + }; + let ptr = ptr; + let len = len; // It's `fn panic_invalid_enum_construction(source: u128)`, // `#[track_caller]` adds an implicit argument. - (LangItem::PanicInvalidEnumConstruction, vec![source, location]) + (LangItem::PanicInvalidEnumConstruction, vec![ptr, len, source, location]) } _ => { // It's `pub fn panic_...()` and `#[track_caller]` adds an implicit argument. diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 3b4f7ed3261ab..fa37a867c13a6 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -652,7 +652,9 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { found: eval_to_int(found)?, }, NullPointerDereference => NullPointerDereference, - InvalidEnumConstruction(source) => InvalidEnumConstruction(eval_to_int(source)?), + InvalidEnumConstruction(ty, source) => { + InvalidEnumConstruction(eval_to_int(ty)?, eval_to_int(source)?) + } }; Err(ConstEvalErrKind::AssertFailure(err)).into() } diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 35f67460f51c0..66ee8587d38a6 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1074,7 +1074,7 @@ pub enum AssertKind { ResumedAfterDrop(CoroutineKind), MisalignedPointerDereference { required: O, found: O }, NullPointerDereference, - InvalidEnumConstruction(O), + InvalidEnumConstruction(O, O), } #[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)] diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs index 1e7729bd8d657..e8701be4dea0b 100644 --- a/compiler/rustc_middle/src/mir/terminator.rs +++ b/compiler/rustc_middle/src/mir/terminator.rs @@ -208,7 +208,7 @@ impl AssertKind { LangItem::PanicGenFnNonePanic } NullPointerDereference => LangItem::PanicNullPointerDereference, - InvalidEnumConstruction(_) => LangItem::PanicInvalidEnumConstruction, + InvalidEnumConstruction(..) => LangItem::PanicInvalidEnumConstruction, ResumedAfterDrop(CoroutineKind::Coroutine(_)) => LangItem::PanicCoroutineResumedDrop, ResumedAfterDrop(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) => { LangItem::PanicAsyncFnResumedDrop @@ -285,8 +285,11 @@ impl AssertKind { ) } NullPointerDereference => write!(f, "\"null pointer dereference occurred\""), - InvalidEnumConstruction(source) => { - write!(f, "\"trying to construct an enum from an invalid value {{}}\", {source:?}") + InvalidEnumConstruction(ty, source) => { + write!( + f, + "\"trying to construct an enum from an invalid value {{}}; type {{}}\", {source:?}, {ty:?}" + ) } ResumedAfterReturn(CoroutineKind::Coroutine(_)) => { write!(f, "\"coroutine resumed after completion\"") @@ -379,7 +382,7 @@ impl AssertKind { msg!("coroutine resumed after panicking") } NullPointerDereference => msg!("null pointer dereference occurred"), - InvalidEnumConstruction(_) => { + InvalidEnumConstruction(..) => { msg!("trying to construct an enum from an invalid value `{$source}`") } ResumedAfterDrop(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) => { @@ -437,8 +440,9 @@ impl AssertKind { add!("required", format!("{required:#?}")); add!("found", format!("{found:#?}")); } - InvalidEnumConstruction(source) => { + InvalidEnumConstruction(ty, source) => { add!("source", format!("{source:#?}")); + add!("ty", format!("{ty:#?}")); } } } diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs index 16a8743a6d67b..4050892b76ee6 100644 --- a/compiler/rustc_middle/src/mir/visit.rs +++ b/compiler/rustc_middle/src/mir/visit.rs @@ -685,7 +685,11 @@ macro_rules! make_mir_visitor { self.visit_operand(l, location); self.visit_operand(r, location); } - OverflowNeg(op) | DivisionByZero(op) | RemainderByZero(op) | InvalidEnumConstruction(op) => { + OverflowNeg(op) | DivisionByZero(op) | RemainderByZero(op) => { + self.visit_operand(op, location); + } + InvalidEnumConstruction(ty, op) => { + self.visit_operand(ty, location); self.visit_operand(op, location); } ResumedAfterReturn(_) | ResumedAfterPanic(_) | NullPointerDereference | ResumedAfterDrop(_) => { diff --git a/compiler/rustc_mir_transform/src/check_enums.rs b/compiler/rustc_mir_transform/src/check_enums.rs index 12447dc7cbb0c..41df8d60d4c73 100644 --- a/compiler/rustc_mir_transform/src/check_enums.rs +++ b/compiler/rustc_mir_transform/src/check_enums.rs @@ -1,12 +1,14 @@ -use rustc_abi::{Scalar, Size, TagEncoding, Variants, WrappingRange}; +use rustc_abi::{HasDataLayout, Scalar, Size, TagEncoding, Variants, WrappingRange}; +use rustc_const_eval::interpret::CTFE_ALLOC_SALT; use rustc_hir::LangItem; use rustc_index::IndexVec; use rustc_middle::bug; -use rustc_middle::mir::visit::Visitor; +use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; -use rustc_middle::ty::layout::PrimitiveExt; -use rustc_middle::ty::{self, Ty, TyCtxt, TypingEnv}; +use rustc_middle::ty::layout::{IntegerExt, PrimitiveExt}; +use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt, TypingEnv}; use rustc_session::Session; +use rustc_span::DUMMY_SP; use tracing::debug; /// This pass inserts checks for a valid enum discriminant where they are most @@ -163,79 +165,203 @@ impl<'a, 'tcx> EnumFinder<'a, 'tcx> { fn into_found_enums(self) -> Vec> { self.enums } + + /// Registers a new enum check in the finder. + fn register_new_check( + &mut self, + enum_ty: Ty<'tcx>, + enum_def: AdtDef<'tcx>, + source_op: Operand<'tcx>, + ) { + let Ok(enum_layout) = self.tcx.layout_of(self.typing_env.as_query_input(enum_ty)) else { + return; + }; + // If the operand is a pointer, we want to pass on the size of the operand to the check, + // as we will dereference the pointer and look at the value directly. + let Ok(op_layout) = (if let ty::RawPtr(pointee_ty, _) = + source_op.ty(self.local_decls, self.tcx).kind() + { + self.tcx.layout_of(self.typing_env.as_query_input(*pointee_ty)) + } else { + self.tcx + .layout_of(self.typing_env.as_query_input(source_op.ty(self.local_decls, self.tcx))) + }) else { + return; + }; + + match enum_layout.variants { + Variants::Empty if op_layout.is_uninhabited() => return, + // An empty enum that tries to be constructed from an inhabited value, this + // is never correct. + Variants::Empty => { + // The enum layout is uninhabited but we construct it from sth inhabited. + // This is always UB. + self.enums.push(EnumCheckType::Uninhabited); + } + // Construction of Single value enums is always fine. + Variants::Single { .. } => {} + // Construction of an enum with multiple variants but no niche optimizations. + Variants::Multiple { + tag_encoding: TagEncoding::Direct, + tag: Scalar::Initialized { value, .. }, + .. + } => { + let valid_discrs = + enum_def.discriminants(self.tcx).map(|(_, discr)| discr.val).collect(); + + let discr = TyAndSize { ty: value.to_ty(self.tcx), size: value.size(&self.tcx) }; + self.enums.push(EnumCheckType::Direct { + source_op: source_op.to_copy(), + discr, + op_size: op_layout.size, + valid_discrs, + }); + } + // Construction of an enum with multiple variants and niche optimizations. + Variants::Multiple { + tag_encoding: TagEncoding::Niche { .. }, + tag: Scalar::Initialized { value, valid_range, .. }, + tag_field, + .. + } => { + let discr = TyAndSize { ty: value.to_ty(self.tcx), size: value.size(&self.tcx) }; + self.enums.push(EnumCheckType::WithNiche { + source_op: source_op.to_copy(), + discr, + op_size: op_layout.size, + offset: enum_layout.fields.offset(tag_field.as_usize()), + valid_range, + }); + } + _ => return, + } + } } impl<'a, 'tcx> Visitor<'tcx> for EnumFinder<'a, 'tcx> { - fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { - if let Rvalue::Cast(CastKind::Transmute, op, ty) = rvalue { - let ty::Adt(adt_def, _) = ty.kind() else { - return; - }; - if !adt_def.is_enum() { + fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) { + self.super_place(place, context, location); + // We only want to emit this check on pointer reads. + match context { + PlaceContext::NonMutatingUse( + NonMutatingUseContext::Copy + | NonMutatingUseContext::Move + | NonMutatingUseContext::SharedBorrow, + ) => {} + _ => { return; } + } - let Ok(enum_layout) = self.tcx.layout_of(self.typing_env.as_query_input(*ty)) else { - return; - }; - let Ok(op_layout) = self - .tcx - .layout_of(self.typing_env.as_query_input(op.ty(self.local_decls, self.tcx))) + if !place.is_indirect() { + return; + } + // Get the place and type we visit. + let pointer = Place::from(place.local); + let pointer_ty = pointer.ty(self.local_decls, self.tcx).ty; + + // We only want to check places based on raw pointers to enums or ManuallyDrop. + let &ty::RawPtr(pointee_ty, _) = pointer_ty.kind() else { + return; + }; + let ty::Adt(enum_adt_def, _) = pointee_ty.kind() else { + return; + }; + + let (enum_ty, enum_adt_def) = if enum_adt_def.is_enum() { + (pointee_ty, enum_adt_def) + + // } else if enum_adt_def.is_manually_drop() { + // // Find the type contained in the ManuallyDrop and check whether it is an enum. + // let Some((manual_drop_arg, adt_def)) = + // pointee_ty.walk().skip(1).next().map_or(None, |arg| { + // if let Some(ty) = arg.as_type() + // && let ty::Adt(adt_def, _) = ty.kind() + // { + // Some((ty, adt_def)) + // } else { + // None + // } + // }) + // else { + // return; + // }; + + // (manual_drop_arg, adt_def) + } else { + return; + }; + // Exclude c_void. + if enum_ty.is_c_void(self.tcx) { + return; + } + + self.register_new_check(enum_ty, *enum_adt_def, Operand::Copy(*place)); + } + + fn visit_projection_elem( + &mut self, + place_ref: PlaceRef<'tcx>, + elem: PlaceElem<'tcx>, + context: visit::PlaceContext, + location: Location, + ) { + self.super_projection_elem(place_ref, elem, context, location); + // Check whether we are reading an enum or a ManuallyDrop from a union. + let ty::Adt(union_adt_def, _) = place_ref.ty(self.local_decls, self.tcx).ty.kind() else { + return; + }; + if !union_adt_def.is_union() { + return; + } + let PlaceElem::Field(_, extracted_ty) = elem else { + return; + }; + let ty::Adt(enum_adt_def, _) = extracted_ty.kind() else { + return; + }; + let (enum_ty, enum_adt_def) = if enum_adt_def.is_enum() { + (extracted_ty, enum_adt_def) + } else if enum_adt_def.is_manually_drop() { + // Find the type contained in the ManuallyDrop and check whether it is an enum. + let Some((manual_drop_arg, adt_def)) = + extracted_ty.walk().skip(1).next().map_or(None, |arg| { + if let Some(ty) = arg.as_type() + && let ty::Adt(adt_def, _) = ty.kind() + { + Some((ty, adt_def)) + } else { + None + } + }) else { return; }; - match enum_layout.variants { - Variants::Empty if op_layout.is_uninhabited() => return, - // An empty enum that tries to be constructed from an inhabited value, this - // is never correct. - Variants::Empty => { - // The enum layout is uninhabited but we construct it from sth inhabited. - // This is always UB. - self.enums.push(EnumCheckType::Uninhabited); - } - // Construction of Single value enums is always fine. - Variants::Single { .. } => {} - // Construction of an enum with multiple variants but no niche optimizations. - Variants::Multiple { - tag_encoding: TagEncoding::Direct, - tag: Scalar::Initialized { value, .. }, - .. - } => { - let valid_discrs = - adt_def.discriminants(self.tcx).map(|(_, discr)| discr.val).collect(); - - let discr = - TyAndSize { ty: value.to_int_ty(self.tcx), size: value.size(&self.tcx) }; - self.enums.push(EnumCheckType::Direct { - source_op: op.to_copy(), - discr, - op_size: op_layout.size, - valid_discrs, - }); - } - // Construction of an enum with multiple variants and niche optimizations. - Variants::Multiple { - tag_encoding: TagEncoding::Niche { .. }, - tag: Scalar::Initialized { value, valid_range, .. }, - tag_field, - .. - } => { - let discr = - TyAndSize { ty: value.to_int_ty(self.tcx), size: value.size(&self.tcx) }; - self.enums.push(EnumCheckType::WithNiche { - source_op: op.to_copy(), - discr, - op_size: op_layout.size, - offset: enum_layout.fields.offset(tag_field.as_usize()), - valid_range, - }); - } - _ => return, + (manual_drop_arg, adt_def) + } else { + return; + }; + + self.register_new_check( + enum_ty, + *enum_adt_def, + Operand::Copy(place_ref.to_place(self.tcx)), + ); + } + + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + if let Rvalue::Cast(CastKind::Transmute, op, ty) = rvalue { + let ty::Adt(adt_def, _) = ty.kind() else { + return; + }; + if !adt_def.is_enum() { + return; } - self.super_rvalue(rvalue, location); + self.register_new_check(*ty, *adt_def, op.to_copy()); } + self.super_rvalue(rvalue, location); } } @@ -261,7 +387,7 @@ fn insert_discr_cast_to_u128<'tcx>( local_decls: &mut IndexVec>, block_data: &mut BasicBlockData<'tcx>, source_op: Operand<'tcx>, - discr: TyAndSize<'tcx>, + mut discr: TyAndSize<'tcx>, op_size: Size, offset: Option, source_info: SourceInfo, @@ -277,6 +403,29 @@ fn insert_discr_cast_to_u128<'tcx>( } }; + // If the enum is behind a pointer, cast it to a *[const|mut] MaybeUninit and then extract the discriminant through that. + let source_op = if let ty::RawPtr(pointee_ty, mutbl) = source_op.ty(local_decls, tcx).kind() + && !discr.ty.is_raw_ptr() + { + let mu_ptr_ty = Ty::new_ptr(tcx, Ty::new_maybe_uninit(tcx, *pointee_ty), *mutbl); + let mu_ptr_decl = + local_decls.push(LocalDecl::with_source_info(mu_ptr_ty, source_info)).into(); + let rvalue = Rvalue::Cast(CastKind::Transmute, source_op, mu_ptr_ty); + block_data.statements.push(Statement::new( + source_info, + StatementKind::Assign(Box::new((mu_ptr_decl, rvalue))), + )); + + Operand::Copy(mu_ptr_decl.project_deeper(&[ProjectionElem::Deref], tcx)) + } else { + source_op + }; + + // Correct the discriminant ty to an integer, to not screw up our casts to the discriminant ty. + if discr.ty.is_raw_ptr() { + discr.ty = tcx.data_layout().ptr_sized_integer().to_ty(tcx, false); + } + let (cast_kind, discr_ty_bits) = if discr.size.bytes() < op_size.bytes() { // The discriminant is less wide than the operand, cast the operand into // [MaybeUninit; N] and then index into it. @@ -350,14 +499,15 @@ fn insert_direct_enum_check<'tcx>( new_block: BasicBlock, ) { // Insert a new target block that is branched to in case of an invalid discriminant. - let invalid_discr_block_data = BasicBlockData::new(None, false); + let invalid_discr_block_data = + BasicBlockData::new(None, basic_blocks[current_block].is_cleanup); let invalid_discr_block = basic_blocks.push(invalid_discr_block_data); let block_data = &mut basic_blocks[current_block]; let discr_place = insert_discr_cast_to_u128( tcx, local_decls, block_data, - source_op, + source_op.clone(), discr, op_size, None, @@ -397,6 +547,9 @@ fn insert_direct_enum_check<'tcx>( }, }); + let debug_str = format!("{:#?}", source_op.ty(local_decls, tcx)); + let allocation = + tcx.allocate_bytes_dedup(std::borrow::Cow::Borrowed(debug_str.as_bytes()), CTFE_ALLOC_SALT); // Abort in case of an invalid enum discriminant. basic_blocks[invalid_discr_block].terminator = Some(Terminator { source_info, @@ -408,7 +561,17 @@ fn insert_direct_enum_check<'tcx>( })), expected: true, target: new_block, - msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Copy(discr_masked))), + msg: Box::new(AssertKind::InvalidEnumConstruction( + Operand::Constant(Box::new(ConstOperand { + span: DUMMY_SP, + user_ty: None, + const_: Const::Val( + ConstValue::Slice { alloc_id: allocation, meta: debug_str.len() as u64 }, + Ty::new_ref(tcx, tcx.lifetimes.re_erased, tcx.types.str_, Mutability::Not), + ), + })), + Operand::Copy(discr_masked), + )), // This calls panic_invalid_enum_construction, which is #[rustc_nounwind]. // We never want to insert an unwind into unsafe code, because unwinding could // make a failing UB check turn into much worse UB when we start unwinding. @@ -438,19 +601,30 @@ fn insert_uninhabited_enum_check<'tcx>( ))), )); + let debug_str = "None".to_owned(); + let allocation = + tcx.allocate_bytes_dedup(std::borrow::Cow::Borrowed(debug_str.as_bytes()), CTFE_ALLOC_SALT); block_data.terminator = Some(Terminator { source_info, kind: TerminatorKind::Assert { cond: Operand::Copy(is_ok), expected: true, target: new_block, - msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Constant(Box::new( - ConstOperand { + msg: Box::new(AssertKind::InvalidEnumConstruction( + Operand::Constant(Box::new(ConstOperand { + span: DUMMY_SP, + user_ty: None, + const_: Const::Val( + ConstValue::Slice { alloc_id: allocation, meta: debug_str.len() as u64 }, + Ty::new_ref(tcx, tcx.lifetimes.re_erased, tcx.types.str_, Mutability::Not), + ), + })), + Operand::Constant(Box::new(ConstOperand { span: source_info.span, user_ty: None, const_: Const::Val(ConstValue::from_u128(0), tcx.types.u128), - }, - )))), + })), + )), // This calls panic_invalid_enum_construction, which is #[rustc_nounwind]. // We never want to insert an unwind into unsafe code, because unwinding could // make a failing UB check turn into much worse UB when we start unwinding. @@ -475,7 +649,7 @@ fn insert_niche_check<'tcx>( tcx, local_decls, block_data, - source_op, + source_op.clone(), discr, op_size, Some(offset), @@ -521,13 +695,26 @@ fn insert_niche_check<'tcx>( ))), )); + let debug_str = format!("{:#?}", source_op.ty(local_decls, tcx)); + let allocation = + tcx.allocate_bytes_dedup(std::borrow::Cow::Borrowed(debug_str.as_bytes()), CTFE_ALLOC_SALT); block_data.terminator = Some(Terminator { source_info, kind: TerminatorKind::Assert { cond: Operand::Copy(is_ok), expected: true, target: new_block, - msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Copy(discr))), + msg: Box::new(AssertKind::InvalidEnumConstruction( + Operand::Constant(Box::new(ConstOperand { + span: DUMMY_SP, + user_ty: None, + const_: Const::Val( + ConstValue::Slice { alloc_id: allocation, meta: debug_str.len() as u64 }, + Ty::new_ref(tcx, tcx.lifetimes.re_erased, tcx.types.str_, Mutability::Not), + ), + })), + Operand::Copy(discr), + )), // This calls panic_invalid_enum_construction, which is #[rustc_nounwind]. // We never want to insert an unwind into unsafe code, because unwinding could // make a failing UB check turn into much worse UB when we start unwinding. diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 7dd198ed9f9cc..a410b6297c882 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -704,9 +704,9 @@ pub(crate) fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<' body, &[ // Add some UB checks before any UB gets optimized away. + &check_enums::CheckEnums, &check_alignment::CheckAlignment, &check_null::CheckNull, - &check_enums::CheckEnums, // Before inlining: trim down MIR with passes to reduce inlining work. // Has to be done before inlining, otherwise actual call will be almost always inlined. diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 4f6e2cc005160..63fd9755d264d 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -905,7 +905,7 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> { mir::AssertKind::NullPointerDereference => { push_mono_lang_item(self, LangItem::PanicNullPointerDereference); } - mir::AssertKind::InvalidEnumConstruction(_) => { + mir::AssertKind::InvalidEnumConstruction(..) => { push_mono_lang_item(self, LangItem::PanicInvalidEnumConstruction); } _ => { diff --git a/compiler/rustc_public/src/mir/body.rs b/compiler/rustc_public/src/mir/body.rs index 51757c5827221..47f6718faf52d 100644 --- a/compiler/rustc_public/src/mir/body.rs +++ b/compiler/rustc_public/src/mir/body.rs @@ -269,7 +269,7 @@ pub enum AssertMessage { ResumedAfterDrop(CoroutineKind), MisalignedPointerDereference { required: Operand, found: Operand }, NullPointerDereference, - InvalidEnumConstruction(Operand), + InvalidEnumConstruction(Operand, Operand), } impl AssertMessage { @@ -342,7 +342,7 @@ impl AssertMessage { Ok("misaligned pointer dereference") } AssertMessage::NullPointerDereference => Ok("null pointer dereference occurred"), - AssertMessage::InvalidEnumConstruction(_) => { + AssertMessage::InvalidEnumConstruction(..) => { Ok("trying to construct an enum from an invalid value") } } diff --git a/compiler/rustc_public/src/mir/pretty.rs b/compiler/rustc_public/src/mir/pretty.rs index bf2655e9a789c..c380e1f5eca8d 100644 --- a/compiler/rustc_public/src/mir/pretty.rs +++ b/compiler/rustc_public/src/mir/pretty.rs @@ -311,9 +311,13 @@ fn pretty_assert_message(writer: &mut W, msg: &AssertMessage) -> io::R AssertMessage::NullPointerDereference => { write!(writer, "\"null pointer dereference occurred\"") } - AssertMessage::InvalidEnumConstruction(op) => { + AssertMessage::InvalidEnumConstruction(ty, op) => { + let pretty_ty = pretty_operand(ty); let pretty_op = pretty_operand(op); - write!(writer, "\"trying to construct an enum from an invalid value {{}}\",{pretty_op}") + write!( + writer, + "\"trying to construct an enum from an invalid value {{}}, ty {{}}\",{pretty_op},{pretty_ty}" + ) } AssertMessage::ResumedAfterReturn(_) | AssertMessage::ResumedAfterPanic(_) diff --git a/compiler/rustc_public/src/mir/visit.rs b/compiler/rustc_public/src/mir/visit.rs index e1d9cf31036e2..4ceb801ab1df2 100644 --- a/compiler/rustc_public/src/mir/visit.rs +++ b/compiler/rustc_public/src/mir/visit.rs @@ -358,8 +358,11 @@ macro_rules! make_mir_visitor { } AssertMessage::OverflowNeg(op) | AssertMessage::DivisionByZero(op) - | AssertMessage::RemainderByZero(op) - | AssertMessage::InvalidEnumConstruction(op) => { + | AssertMessage::RemainderByZero(op) => { + self.visit_operand(op, location); + } + AssertMessage::InvalidEnumConstruction(ty, op) => { + self.visit_operand(ty, location); self.visit_operand(op, location); } AssertMessage::ResumedAfterReturn(_) diff --git a/compiler/rustc_public/src/unstable/convert/stable/mir.rs b/compiler/rustc_public/src/unstable/convert/stable/mir.rs index d25751c81f3f5..deda6d847267c 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/mir.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/mir.rs @@ -557,8 +557,11 @@ impl<'tcx> Stable<'tcx> for mir::AssertMessage<'tcx> { } } AssertKind::NullPointerDereference => crate::mir::AssertMessage::NullPointerDereference, - AssertKind::InvalidEnumConstruction(source) => { - crate::mir::AssertMessage::InvalidEnumConstruction(source.stable(tables, cx)) + AssertKind::InvalidEnumConstruction(ty, source) => { + crate::mir::AssertMessage::InvalidEnumConstruction( + ty.stable(tables, cx), + source.stable(tables, cx), + ) } } } diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs index 3609dd1fe2e02..1823ce02549b4 100644 --- a/library/core/src/panicking.rs +++ b/library/core/src/panicking.rs @@ -310,13 +310,13 @@ fn panic_null_pointer_dereference() -> ! { #[track_caller] #[lang = "panic_invalid_enum_construction"] // needed by codegen for panic on invalid enum construction. #[rustc_nounwind] // `CheckEnums` MIR pass requires this function to never unwind -fn panic_invalid_enum_construction(source: u128) -> ! { +fn panic_invalid_enum_construction(ty: &'static str, source: u128) -> ! { if cfg!(panic = "immediate-abort") { super::intrinsics::abort() } panic_nounwind_fmt( - format_args!("trying to construct an enum from an invalid value {source:#x}"), + format_args!("trying to construct an enum from an invalid value {source:#x}; type {ty}"), /* force_no_backtrace */ false, ) } diff --git a/tests/ui/mir/enum/pointer_manually_drop_read_break.rs b/tests/ui/mir/enum/pointer_manually_drop_read_break.rs new file mode 100644 index 0000000000000..5867190bc83ee --- /dev/null +++ b/tests/ui/mir/enum/pointer_manually_drop_read_break.rs @@ -0,0 +1,22 @@ +//@ ignore-test + +//@ run-crash +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x1 + +#[allow(dead_code)] +#[repr(u16)] +#[derive(Copy, Clone)] +enum Single { + A, +} + +fn main() { + let illegal_val: u16 = 1; + let illegal_val_ptr = &raw const illegal_val; + let foo: *const std::mem::ManuallyDrop = + unsafe { std::mem::transmute(illegal_val_ptr) }; + + let val: Single = unsafe { std::mem::ManuallyDrop::into_inner(*foo) }; + println!("{}", val as u16); +} diff --git a/tests/ui/mir/enum/pointer_manually_drop_read_ok.rs b/tests/ui/mir/enum/pointer_manually_drop_read_ok.rs new file mode 100644 index 0000000000000..80627f3d799e8 --- /dev/null +++ b/tests/ui/mir/enum/pointer_manually_drop_read_ok.rs @@ -0,0 +1,20 @@ +//@ ignore-test + +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +#[repr(u16)] +enum Single { + A, +} + +fn main() { + let illegal_val: u16 = 0; + let illegal_val_ptr = &raw const illegal_val; + let foo: *const std::mem::ManuallyDrop = + unsafe { std::mem::transmute(illegal_val_ptr) }; + + let val: Single = unsafe { foo.cast::().read() }; + println!("{}", val as u16); +} diff --git a/tests/ui/mir/enum/pointer_read_break.rs b/tests/ui/mir/enum/pointer_read_break.rs new file mode 100644 index 0000000000000..276c702ee4d96 --- /dev/null +++ b/tests/ui/mir/enum/pointer_read_break.rs @@ -0,0 +1,21 @@ +//@ ignore-test + +//@ run-crash +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x1 + +#[allow(dead_code)] +#[repr(u16)] +#[derive(Copy, Clone)] +enum Single { + A, +} + +fn main() { + let illegal_val: u16 = 1; + let illegal_val_ptr = &raw const illegal_val; + let foo: *const Single = unsafe { std::mem::transmute(illegal_val_ptr) }; + + let val: Single = unsafe { *foo }; + println!("{}", val as u16); +} diff --git a/tests/ui/mir/enum/pointer_read_ok.rs b/tests/ui/mir/enum/pointer_read_ok.rs new file mode 100644 index 0000000000000..04435ecdea687 --- /dev/null +++ b/tests/ui/mir/enum/pointer_read_ok.rs @@ -0,0 +1,19 @@ +//@ ignore-test + +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +#[repr(u16)] +enum Single { + A, +} + +fn main() { + let illegal_val: u16 = 0; + let illegal_val_ptr = &raw const illegal_val; + let foo: *const Single = unsafe { std::mem::transmute(illegal_val_ptr) }; + + let val: Single = unsafe { foo.read() }; + println!("{}", val as u16); +} diff --git a/tests/ui/mir/enum/union_manually_drop_read_break.rs b/tests/ui/mir/enum/union_manually_drop_read_break.rs new file mode 100644 index 0000000000000..7f75567b73ebc --- /dev/null +++ b/tests/ui/mir/enum/union_manually_drop_read_break.rs @@ -0,0 +1,20 @@ +//@ run-crash +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x1 + +#[allow(dead_code)] +#[repr(u16)] +enum Single { + A, +} + +union Foo { + a: std::mem::ManuallyDrop, +} + +fn main() { + let foo = Foo { a: unsafe { std::mem::transmute(1_u16) } }; + + let val: Single = unsafe { std::mem::ManuallyDrop::into_inner(foo.a) }; + println!("{}", val as u16); +} diff --git a/tests/ui/mir/enum/union_manually_drop_read_ok.rs b/tests/ui/mir/enum/union_manually_drop_read_ok.rs new file mode 100644 index 0000000000000..6687d362ce4a8 --- /dev/null +++ b/tests/ui/mir/enum/union_manually_drop_read_ok.rs @@ -0,0 +1,19 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +#[repr(u16)] +enum Single { + A, +} + +union Foo { + a: std::mem::ManuallyDrop, +} + +fn main() { + let foo = Foo { a: unsafe { std::mem::transmute(0_u16) } }; + + let val: Single = unsafe { std::mem::ManuallyDrop::into_inner(foo.a) }; + println!("{}", val as u16); +} diff --git a/tests/ui/mir/enum/union_read_break.rs b/tests/ui/mir/enum/union_read_break.rs new file mode 100644 index 0000000000000..cf745d53396bc --- /dev/null +++ b/tests/ui/mir/enum/union_read_break.rs @@ -0,0 +1,21 @@ +//@ run-crash +//@ compile-flags: -C debug-assertions +//@ error-pattern: trying to construct an enum from an invalid value 0x1 + +#[allow(dead_code)] +#[repr(u16)] +#[derive(Copy, Clone)] +enum Single { + A, +} + +union Foo { + a: Single, +} + +fn main() { + let foo = Foo { a: unsafe { std::mem::transmute(1_u16) } }; + + let val: Single = unsafe { foo.a }; + println!("{}", val as u16); +} diff --git a/tests/ui/mir/enum/union_read_ok.rs b/tests/ui/mir/enum/union_read_ok.rs new file mode 100644 index 0000000000000..568194bf8aaab --- /dev/null +++ b/tests/ui/mir/enum/union_read_ok.rs @@ -0,0 +1,20 @@ +//@ run-pass +//@ compile-flags: -C debug-assertions + +#[allow(dead_code)] +#[repr(u16)] +#[derive(Copy, Clone)] +enum Single { + A, +} + +union Foo { + a: Single, +} + +fn main() { + let foo = Foo { a: unsafe { std::mem::transmute(0_u16) } }; + + let val: Single = unsafe { foo.a }; + println!("{}", val as u16); +}