From 9c7354e6c750809b84dba310f3866e5f1ec6f816 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Sat, 19 Nov 2022 13:33:46 -0500 Subject: [PATCH] Check for occupied niches --- compiler/rustc_codegen_cranelift/src/base.rs | 13 + compiler/rustc_codegen_ssa/src/common.rs | 9 +- compiler/rustc_codegen_ssa/src/mir/block.rs | 40 +- .../src/const_eval/machine.rs | 5 + compiler/rustc_hir/src/lang_items.rs | 1 + compiler/rustc_middle/messages.ftl | 3 + compiler/rustc_middle/src/mir/syntax.rs | 1 + compiler/rustc_middle/src/mir/terminator.rs | 16 +- compiler/rustc_middle/src/mir/visit.rs | 5 + .../rustc_mir_transform/src/check_niches.rs | 502 ++++++++++++++++++ compiler/rustc_mir_transform/src/lib.rs | 3 + compiler/rustc_monomorphize/src/collector.rs | 14 + compiler/rustc_smir/src/rustc_smir/mod.rs | 7 + compiler/rustc_span/src/symbol.rs | 1 + compiler/stable_mir/src/mir/body.rs | 1 + compiler/stable_mir/src/mir/visit.rs | 5 + library/core/src/panicking.rs | 16 + src/tools/miri/src/lib.rs | 2 +- tests/codegen/array-equality.rs | 1 + tests/codegen/debug-vtable.rs | 2 +- tests/codegen/intrinsics/transmute-niched.rs | 2 +- .../cfi-emit-type-checks-attr-no-sanitize.rs | 2 +- .../codegen/sanitizer/cfi-emit-type-checks.rs | 2 +- tests/codegen/thread-local.rs | 1 + tests/codegen/transmute-scalar.rs | 2 +- tests/mir-opt/remove_storage_markers.rs | 2 +- tests/ui-fulldeps/stable-mir/crate-info.rs | 1 + .../large_assignments/box_rc_arc_allowed.rs | 2 +- .../check_niches/invalid_bool_transmutes.rs | 10 + tests/ui/mir/check_niches/invalid_enums.rs | 23 + .../check_niches/invalid_nonnull_transmute.rs | 10 + .../check_niches/invalid_nonzero_argument.rs | 14 + .../mir/check_niches/valid_bool_transmutes.rs | 12 + .../check_niches/valid_nonzero_argument.rs | 13 + .../saturating-float-casts-wasm.rs | 2 +- .../saturating-float-casts.rs | 2 +- tests/ui/print_type_sizes/niche-filling.rs | 2 +- .../self-in-enum-definition.rs | 2 + .../self-in-enum-definition.stderr | 30 +- 39 files changed, 734 insertions(+), 47 deletions(-) create mode 100644 compiler/rustc_mir_transform/src/check_niches.rs create mode 100644 tests/ui/mir/check_niches/invalid_bool_transmutes.rs create mode 100644 tests/ui/mir/check_niches/invalid_enums.rs create mode 100644 tests/ui/mir/check_niches/invalid_nonnull_transmute.rs create mode 100644 tests/ui/mir/check_niches/invalid_nonzero_argument.rs create mode 100644 tests/ui/mir/check_niches/valid_bool_transmutes.rs create mode 100644 tests/ui/mir/check_niches/valid_nonzero_argument.rs diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index 91b1547cb6ea6..e273eb511b68a 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -368,6 +368,19 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) { source_info.span, ); } + AssertKind::OccupiedNiche { ref found, ref start, ref end } => { + let found = codegen_operand(fx, found).load_scalar(fx); + let start = codegen_operand(fx, start).load_scalar(fx); + let end = codegen_operand(fx, end).load_scalar(fx); + let location = fx.get_caller_location(source_info).load_scalar(fx); + + codegen_panic_inner( + fx, + rustc_hir::LangItem::PanicOccupiedNiche, + &[found, start, end, location], + source_info.span, + ) + } _ => { let msg_str = msg.description(); codegen_panic(fx, msg_str, source_info); diff --git a/compiler/rustc_codegen_ssa/src/common.rs b/compiler/rustc_codegen_ssa/src/common.rs index 641ac3eb80872..9e2372f614ce8 100644 --- a/compiler/rustc_codegen_ssa/src/common.rs +++ b/compiler/rustc_codegen_ssa/src/common.rs @@ -2,7 +2,7 @@ use rustc_hir::LangItem; use rustc_middle::mir; -use rustc_middle::ty::{self, layout::TyAndLayout, Ty, TyCtxt}; +use rustc_middle::ty::{self, layout::TyAndLayout, GenericArg, Ty, TyCtxt}; use rustc_span::Span; use crate::base; @@ -120,10 +120,15 @@ pub fn build_langcall<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( bx: &Bx, span: Option, li: LangItem, + generic: Option>, ) -> (Bx::FnAbiOfResult, Bx::Value) { let tcx = bx.tcx(); let def_id = tcx.require_lang_item(li, span); - let instance = ty::Instance::mono(tcx, def_id); + let instance = if let Some(arg) = generic { + ty::Instance::new(def_id, tcx.mk_args(&[arg])) + } else { + ty::Instance::mono(tcx, def_id) + }; (bx.fn_abi_of_instance(instance, ty::List::empty()), bx.get_fn_addr(instance)) } diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 3d2d8f8b50990..ec784d10fa5f1 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -572,7 +572,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { mergeable_succ: bool, ) -> MergingSucc { let span = terminator.source_info.span; - let cond = self.codegen_operand(bx, cond).immediate(); + let mut cond = self.codegen_operand(bx, cond).immediate(); let mut const_cond = bx.const_to_opt_u128(cond, false).map(|c| c == 1); // This case can currently arise only from functions marked @@ -588,8 +588,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { return helper.funclet_br(self, bx, target, mergeable_succ); } - // Pass the condition through llvm.expect for branch hinting. - let cond = bx.expect(cond, expected); + if bx.tcx().sess.opts.optimize != OptLevel::No { + // Pass the condition through llvm.expect for branch hinting. + cond = bx.expect(cond, expected); + } // Create the failure block and the conditional branch to it. let lltarget = helper.llbb_with_cleanup(self, target); @@ -608,30 +610,40 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let location = self.get_caller_location(bx, terminator.source_info).immediate(); // Put together the arguments to the panic entry point. - let (lang_item, args) = match msg { + let (lang_item, args, generic) = match msg { AssertKind::BoundsCheck { ref len, ref index } => { let len = self.codegen_operand(bx, len).immediate(); let index = self.codegen_operand(bx, index).immediate(); // It's `fn panic_bounds_check(index: usize, len: usize)`, // and `#[track_caller]` adds an implicit third argument. - (LangItem::PanicBoundsCheck, vec![index, len, location]) + (LangItem::PanicBoundsCheck, vec![index, len, location], None) } AssertKind::MisalignedPointerDereference { ref required, ref found } => { let required = self.codegen_operand(bx, required).immediate(); let found = self.codegen_operand(bx, found).immediate(); // It's `fn panic_misaligned_pointer_dereference(required: usize, found: usize)`, // and `#[track_caller]` adds an implicit third argument. - (LangItem::PanicMisalignedPointerDereference, vec![required, found, location]) + (LangItem::PanicMisalignedPointerDereference, vec![required, found, location], None) + } + AssertKind::OccupiedNiche { ref found, ref start, ref end } => { + let found = self.codegen_operand(bx, found); + let generic_arg = ty::GenericArg::from(found.layout.ty); + let found = found.immediate(); + let start = self.codegen_operand(bx, start).immediate(); + let end = self.codegen_operand(bx, end).immediate(); + // It's `fn panic_occupied_niche(found: T, start: T, end: T)`, + // and `#[track_caller]` adds an implicit fourth argument. + (LangItem::PanicOccupiedNiche, vec![found, start, end, location], Some(generic_arg)) } _ => { let msg = bx.const_str(msg.description()); // It's `pub fn panic(expr: &str)`, with the wide reference being passed // as two arguments, and `#[track_caller]` adds an implicit third argument. - (LangItem::Panic, vec![msg.0, msg.1, location]) + (LangItem::Panic, vec![msg.0, msg.1, location], None) } }; - let (fn_abi, llfn) = common::build_langcall(bx, Some(span), lang_item); + let (fn_abi, llfn) = common::build_langcall(bx, Some(span), lang_item, generic); // Codegen the actual panic invoke/call. let merging_succ = helper.do_call(self, bx, fn_abi, llfn, &args, None, unwind, &[], false); @@ -650,7 +662,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { self.set_debug_loc(bx, terminator.source_info); // Obtain the panic entry point. - let (fn_abi, llfn) = common::build_langcall(bx, Some(span), reason.lang_item()); + let (fn_abi, llfn) = common::build_langcall(bx, Some(span), reason.lang_item(), None); // Codegen the actual panic invoke/call. let merging_succ = helper.do_call( @@ -711,8 +723,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let msg = bx.const_str(&msg_str); // Obtain the panic entry point. - let (fn_abi, llfn) = - common::build_langcall(bx, Some(source_info.span), LangItem::PanicNounwind); + let (fn_abi, llfn) = common::build_langcall( + bx, + Some(source_info.span), + LangItem::PanicNounwind, + None, + ); // Codegen the actual panic invoke/call. helper.do_call( @@ -1586,7 +1602,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { self.set_debug_loc(&mut bx, mir::SourceInfo::outermost(self.mir.span)); - let (fn_abi, fn_ptr) = common::build_langcall(&bx, None, reason.lang_item()); + let (fn_abi, fn_ptr) = common::build_langcall(&bx, None, reason.lang_item(), None); let fn_ty = bx.fn_decl_backend_type(&fn_abi); let llret = bx.call(fn_ty, None, Some(&fn_abi), fn_ptr, &[], funclet.as_ref()); diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 4b447229c5f61..8e949ca1e5b62 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -561,6 +561,11 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, found: eval_to_int(found)?, } } + OccupiedNiche { ref found, ref start, ref end } => OccupiedNiche { + found: eval_to_int(found)?, + start: eval_to_int(start)?, + end: eval_to_int(end)?, + }, }; Err(ConstEvalErrKind::AssertFailure(err).into()) } diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 1d1a1ee886272..65377efdb45a6 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -234,6 +234,7 @@ language_item_table! { ConstPanicFmt, sym::const_panic_fmt, const_panic_fmt, Target::Fn, GenericRequirement::None; PanicBoundsCheck, sym::panic_bounds_check, panic_bounds_check_fn, Target::Fn, GenericRequirement::Exact(0); PanicMisalignedPointerDereference, sym::panic_misaligned_pointer_dereference, panic_misaligned_pointer_dereference_fn, Target::Fn, GenericRequirement::Exact(0); + PanicOccupiedNiche, sym::panic_occupied_niche, panic_occupied_niche_fn, Target::Fn, GenericRequirement::Exact(1); PanicInfo, sym::panic_info, panic_info, Target::Struct, GenericRequirement::None; PanicLocation, sym::panic_location, panic_location, Target::Struct, GenericRequirement::None; PanicImpl, sym::panic_impl, panic_impl, Target::Fn, GenericRequirement::None; diff --git a/compiler/rustc_middle/messages.ftl b/compiler/rustc_middle/messages.ftl index 27d555d7e26c7..0c613cb3718f6 100644 --- a/compiler/rustc_middle/messages.ftl +++ b/compiler/rustc_middle/messages.ftl @@ -17,6 +17,9 @@ middle_assert_gen_resume_after_panic = `gen` fn or block cannot be further itera middle_assert_misaligned_ptr_deref = misaligned pointer dereference: address must be a multiple of {$required} but is {$found} +middle_assert_occupied_niche = + occupied niche: {$found} must be in {$start}..={$end} + middle_assert_op_overflow = attempt to compute `{$left} {$op} {$right}`, which would overflow diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 7b0f27f9b348c..448de1682756d 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -886,6 +886,7 @@ pub enum AssertKind { ResumedAfterReturn(CoroutineKind), ResumedAfterPanic(CoroutineKind), MisalignedPointerDereference { required: O, found: O }, + OccupiedNiche { found: O, start: O, end: 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 9dfbe1733cc51..27900ab10cf24 100644 --- a/compiler/rustc_middle/src/mir/terminator.rs +++ b/compiler/rustc_middle/src/mir/terminator.rs @@ -157,7 +157,7 @@ impl AssertKind { "`gen fn` should just keep returning `None` after panicking" } - BoundsCheck { .. } | MisalignedPointerDereference { .. } => { + BoundsCheck { .. } | MisalignedPointerDereference { .. } | OccupiedNiche { .. } => { bug!("Unexpected AssertKind") } } @@ -220,6 +220,13 @@ impl AssertKind { "\"misaligned pointer dereference: address must be a multiple of {{}} but is {{}}\", {required:?}, {found:?}" ) } + OccupiedNiche { found, start, end } => { + write!( + f, + "\"occupied niche: {{}} must be in {{}}..={{}}\", {:?}, {:?}, {:?}", + found, start, end + ) + } _ => write!(f, "\"{}\"", self.description()), } } @@ -254,8 +261,8 @@ impl AssertKind { ResumedAfterPanic(CoroutineKind::Coroutine) => { middle_assert_coroutine_resume_after_panic } - MisalignedPointerDereference { .. } => middle_assert_misaligned_ptr_deref, + OccupiedNiche { .. } => middle_assert_occupied_niche, } } @@ -292,6 +299,11 @@ impl AssertKind { add!("required", format!("{required:#?}")); add!("found", format!("{found:#?}")); } + OccupiedNiche { found, start, end } => { + add!("found", format!("{found:?}")); + add!("start", format!("{start:?}")); + add!("end", format!("{end:?}")); + } } } } diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs index d47cfd5712f8b..6c5e5a87b00f4 100644 --- a/compiler/rustc_middle/src/mir/visit.rs +++ b/compiler/rustc_middle/src/mir/visit.rs @@ -625,6 +625,11 @@ macro_rules! make_mir_visitor { self.visit_operand(required, location); self.visit_operand(found, location); } + OccupiedNiche { found, start, end } => { + self.visit_operand(found, location); + self.visit_operand(start, location); + self.visit_operand(end, location); + } } } diff --git a/compiler/rustc_mir_transform/src/check_niches.rs b/compiler/rustc_mir_transform/src/check_niches.rs new file mode 100644 index 0000000000000..b740e5251efa6 --- /dev/null +++ b/compiler/rustc_mir_transform/src/check_niches.rs @@ -0,0 +1,502 @@ +use crate::MirPass; +use rustc_hir::def::DefKind; +use rustc_hir::lang_items::LangItem; +use rustc_hir::Unsafety; +use rustc_index::IndexVec; +use rustc_middle::mir::interpret::{Pointer, Scalar}; +use rustc_middle::mir::visit::NonMutatingUseContext; +use rustc_middle::mir::visit::PlaceContext; +use rustc_middle::mir::visit::Visitor; +use rustc_middle::mir::*; +use rustc_middle::ty::print::with_no_trimmed_paths; +use rustc_middle::ty::{ParamEnv, Ty, TyCtxt, TypeAndMut}; +use rustc_session::Session; +use rustc_target::abi::{Integer, Niche, Primitive, Size}; + +pub struct CheckNiches; + +impl<'tcx> MirPass<'tcx> for CheckNiches { + fn is_enabled(&self, sess: &Session) -> bool { + sess.opts.debug_assertions + } + + fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + // This pass emits new panics. If for whatever reason we do not have a panic + // implementation, running this pass may cause otherwise-valid code to not compile. + if tcx.lang_items().get(LangItem::PanicImpl).is_none() { + return; + } + + let def_id = body.source.def_id(); + if tcx.type_of(def_id).instantiate_identity().is_coroutine() { + return; + } + + // This pass will in general insert an enormous amount of checks, so we need some way to + // trim them down a bit. + // Our tactic here is to only emit checks if we are compiling an `unsafe fn` or a function + // that contains an unsafe block. Yes this means that we can fail to check some operations. + // If you have a better strategy that doesn't impose 2x compile time overhead, please + // share. + let is_safe_code = + tcx.unsafety_check_result(def_id.expect_local()).used_unsafe_blocks.is_empty(); + let is_unsafe_fn = match tcx.def_kind(def_id) { + DefKind::Closure => false, + _ => tcx.fn_sig(def_id).skip_binder().unsafety() == Unsafety::Unsafe, + }; + if is_safe_code && !is_unsafe_fn { + return; + } + + with_no_trimmed_paths!(debug!("Inserting niche checks for {:?}", body.source)); + + let basic_blocks = &body.basic_blocks; + let param_env = tcx.param_env_reveal_all_normalized(def_id); + + // This pass inserts new blocks. Each insertion changes the Location for all + // statements/blocks after. Iterating or visiting the MIR in order would require updating + // our current location after every insertion. By iterating backwards, we dodge this issue: + // The only Locations that an insertion changes have already been handled. + for block in (0..basic_blocks.len()).rev() { + let block = block.into(); + let basic_blocks = &body.basic_blocks; + for statement_index in (0..basic_blocks[block].statements.len()).rev() { + let location = Location { block, statement_index }; + let statement = &body.basic_blocks[block].statements[statement_index]; + let source_info = statement.source_info; + + let mut finder = NicheFinder { tcx, param_env, body, places: Vec::new() }; + finder.visit_statement(statement, location); + let places = finder.places; + + let mut checker = NicheChecker { tcx, local_decls: &mut body.local_decls }; + for (place, niche) in places { + let basic_blocks = body.basic_blocks.as_mut(); + let (block_data, new_block) = split_block(basic_blocks, location); + checker.insert_niche_check(block_data, new_block, place, niche, source_info); + } + } + } + } +} + +struct NicheFinder<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, + body: &'a Body<'tcx>, + places: Vec<(Operand<'tcx>, NicheKind)>, +} + +impl<'a, 'tcx> Visitor<'tcx> for NicheFinder<'a, 'tcx> { + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + if let Rvalue::Cast(CastKind::Transmute, op, ty) = rvalue { + if let Some(niche) = self.get_niche(*ty) { + with_no_trimmed_paths!(debug!( + "Found place {op:?}: {ty:?} with niche {niche:?} due to Transmute: {:?}", + self.body.stmt_at(location) + )); + self.places.push((op.clone(), niche)); + } + } + + self.super_rvalue(rvalue, location); + } + + fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) { + match context { + PlaceContext::NonMutatingUse( + NonMutatingUseContext::Inspect + | NonMutatingUseContext::Copy + | NonMutatingUseContext::Move, + ) => {} + _ => { + return; + } + } + + let ty = place.ty(self.body, self.tcx).ty; + // bool is actually an i1 to LLVM which means a bool local can never be invalid. + if ty == self.tcx.types.bool && place.projection.is_empty() { + return; + } + let Some(niche) = self.get_niche(ty) else { + return; + }; + + if location.statement_index > 0 { + let prev = self.body.stmt_at(Location { + block: location.block, + statement_index: location.statement_index - 1, + }); + if let StatementKind::Assign(box (_place, Rvalue::Aggregate(_, _))) = + &prev.unwrap_left().kind + { + return; + } + } + + with_no_trimmed_paths!(debug!( + "Found place {place:?}: {ty:?} with niche {niche:?} due to {:?}", + self.body.stmt_at(location) + )); + self.places.push((Operand::Copy(*place), niche)); + + self.super_place(place, context, location); + } +} + +impl<'a, 'tcx> NicheFinder<'a, 'tcx> { + fn get_niche(&self, ty: Ty<'tcx>) -> Option { + // If we can't get the layout of this, just skip the check. + let Ok(layout) = self.tcx.layout_of(self.param_env.and(ty)) else { + return None; + }; + + let niche = layout.largest_niche?; + + if niche.offset == Size::ZERO { + return None; + } + + if niche.size(self.tcx) == layout.size { + Some(NicheKind::Full(niche)) + } else { + Some(NicheKind::Partial(niche, layout.size)) + } + } +} + +struct NicheChecker<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + local_decls: &'a mut IndexVec>, +} + +impl<'a, 'tcx> NicheChecker<'a, 'tcx> { + fn insert_niche_check( + &mut self, + block_data: &mut BasicBlockData<'tcx>, + new_block: BasicBlock, + operand: Operand<'tcx>, + niche: NicheKind, + source_info: SourceInfo, + ) { + let mut value_in_niche = self + .local_decls + .push(LocalDecl::with_source_info(niche.niche().ty(self.tcx), source_info)) + .into(); + + match niche { + NicheKind::Full(niche) => { + // The niche occupies the entire source Operand, so we can just transmute + // directly to the niche primitive. + let rvalue = Rvalue::Cast(CastKind::Transmute, operand, niche.ty(self.tcx)); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new((value_in_niche, rvalue))), + }); + } + NicheKind::Partial(niche, size) => { + if niche.offset == Size::ZERO { + // FIXME: Delete value_in_niche, we don't need it on this path + self.local_decls.pop(); + + // Take the address of the place + let full_ptr = self + .local_decls + .push(LocalDecl::with_source_info( + Ty::new_ptr( + self.tcx, + TypeAndMut { + ty: operand.ty(self.local_decls, self.tcx), + mutbl: Mutability::Not, + }, + ), + source_info, + )) + .into(); + let rvalue = Rvalue::AddressOf(Mutability::Not, operand.place().unwrap()); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new((full_ptr, rvalue))), + }); + + // Cast to a pointer to the niche type + let niche_ptr_ty = Ty::new_ptr( + self.tcx, + TypeAndMut { ty: niche.ty(self.tcx), mutbl: Mutability::Not }, + ); + let niche_ptr = self + .local_decls + .push(LocalDecl::with_source_info(niche_ptr_ty, source_info)) + .into(); + let rvalue = + Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(full_ptr), niche_ptr_ty); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new((niche_ptr, rvalue))), + }); + + // Set value_in_niche to a projection of our pointer + value_in_niche = niche_ptr.project_deeper(&[ProjectionElem::Deref], self.tcx); + } else { + let mu = Ty::new_maybe_uninit(self.tcx, niche.ty(self.tcx)); + + // Transmute the niche-containing type to a [MaybeUninit; N] + let array_len = size.bytes() / niche.size(self.tcx).bytes(); + let mu_array_ty = Ty::new_array(self.tcx, mu, array_len); + let mu_array = self + .local_decls + .push(LocalDecl::with_source_info(mu_array_ty, source_info)) + .into(); + let rvalue = Rvalue::Cast(CastKind::Transmute, operand, mu_array_ty); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new((mu_array, rvalue))), + }); + + // Convert the niche byte offset into an array index + assert_eq!(niche.offset.bytes() % niche.size(self.tcx).bytes(), 0); + let offset = niche.offset.bytes() / niche.size(self.tcx).bytes(); + + let niche_as_mu = mu_array.project_deeper( + &[ProjectionElem::ConstantIndex { + offset, + min_length: array_len, + from_end: false, + }], + self.tcx, + ); + + // Transmute the MaybeUninit to the niche primitive + let rvalue = Rvalue::Cast( + CastKind::Transmute, + Operand::Copy(niche_as_mu), + niche.ty(self.tcx), + ); + block_data.statements.push(Statement { + source_info, + kind: StatementKind::Assign(Box::new((value_in_niche, rvalue))), + }); + } + } + } + + let is_in_range = self + .local_decls + .push(LocalDecl::with_source_info(self.tcx.types.bool, source_info)) + .into(); + + NicheCheckBuilder { + tcx: self.tcx, + local_decls: self.local_decls, + block_data, + new_block, + niche: niche.niche(), + value_in_niche, + is_in_range, + source_info, + } + .insert_niche_check(); + } +} + +#[derive(Clone, Copy, Debug)] +enum NicheKind { + Full(Niche), + // We need the full Size of the type in order to do the transmute-to-MU approach + Partial(Niche, Size), +} + +impl NicheKind { + fn niche(self) -> Niche { + use NicheKind::*; + match self { + Full(niche) => niche, + Partial(niche, _size) => niche, + } + } +} + +fn split_block<'a, 'tcx>( + basic_blocks: &'a mut IndexVec>, + location: Location, +) -> (&'a mut BasicBlockData<'tcx>, BasicBlock) { + let block_data = &mut basic_blocks[location.block]; + + // Drain every statement after this one and move the current terminator to a new basic block + let new_block = BasicBlockData { + statements: block_data.statements.drain(location.statement_index..).collect(), + terminator: block_data.terminator.take(), + is_cleanup: block_data.is_cleanup, + }; + + let new_block = basic_blocks.push(new_block); + let block_data = &mut basic_blocks[location.block]; + + (block_data, new_block) +} + +struct NicheCheckBuilder<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + block_data: &'a mut BasicBlockData<'tcx>, + local_decls: &'a mut IndexVec>, + new_block: BasicBlock, + niche: Niche, + value_in_niche: Place<'tcx>, + is_in_range: Place<'tcx>, + source_info: SourceInfo, +} + +impl<'a, 'tcx> NicheCheckBuilder<'a, 'tcx> { + fn insert_niche_check(&mut self) { + let niche = self.niche; + + let size = niche.size(self.tcx); + + if niche.valid_range.start == 0 { + // The niche starts at 0, so we can just check if it is Le the end + self.check_end_only(); + } else if niche.valid_range.end == (u128::MAX >> (128 - size.bits())) { + // The niche ends at the max, so we can just check if it is Ge the start + self.check_start_only(); + } else { + self.general_case(); + } + + let start = self.niche_const(niche.valid_range.start); + let end = self.niche_const(niche.valid_range.end); + + self.block_data.terminator = Some(Terminator { + source_info: self.source_info, + kind: TerminatorKind::Assert { + cond: Operand::Copy(self.is_in_range), + expected: true, + target: self.new_block, + msg: Box::new(AssertKind::OccupiedNiche { + found: Operand::Copy(self.value_in_niche), + start, + end, + }), + // This calls panic_occupied_niche, 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. + unwind: UnwindAction::Unreachable, + }, + }); + } + + fn niche_const(&self, val: u128) -> Operand<'tcx> { + let niche_ty = self.niche.ty(self.tcx); + if niche_ty.is_any_ptr() { + Operand::Constant(Box::new(ConstOperand { + span: self.source_info.span, + user_ty: None, + const_: Const::Val( + ConstValue::Scalar(Scalar::from_maybe_pointer( + Pointer::from_addr_invalid(val as u64), + &self.tcx, + )), + niche_ty, + ), + })) + } else { + Operand::Constant(Box::new(ConstOperand { + span: self.source_info.span, + user_ty: None, + const_: Const::Val( + ConstValue::Scalar(Scalar::from_uint(val, self.niche.size(self.tcx))), + niche_ty, + ), + })) + } + } + + fn add_assignment(&mut self, place: Place<'tcx>, rvalue: Rvalue<'tcx>) { + self.block_data.statements.push(Statement { + source_info: self.source_info, + kind: StatementKind::Assign(Box::new((place, rvalue))), + }); + } + + fn check_start_only(&mut self) { + let rvalue = if self.niche.valid_range.start == 1 { + let bound = self.niche_const(0); + Rvalue::BinaryOp(BinOp::Ne, Box::new((Operand::Copy(self.value_in_niche), bound))) + } else { + let bound = self.niche_const(self.niche.valid_range.start); + Rvalue::BinaryOp(BinOp::Ge, Box::new((Operand::Copy(self.value_in_niche), bound))) + }; + self.add_assignment(self.is_in_range, rvalue); + } + + fn check_end_only(&mut self) { + let end = self.niche_const(self.niche.valid_range.end); + + let rvalue = + Rvalue::BinaryOp(BinOp::Le, Box::new((Operand::Copy(self.value_in_niche), end))); + self.add_assignment(self.is_in_range, rvalue); + } + + fn general_case(&mut self) { + let mut max = self.niche.valid_range.end.wrapping_sub(self.niche.valid_range.start); + let size = self.niche.size(self.tcx); + if size.bits() < 128 { + let mask = (1 << size.bits()) - 1; + max &= mask; + } + + let start = self.niche_const(self.niche.valid_range.start); + let max_adjusted_allowed_value = self.niche_const(max); + + let biased = self + .local_decls + .push(LocalDecl::with_source_info(self.niche.ty(self.tcx), self.source_info)) + .into(); + let rvalue = + Rvalue::BinaryOp(BinOp::Sub, Box::new((Operand::Copy(self.value_in_niche), start))); + self.add_assignment(biased, rvalue); + + let rvalue = Rvalue::BinaryOp( + BinOp::Le, + Box::new((Operand::Copy(biased), max_adjusted_allowed_value)), + ); + self.add_assignment(self.is_in_range, rvalue); + } +} + +trait NicheExt { + fn ty<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Ty<'tcx>; + fn size<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Size; +} + +impl NicheExt for Niche { + fn ty<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> { + let types = &tcx.types; + match self.value { + Primitive::Int(Integer::I8, _) => types.u8, + Primitive::Int(Integer::I16, _) => types.u16, + Primitive::Int(Integer::I32, _) => types.u32, + Primitive::Int(Integer::I64, _) => types.u64, + Primitive::Int(Integer::I128, _) => types.u128, + Primitive::Pointer(_) => { + Ty::new_ptr(tcx, TypeAndMut { ty: types.unit, mutbl: Mutability::Not }) + } + Primitive::F32 => types.u32, + Primitive::F64 => types.u64, + } + } + + fn size<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Size { + let bits = match self.value { + Primitive::Int(Integer::I8, _) => 8, + Primitive::Int(Integer::I16, _) => 16, + Primitive::Int(Integer::I32, _) => 32, + Primitive::Int(Integer::I64, _) => 64, + Primitive::Int(Integer::I128, _) => 128, + Primitive::Pointer(_) => tcx.sess.target.pointer_width as usize, + Primitive::F32 => 32, + Primitive::F64 => 64, + }; + Size::from_bits(bits) + } +} diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index bf5f0ca7cbd23..8a3f0fb6fbbf9 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -52,6 +52,7 @@ mod add_call_guards; mod add_moves_for_packed_drops; mod add_retag; mod check_const_item_mutation; +mod check_niches; mod check_packed_ref; pub mod check_unsafety; mod remove_place_mention; @@ -321,6 +322,7 @@ fn mir_promoted( tcx: TyCtxt<'_>, def: LocalDefId, ) -> (&Steal>, &Steal>>) { + tcx.ensure_with_value().unsafety_check_result(def); // Ensure that we compute the `mir_const_qualif` for constants at // this point, before we steal the mir-const result. // Also this means promotion can rely on all const checks having been done. @@ -567,6 +569,7 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { body, &[ &check_alignment::CheckAlignment, + &check_niches::CheckNiches, &lower_slice_len::LowerSliceLenCalls, // has to be done before inlining, otherwise actual call will be almost always inlined. Also simple, so can just do first &inline::Inline, // Substitutions during inlining may introduce switch on enums with uninhabited branches. diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 65bdcf1076225..f22910a812269 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -844,6 +844,20 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> { mir::TerminatorKind::Assert { ref msg, .. } => { let lang_item = match &**msg { mir::AssertKind::BoundsCheck { .. } => LangItem::PanicBoundsCheck, + mir::AssertKind::MisalignedPointerDereference { .. } => { + LangItem::PanicMisalignedPointerDereference + } + mir::AssertKind::OccupiedNiche { found, .. } => { + let lang_item = LangItem::PanicOccupiedNiche; + let instance = Instance::new( + tcx.require_lang_item(lang_item, Some(source)), + tcx.mk_args(&[ty::GenericArg::from(found.ty(self.body, tcx))]), + ); + if should_codegen_locally(tcx, &instance) { + self.output.push(create_fn_mono_item(tcx, instance, source)); + } + LangItem::Panic + } _ => LangItem::Panic, }; push_mono_lang_item(self, lang_item); diff --git a/compiler/rustc_smir/src/rustc_smir/mod.rs b/compiler/rustc_smir/src/rustc_smir/mod.rs index 27596c08f1c1e..71ad5cefd19b8 100644 --- a/compiler/rustc_smir/src/rustc_smir/mod.rs +++ b/compiler/rustc_smir/src/rustc_smir/mod.rs @@ -812,6 +812,13 @@ impl<'tcx> Stable<'tcx> for mir::AssertMessage<'tcx> { found: found.stable(tables), } } + AssertKind::OccupiedNiche { found, start, end } => { + stable_mir::mir::AssertMessage::OccupiedNiche { + found: found.stable(tables), + start: start.stable(tables), + end: end.stable(tables), + } + } } } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index f287862cc23c5..47d07617ba20f 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1157,6 +1157,7 @@ symbols! { panic_location, panic_misaligned_pointer_dereference, panic_nounwind, + panic_occupied_niche, panic_runtime, panic_str, panic_unwind, diff --git a/compiler/stable_mir/src/mir/body.rs b/compiler/stable_mir/src/mir/body.rs index 0693378368534..d9f1d04b4d1d6 100644 --- a/compiler/stable_mir/src/mir/body.rs +++ b/compiler/stable_mir/src/mir/body.rs @@ -149,6 +149,7 @@ pub enum AssertMessage { ResumedAfterReturn(CoroutineKind), ResumedAfterPanic(CoroutineKind), MisalignedPointerDereference { required: Operand, found: Operand }, + OccupiedNiche { found: Operand, start: Operand, end: Operand }, } #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/compiler/stable_mir/src/mir/visit.rs b/compiler/stable_mir/src/mir/visit.rs index 806dced71ff3e..636108c739cd5 100644 --- a/compiler/stable_mir/src/mir/visit.rs +++ b/compiler/stable_mir/src/mir/visit.rs @@ -372,6 +372,11 @@ pub trait MirVisitor { self.visit_operand(required, location); self.visit_operand(found, location); } + AssertMessage::OccupiedNiche { found, start, end } => { + self.visit_operand(found, location); + self.visit_operand(start, location); + self.visit_operand(end, location); + } } } } diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs index 39a5e8d9fe2ec..687ee8ce0c18f 100644 --- a/library/core/src/panicking.rs +++ b/library/core/src/panicking.rs @@ -208,6 +208,22 @@ fn panic_misaligned_pointer_dereference(required: usize, found: usize) -> ! { ) } +#[cold] +#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never))] +#[track_caller] +#[cfg_attr(not(bootstrap), lang = "panic_occupied_niche")] // needed by codegen for panic on occupied niches +#[rustc_nounwind] // `CheckNiches` MIR pass requires this function to never unwind +fn panic_occupied_niche(found: T, min: T, max: T) -> ! { + if cfg!(feature = "panic_immediate_abort") { + super::intrinsics::abort() + } + + panic_nounwind_fmt( + format_args!("occupied niche: found {found:?} but must be in {min:?}..={max:?}"), + /* force_no_backtrace */ false, + ) +} + /// Panic because we cannot unwind out of a function. /// /// This is a separate function to avoid the codesize impact of each crate containing the string to diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index b12aae6d4148c..44f5c99ba9128 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -139,5 +139,5 @@ pub const MIRI_DEFAULT_ARGS: &[&str] = &[ "-Zmir-emit-retag", "-Zmir-keep-place-mention", "-Zmir-opt-level=0", - "-Zmir-enable-passes=-CheckAlignment", + "-Zmir-enable-passes=-CheckAlignment,-CheckNiches", ]; diff --git a/tests/codegen/array-equality.rs b/tests/codegen/array-equality.rs index 1941452ea6159..0edd45ecf8d90 100644 --- a/tests/codegen/array-equality.rs +++ b/tests/codegen/array-equality.rs @@ -1,5 +1,6 @@ // compile-flags: -O -Z merge-functions=disabled // only-x86_64 +// ignore-debug: array comparison sometimes transmutes references, so we have niche checks in std #![crate_type = "lib"] diff --git a/tests/codegen/debug-vtable.rs b/tests/codegen/debug-vtable.rs index e52392b260bd4..8bcb28735ade0 100644 --- a/tests/codegen/debug-vtable.rs +++ b/tests/codegen/debug-vtable.rs @@ -6,7 +6,7 @@ // legacy mangling scheme rustc version and generic parameters are both hashed into a single part // of the name, thus randomizing item order with respect to rustc version. -// compile-flags: -Cdebuginfo=2 -Copt-level=0 -Csymbol-mangling-version=v0 +// compile-flags: -Cdebuginfo=2 -Copt-level=0 -Csymbol-mangling-version=v0 -Zmir-enable-passes=-CheckNiches // ignore-tidy-linelength // Make sure that vtables don't have the unnamed_addr attribute when debuginfo is enabled. diff --git a/tests/codegen/intrinsics/transmute-niched.rs b/tests/codegen/intrinsics/transmute-niched.rs index e9c8d803cb9e9..86acb47bc8266 100644 --- a/tests/codegen/intrinsics/transmute-niched.rs +++ b/tests/codegen/intrinsics/transmute-niched.rs @@ -1,6 +1,6 @@ // revisions: OPT DBG // [OPT] compile-flags: -C opt-level=3 -C no-prepopulate-passes -// [DBG] compile-flags: -C opt-level=0 -C no-prepopulate-passes +// [DBG] compile-flags: -C opt-level=0 -C no-prepopulate-passes -Zmir-enable-passes=-CheckNiches // only-64bit (so I don't need to worry about usize) #![crate_type = "lib"] diff --git a/tests/codegen/sanitizer/cfi-emit-type-checks-attr-no-sanitize.rs b/tests/codegen/sanitizer/cfi-emit-type-checks-attr-no-sanitize.rs index a3cd16e3dd5a5..e53ea03574319 100644 --- a/tests/codegen/sanitizer/cfi-emit-type-checks-attr-no-sanitize.rs +++ b/tests/codegen/sanitizer/cfi-emit-type-checks-attr-no-sanitize.rs @@ -1,7 +1,7 @@ // Verifies that pointer type membership tests for indirect calls are omitted. // // needs-sanitizer-cfi -// compile-flags: -Clto -Cno-prepopulate-passes -Ctarget-feature=-crt-static -Zsanitizer=cfi -Copt-level=0 +// compile-flags: -Clto -Cno-prepopulate-passes -Ctarget-feature=-crt-static -Zsanitizer=cfi -Copt-level=0 -Zmir-enable-passes=-CheckNiches #![crate_type="lib"] #![feature(no_sanitize)] diff --git a/tests/codegen/sanitizer/cfi-emit-type-checks.rs b/tests/codegen/sanitizer/cfi-emit-type-checks.rs index f0fe5de9f66c1..567597393f774 100644 --- a/tests/codegen/sanitizer/cfi-emit-type-checks.rs +++ b/tests/codegen/sanitizer/cfi-emit-type-checks.rs @@ -1,7 +1,7 @@ // Verifies that pointer type membership tests for indirect calls are emitted. // // needs-sanitizer-cfi -// compile-flags: -Clto -Cno-prepopulate-passes -Ctarget-feature=-crt-static -Zsanitizer=cfi -Copt-level=0 +// compile-flags: -Clto -Cno-prepopulate-passes -Ctarget-feature=-crt-static -Zsanitizer=cfi -Copt-level=0 -Zmir-enable-passes=-CheckNiches #![crate_type="lib"] diff --git a/tests/codegen/thread-local.rs b/tests/codegen/thread-local.rs index caf0366d2c144..383014d0c3132 100644 --- a/tests/codegen/thread-local.rs +++ b/tests/codegen/thread-local.rs @@ -1,4 +1,5 @@ // compile-flags: -O +// ignore-debug: niche checks in std interfere with the codegen we are looking for // aux-build:thread_local_aux.rs // ignore-windows FIXME(#84933) // ignore-wasm globals are used instead of thread locals diff --git a/tests/codegen/transmute-scalar.rs b/tests/codegen/transmute-scalar.rs index 39126b024a6e9..8b29a06f66969 100644 --- a/tests/codegen/transmute-scalar.rs +++ b/tests/codegen/transmute-scalar.rs @@ -1,4 +1,4 @@ -// compile-flags: -C opt-level=0 -C no-prepopulate-passes +// compile-flags: -C opt-level=0 -C no-prepopulate-passes -Zmir-enable-passes=-CheckNiches #![crate_type = "lib"] diff --git a/tests/mir-opt/remove_storage_markers.rs b/tests/mir-opt/remove_storage_markers.rs index 6666ff3b7263b..a1a4c15c2bd53 100644 --- a/tests/mir-opt/remove_storage_markers.rs +++ b/tests/mir-opt/remove_storage_markers.rs @@ -4,7 +4,7 @@ // Checks that storage markers are removed at opt-level=0. // -// compile-flags: -C opt-level=0 -Coverflow-checks=off +// compile-flags: -C opt-level=0 -Cdebug-assertions=off // EMIT_MIR remove_storage_markers.main.RemoveStorageMarkers.diff fn main() { diff --git a/tests/ui-fulldeps/stable-mir/crate-info.rs b/tests/ui-fulldeps/stable-mir/crate-info.rs index ed6b786f5e1de..692716e337fbc 100644 --- a/tests/ui-fulldeps/stable-mir/crate-info.rs +++ b/tests/ui-fulldeps/stable-mir/crate-info.rs @@ -186,6 +186,7 @@ fn main() { let args = vec![ "rustc".to_string(), "--crate-type=lib".to_string(), + "-Zmir-enable-passes=-CheckNiches".to_string(), "--crate-name".to_string(), CRATE_NAME.to_string(), path.to_string(), diff --git a/tests/ui/lint/large_assignments/box_rc_arc_allowed.rs b/tests/ui/lint/large_assignments/box_rc_arc_allowed.rs index 33113642023a2..99282829dbf29 100644 --- a/tests/ui/lint/large_assignments/box_rc_arc_allowed.rs +++ b/tests/ui/lint/large_assignments/box_rc_arc_allowed.rs @@ -5,7 +5,7 @@ // only-x86_64 // edition:2018 -// compile-flags: -Zmir-opt-level=0 +// compile-flags: -Zmir-opt-level=0 -Zmir-enable-passes=-CheckNiches use std::{sync::Arc, rc::Rc}; diff --git a/tests/ui/mir/check_niches/invalid_bool_transmutes.rs b/tests/ui/mir/check_niches/invalid_bool_transmutes.rs new file mode 100644 index 0000000000000..ee190babfa4a1 --- /dev/null +++ b/tests/ui/mir/check_niches/invalid_bool_transmutes.rs @@ -0,0 +1,10 @@ +// run-fail +// ignore-wasm32-bare: No panic messages +// compile-flags: -C debug-assertions +// error-pattern: occupied niche: found 2 but must be in 0..=1 + +fn main() { + unsafe { + std::mem::transmute::(2); + } +} diff --git a/tests/ui/mir/check_niches/invalid_enums.rs b/tests/ui/mir/check_niches/invalid_enums.rs new file mode 100644 index 0000000000000..a5117f8ba27a2 --- /dev/null +++ b/tests/ui/mir/check_niches/invalid_enums.rs @@ -0,0 +1,23 @@ +// run-fail +// ignore-wasm32-bare: No panic messages +// compile-flags: -C debug-assertions -Zmir-opt-level=0 + +#[repr(C)] +struct Thing { + x: usize, + y: Contents, + z: usize, +} + +#[repr(usize)] +enum Contents { + A = 8usize, + B = 9usize, + C = 10usize, +} + +fn main() { + unsafe { + let _thing = std::mem::transmute::<(usize, usize, usize), Thing>((0, 3, 0)); + } +} diff --git a/tests/ui/mir/check_niches/invalid_nonnull_transmute.rs b/tests/ui/mir/check_niches/invalid_nonnull_transmute.rs new file mode 100644 index 0000000000000..99ed52b81f8d4 --- /dev/null +++ b/tests/ui/mir/check_niches/invalid_nonnull_transmute.rs @@ -0,0 +1,10 @@ +// run-fail +// ignore-wasm32-bare: No panic messages +// compile-flags: -C debug-assertions -Zmir-opt-level=0 +// error-pattern: occupied niche: found 0x0 but must be in 0x1..=0xffffffff + +fn main() { + unsafe { + std::mem::transmute::<*const u8, std::ptr::NonNull>(std::ptr::null()); + } +} diff --git a/tests/ui/mir/check_niches/invalid_nonzero_argument.rs b/tests/ui/mir/check_niches/invalid_nonzero_argument.rs new file mode 100644 index 0000000000000..a5d03013e5ff6 --- /dev/null +++ b/tests/ui/mir/check_niches/invalid_nonzero_argument.rs @@ -0,0 +1,14 @@ +// run-fail +// ignore-wasm32-bare: No panic messages +// compile-flags: -C debug-assertions -Zmir-opt-level=0 +// error-pattern: occupied niche: found 0 but must be in 1..=255 + +fn main() { + let mut bad = std::num::NonZeroU8::new(1u8).unwrap(); + unsafe { + std::ptr::write_bytes(&mut bad, 0u8, 1usize); + } + func(bad); +} + +fn func(_t: T) {} diff --git a/tests/ui/mir/check_niches/valid_bool_transmutes.rs b/tests/ui/mir/check_niches/valid_bool_transmutes.rs new file mode 100644 index 0000000000000..adb67ff7517e9 --- /dev/null +++ b/tests/ui/mir/check_niches/valid_bool_transmutes.rs @@ -0,0 +1,12 @@ +// run-pass +// ignore-wasm32-bare: No panic messages +// compile-flags: -C debug-assertions + +fn main() { + unsafe { + std::mem::transmute::(0); + } + unsafe { + std::mem::transmute::(1); + } +} diff --git a/tests/ui/mir/check_niches/valid_nonzero_argument.rs b/tests/ui/mir/check_niches/valid_nonzero_argument.rs new file mode 100644 index 0000000000000..727d3e0d67c53 --- /dev/null +++ b/tests/ui/mir/check_niches/valid_nonzero_argument.rs @@ -0,0 +1,13 @@ +// run-pass +// compile-flags: -C debug-assertions -Zmir-opt-level=0 + +fn main() { + for val in i8::MIN..=i8::MAX { + if val != 0 { + let x = std::num::NonZeroI8::new(val).unwrap(); + if val != i8::MIN { + let _y = -x; + } + } + } +} diff --git a/tests/ui/numbers-arithmetic/saturating-float-casts-wasm.rs b/tests/ui/numbers-arithmetic/saturating-float-casts-wasm.rs index cad05917391be..7557e5ce34bb0 100644 --- a/tests/ui/numbers-arithmetic/saturating-float-casts-wasm.rs +++ b/tests/ui/numbers-arithmetic/saturating-float-casts-wasm.rs @@ -1,6 +1,6 @@ // run-pass // only-wasm32 -// compile-flags: -Zmir-opt-level=0 -C target-feature=+nontrapping-fptoint +// compile-flags: -Zmir-opt-level=0 -C target-feature=+nontrapping-fptoint -Zmir-enable-passes=-CheckNiches #![feature(test, stmt_expr_attributes)] #![deny(overflowing_literals)] diff --git a/tests/ui/numbers-arithmetic/saturating-float-casts.rs b/tests/ui/numbers-arithmetic/saturating-float-casts.rs index cc248a9bea087..d26273bc8345f 100644 --- a/tests/ui/numbers-arithmetic/saturating-float-casts.rs +++ b/tests/ui/numbers-arithmetic/saturating-float-casts.rs @@ -1,5 +1,5 @@ // run-pass -// compile-flags:-Zmir-opt-level=0 +// compile-flags:-Zmir-opt-level=0 -Zmir-enable-passes=-CheckNiches #![feature(test, stmt_expr_attributes)] #![deny(overflowing_literals)] diff --git a/tests/ui/print_type_sizes/niche-filling.rs b/tests/ui/print_type_sizes/niche-filling.rs index 5e620f248b65d..82afbd2495706 100644 --- a/tests/ui/print_type_sizes/niche-filling.rs +++ b/tests/ui/print_type_sizes/niche-filling.rs @@ -1,4 +1,4 @@ -// compile-flags: -Z print-type-sizes --crate-type=lib +// compile-flags: -Z print-type-sizes --crate-type=lib -Zmir-enable-passes=-CheckNiches // build-pass // ignore-pass // ^-- needed because `--pass check` does not emit the output needed. diff --git a/tests/ui/type-alias-enum-variants/self-in-enum-definition.rs b/tests/ui/type-alias-enum-variants/self-in-enum-definition.rs index 8dadd77fc16d5..9091b3ba6b8aa 100644 --- a/tests/ui/type-alias-enum-variants/self-in-enum-definition.rs +++ b/tests/ui/type-alias-enum-variants/self-in-enum-definition.rs @@ -1,3 +1,5 @@ +// compile-flags: -Cdebug-assertions=no + #[repr(u8)] enum Alpha { V1 = 41, diff --git a/tests/ui/type-alias-enum-variants/self-in-enum-definition.stderr b/tests/ui/type-alias-enum-variants/self-in-enum-definition.stderr index aa79b1a57c4c8..5c9d75fffdc50 100644 --- a/tests/ui/type-alias-enum-variants/self-in-enum-definition.stderr +++ b/tests/ui/type-alias-enum-variants/self-in-enum-definition.stderr @@ -1,63 +1,53 @@ error[E0391]: cycle detected when simplifying constant for the type system `Alpha::V3::{constant#0}` - --> $DIR/self-in-enum-definition.rs:5:10 + --> $DIR/self-in-enum-definition.rs:7:10 | LL | V3 = Self::V1 {} as u8 + 2, | ^^^^^^^^^^^^^^^^^^^^^ | note: ...which requires simplifying constant for the type system `Alpha::V3::{constant#0}`... - --> $DIR/self-in-enum-definition.rs:5:10 + --> $DIR/self-in-enum-definition.rs:7:10 | LL | V3 = Self::V1 {} as u8 + 2, | ^^^^^^^^^^^^^^^^^^^^^ note: ...which requires const-evaluating + checking `Alpha::V3::{constant#0}`... - --> $DIR/self-in-enum-definition.rs:5:10 + --> $DIR/self-in-enum-definition.rs:7:10 | LL | V3 = Self::V1 {} as u8 + 2, | ^^^^^^^^^^^^^^^^^^^^^ note: ...which requires caching mir of `Alpha::V3::{constant#0}` for CTFE... - --> $DIR/self-in-enum-definition.rs:5:10 + --> $DIR/self-in-enum-definition.rs:7:10 | LL | V3 = Self::V1 {} as u8 + 2, | ^^^^^^^^^^^^^^^^^^^^^ note: ...which requires elaborating drops for `Alpha::V3::{constant#0}`... - --> $DIR/self-in-enum-definition.rs:5:10 + --> $DIR/self-in-enum-definition.rs:7:10 | LL | V3 = Self::V1 {} as u8 + 2, | ^^^^^^^^^^^^^^^^^^^^^ note: ...which requires borrow-checking `Alpha::V3::{constant#0}`... - --> $DIR/self-in-enum-definition.rs:5:10 + --> $DIR/self-in-enum-definition.rs:7:10 | LL | V3 = Self::V1 {} as u8 + 2, | ^^^^^^^^^^^^^^^^^^^^^ note: ...which requires promoting constants in MIR for `Alpha::V3::{constant#0}`... - --> $DIR/self-in-enum-definition.rs:5:10 - | -LL | V3 = Self::V1 {} as u8 + 2, - | ^^^^^^^^^^^^^^^^^^^^^ -note: ...which requires const checking `Alpha::V3::{constant#0}`... - --> $DIR/self-in-enum-definition.rs:5:10 - | -LL | V3 = Self::V1 {} as u8 + 2, - | ^^^^^^^^^^^^^^^^^^^^^ -note: ...which requires preparing `Alpha::V3::{constant#0}` for borrow checking... - --> $DIR/self-in-enum-definition.rs:5:10 + --> $DIR/self-in-enum-definition.rs:7:10 | LL | V3 = Self::V1 {} as u8 + 2, | ^^^^^^^^^^^^^^^^^^^^^ note: ...which requires unsafety-checking `Alpha::V3::{constant#0}`... - --> $DIR/self-in-enum-definition.rs:5:10 + --> $DIR/self-in-enum-definition.rs:7:10 | LL | V3 = Self::V1 {} as u8 + 2, | ^^^^^^^^^^^^^^^^^^^^^ note: ...which requires building MIR for `Alpha::V3::{constant#0}`... - --> $DIR/self-in-enum-definition.rs:5:10 + --> $DIR/self-in-enum-definition.rs:7:10 | LL | V3 = Self::V1 {} as u8 + 2, | ^^^^^^^^^^^^^^^^^^^^^ = note: ...which requires computing layout of `Alpha`... = note: ...which again requires simplifying constant for the type system `Alpha::V3::{constant#0}`, completing the cycle note: cycle used when collecting item types in top-level module - --> $DIR/self-in-enum-definition.rs:1:1 + --> $DIR/self-in-enum-definition.rs:3:1 | LL | / #[repr(u8)] LL | | enum Alpha {