From 714fd639fffdd22e37b11219400638ce5d438deb Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Wed, 15 Oct 2025 02:27:37 +0000 Subject: [PATCH 1/5] Use delay_span_bug in validate-mir. --- compiler/rustc_mir_transform/src/validate.rs | 27 ++++++++++--------- tests/crashes/140850.rs | 2 +- .../coroutine/non-send-dyn-send-ice.rs} | 6 +++-- .../ui/coroutine/non-send-dyn-send-ice.stderr | 23 ++++++++++++++++ .../branch-closure-parameter.rs} | 5 ++-- .../branch-closure-parameter.stderr | 15 +++++++++++ 6 files changed, 61 insertions(+), 17 deletions(-) rename tests/{crashes/137916.rs => ui/coroutine/non-send-dyn-send-ice.rs} (54%) create mode 100644 tests/ui/coroutine/non-send-dyn-send-ice.stderr rename tests/{crashes/126680.rs => ui/type-alias-impl-trait/branch-closure-parameter.rs} (80%) create mode 100644 tests/ui/type-alias-impl-trait/branch-closure-parameter.stderr diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index 1afdb4639a0ce..cc5a07b7b158a 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -11,12 +11,12 @@ use rustc_infer::traits::{Obligation, ObligationCause}; use rustc_middle::mir::coverage::CoverageKind; use rustc_middle::mir::visit::{MutatingUseContext, NonUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; +use rustc_middle::span_bug; use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{ self, CoroutineArgsExt, InstanceKind, ScalarInt, Ty, TyCtxt, TypeVisitableExt, Upcast, Variance, }; -use rustc_middle::{bug, span_bug}; use rustc_mir_dataflow::debuginfo::debuginfo_locals; use rustc_trait_selection::traits::ObligationCtxt; @@ -122,18 +122,17 @@ struct CfgChecker<'a, 'tcx> { impl<'a, 'tcx> CfgChecker<'a, 'tcx> { #[track_caller] - fn fail(&self, location: Location, msg: impl AsRef) { + fn fail(&self, location: Location, msg: impl std::fmt::Display) { // We might see broken MIR when other errors have already occurred. - if self.tcx.dcx().has_errors().is_none() { - span_bug!( - self.body.source_info(location).span, + // But we may have some cases of errors happening *after* MIR construction, + // for instance because of generic constants or coroutines. + self.tcx.dcx().span_delayed_bug( + self.body.source_info(location).span, + format!( "broken MIR in {:?} ({}) at {:?}:\n{}", - self.body.source.instance, - self.when, - location, - msg.as_ref(), - ); - } + self.body.source.instance, self.when, location, msg, + ), + ); } fn check_edge(&mut self, location: Location, bb: BasicBlock, edge_kind: EdgeKind) { @@ -1597,7 +1596,11 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ty::Int(int) => int.normalize(target_width).bit_width().unwrap(), ty::Char => 32, ty::Bool => 1, - other => bug!("unhandled type: {:?}", other), + other => { + self.fail(location, format!("unhandled type in SwitchInt {other:?}")); + // Magic number to avoid ICEing. + 1 + } }); for (value, _) in targets.iter() { diff --git a/tests/crashes/140850.rs b/tests/crashes/140850.rs index fd26097deda00..92ee389facdbf 100644 --- a/tests/crashes/140850.rs +++ b/tests/crashes/140850.rs @@ -1,6 +1,6 @@ //@ known-bug: #140850 //@ compile-flags: -Zvalidate-mir -fn A() -> impl { +fn A() -> impl Copy { while A() {} loop {} } diff --git a/tests/crashes/137916.rs b/tests/ui/coroutine/non-send-dyn-send-ice.rs similarity index 54% rename from tests/crashes/137916.rs rename to tests/ui/coroutine/non-send-dyn-send-ice.rs index b25e7b200d959..275f7dc3430f1 100644 --- a/tests/crashes/137916.rs +++ b/tests/ui/coroutine/non-send-dyn-send-ice.rs @@ -1,9 +1,11 @@ -//@ known-bug: #137916 +//! Regression test for ICE #137916 //@ edition: 2021 +//@ compile-flags: -Zvalidate-mir + use std::ptr::null; async fn a() -> Box { - Box::new(async { + Box::new(async { //~ ERROR future cannot be sent between threads safely let non_send = null::<()>(); &non_send; async {}.await diff --git a/tests/ui/coroutine/non-send-dyn-send-ice.stderr b/tests/ui/coroutine/non-send-dyn-send-ice.stderr new file mode 100644 index 0000000000000..eeb72fa1bc774 --- /dev/null +++ b/tests/ui/coroutine/non-send-dyn-send-ice.stderr @@ -0,0 +1,23 @@ +error: future cannot be sent between threads safely + --> $DIR/non-send-dyn-send-ice.rs:8:5 + | +LL | / Box::new(async { +LL | | let non_send = null::<()>(); +LL | | &non_send; +LL | | async {}.await +LL | | }) + | |______^ future created by async block is not `Send` + | + = help: within `{async block@$DIR/non-send-dyn-send-ice.rs:8:14: 8:19}`, the trait `Send` is not implemented for `*const ()` +note: future is not `Send` as this value is used across an await + --> $DIR/non-send-dyn-send-ice.rs:11:18 + | +LL | let non_send = null::<()>(); + | -------- has type `*const ()` which is not `Send` +LL | &non_send; +LL | async {}.await + | ^^^^^ await occurs here, with `non_send` maybe used later + = note: required for the cast from `Box<{async block@$DIR/non-send-dyn-send-ice.rs:8:14: 8:19}>` to `Box` + +error: aborting due to 1 previous error + diff --git a/tests/crashes/126680.rs b/tests/ui/type-alias-impl-trait/branch-closure-parameter.rs similarity index 80% rename from tests/crashes/126680.rs rename to tests/ui/type-alias-impl-trait/branch-closure-parameter.rs index dcb6ccad6b422..18ca7e01ce562 100644 --- a/tests/crashes/126680.rs +++ b/tests/ui/type-alias-impl-trait/branch-closure-parameter.rs @@ -1,5 +1,6 @@ -//@ known-bug: rust-lang/rust#126680 +//! Regression test for #126680 //@ compile-flags: -Zvalidate-mir + #![feature(type_alias_impl_trait)] type Bar = impl std::fmt::Display; @@ -10,7 +11,7 @@ struct A { } #[define_opaque(Bar)] -fn foo() -> A { +fn foo() -> A { //~ ERROR item does not constrain `Bar::{opaque#0}` A { func: |check, b| { if check { diff --git a/tests/ui/type-alias-impl-trait/branch-closure-parameter.stderr b/tests/ui/type-alias-impl-trait/branch-closure-parameter.stderr new file mode 100644 index 0000000000000..aa37270c2f58e --- /dev/null +++ b/tests/ui/type-alias-impl-trait/branch-closure-parameter.stderr @@ -0,0 +1,15 @@ +error: item does not constrain `Bar::{opaque#0}` + --> $DIR/branch-closure-parameter.rs:14:4 + | +LL | fn foo() -> A { + | ^^^ + | + = note: consider removing `#[define_opaque]` or adding an empty `#[define_opaque()]` +note: this opaque type is supposed to be constrained + --> $DIR/branch-closure-parameter.rs:5:12 + | +LL | type Bar = impl std::fmt::Display; + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From 234765cad7e025051ebc73013fbbc1a3cb5b66ae Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Tue, 11 Nov 2025 20:12:52 +0000 Subject: [PATCH 2/5] Remove test made obsolete by MCP#838. --- tests/crashes/project-to-simd-array-field.rs | 33 -------------------- 1 file changed, 33 deletions(-) delete mode 100644 tests/crashes/project-to-simd-array-field.rs diff --git a/tests/crashes/project-to-simd-array-field.rs b/tests/crashes/project-to-simd-array-field.rs deleted file mode 100644 index 6dc916c41dbd5..0000000000000 --- a/tests/crashes/project-to-simd-array-field.rs +++ /dev/null @@ -1,33 +0,0 @@ -//@ known-bug: #137108 -//@compile-flags: -Copt-level=3 - -// If you fix this, put it in the corresponding codegen test, -// not in a UI test like the readme says. - -#![crate_type = "lib"] - -#![feature(repr_simd, core_intrinsics)] - -#[allow(non_camel_case_types)] -#[derive(Clone, Copy)] -#[repr(simd)] -struct i32x3([i32; 3]); - -const _: () = { assert!(size_of::() == 16) }; - -#[inline(always)] -fn to_array3(a: i32x3) -> [i32; 3] { - a.0 -} - -// CHECK-LABEL: simd_add_self_then_return_array_packed( -// CHECK-SAME: ptr{{.+}}sret{{.+}}%[[RET:.+]], -// CHECK-SAME: ptr{{.+}}%a) -#[no_mangle] -pub fn simd_add_self_then_return_array_packed(a: i32x3) -> [i32; 3] { - // CHECK: %[[T1:.+]] = load <3 x i32>, ptr %a - // CHECK: %[[T2:.+]] = shl <3 x i32> %[[T1]], - // CHECK: store <3 x i32> %[[T2]], ptr %[[RET]] - let b = unsafe { core::intrinsics::simd::simd_add(a, a) }; - to_array3(b) -} From b40d14a69ee2ebecc11f0447c34736356d9ac268 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Tue, 11 Nov 2025 22:11:58 +0000 Subject: [PATCH 3/5] Flush delayed bugs when exiting miri. --- src/tools/miri/src/bin/miri.rs | 7 ++++++ .../miri/tests/panic/mir-validation.stderr | 25 ++++++++----------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 19fbf90246c93..6e996986fd52b 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -373,6 +373,13 @@ impl rustc_driver::Callbacks for MiriDepCompilerCalls { } fn exit(exit_code: i32) -> ! { + // MIR validation uses delayed bugs. Flush them as we won't return for `run_compiler` to do it. + ty::tls::with_opt(|opt_tcx| { + if let Some(tcx) = opt_tcx { + tcx.dcx().flush_delayed() + } + }); + // Drop the tracing guard before exiting, so tracing calls are flushed correctly. deinit_loggers(); // Make sure the supervisor knows about the exit code. diff --git a/src/tools/miri/tests/panic/mir-validation.stderr b/src/tools/miri/tests/panic/mir-validation.stderr index 1d40c93d709e6..700b4995f74f8 100644 --- a/src/tools/miri/tests/panic/mir-validation.stderr +++ b/src/tools/miri/tests/panic/mir-validation.stderr @@ -1,28 +1,23 @@ -error: internal compiler error: compiler/rustc_mir_transform/src/validate.rs:LL:CC: broken MIR in Item(DefId) (after phase change to runtime-optimized) at bb0[1]: +note: no errors encountered even though delayed bugs were created + + +error: internal compiler error: broken MIR in Item(DefId) (after phase change to runtime-optimized) at bb0[1]: place (*(_2.0: *mut i32)) has deref as a later projection (it is only permitted as the first projection) --> tests/panic/mir-validation.rs:LL:CC | LL | *(tuple.0) = 1; | ^^^^^^^^^^^^^^ - - -thread 'rustc' ($TID) panicked at compiler/rustc_mir_transform/src/validate.rs:LL:CC: -Box -stack backtrace: + | + + --> tests/panic/mir-validation.rs:LL:CC + | +LL | *(tuple.0) = 1; + | ^^^^^^^^^^^^^^ query stack during panic: -#0 [optimized_mir] optimizing MIR for `main` end of query stack - -Miri caused an ICE during evaluation. Here's the interpreter backtrace at the time of the panic: - --> RUSTLIB/core/src/ops/function.rs:LL:CC - | -LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - error: aborting due to 1 previous error From 601ebc82e5922612e2e302242193240370be5450 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Sun, 16 Nov 2025 21:51:50 +0000 Subject: [PATCH 4/5] Only delay bugs from type checker. --- compiler/rustc_mir_transform/src/validate.rs | 22 ++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index cc5a07b7b158a..dd6034392e6c3 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -78,7 +78,16 @@ impl<'tcx> crate::MirPass<'tcx> for Validator { // Also run the TypeChecker. for (location, msg) in validate_types(tcx, typing_env, body, body) { - cfg_checker.fail(location, msg); + // We might see broken MIR when other errors have already occurred. + // But we may have some cases of errors happening *after* MIR construction, + // for instance because of generic constants or coroutines. + tcx.dcx().span_delayed_bug( + body.source_info(location).span, + format!( + "broken MIR in {:?} ({}) at {:?}:\n{}", + body.source.instance, self.when, location, msg, + ), + ); } // Ensure that debuginfo records are not emitted for locals that are not in debuginfo. @@ -126,12 +135,13 @@ impl<'a, 'tcx> CfgChecker<'a, 'tcx> { // We might see broken MIR when other errors have already occurred. // But we may have some cases of errors happening *after* MIR construction, // for instance because of generic constants or coroutines. - self.tcx.dcx().span_delayed_bug( + span_bug!( self.body.source_info(location).span, - format!( - "broken MIR in {:?} ({}) at {:?}:\n{}", - self.body.source.instance, self.when, location, msg, - ), + "broken MIR in {:?} ({}) at {:?}:\n{}", + self.body.source.instance, + self.when, + location, + msg, ); } From 99b20385fdaf347ff2663818b886738d42d2b69f Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Sun, 16 Nov 2025 21:52:16 +0000 Subject: [PATCH 5/5] Move checks from cfg and type checker. --- compiler/rustc_mir_transform/src/validate.rs | 530 +++++++++++-------- 1 file changed, 299 insertions(+), 231 deletions(-) diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index dd6034392e6c3..7c93e81915f69 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -305,8 +305,220 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { } } + fn visit_projection_elem( + &mut self, + place_ref: PlaceRef<'tcx>, + elem: PlaceElem<'tcx>, + context: PlaceContext, + location: Location, + ) { + match elem { + ProjectionElem::ConstantIndex { offset, min_length, from_end } => { + if from_end { + if offset > min_length { + self.fail( + location, + format!( + "constant index with offset -{offset} out of bounds of min length {min_length}" + ), + ); + } + } else { + if offset >= min_length { + self.fail( + location, + format!( + "constant index with offset {offset} out of bounds of min length {min_length}" + ), + ); + } + } + } + ProjectionElem::Subslice { from, to, from_end } => { + if !from_end && from > to { + self.fail(location, "backwards subslice {from}..{to}"); + } + } + ProjectionElem::OpaqueCast(ty) => { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail( + location, + format!( + "explicit opaque type cast to `{ty}` after `PostAnalysisNormalize`" + ), + ) + } + } + _ => {} + } + self.super_projection_elem(place_ref, elem, context, location); + } + + fn visit_place(&mut self, place: &Place<'tcx>, cntxt: PlaceContext, location: Location) { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) + && place.projection.len() > 1 + && cntxt != PlaceContext::NonUse(NonUseContext::VarDebugInfo) + && place.projection[1..].contains(&ProjectionElem::Deref) + { + self.fail( + location, + format!("place {place:?} has deref as a later projection (it is only permitted as the first projection)"), + ); + } + + // Ensure all downcast projections are followed by field projections. + let mut projections_iter = place.projection.iter(); + while let Some(proj) = projections_iter.next() { + if matches!(proj, ProjectionElem::Downcast(..)) { + if !matches!(projections_iter.next(), Some(ProjectionElem::Field(..))) { + self.fail( + location, + format!( + "place {place:?} has `Downcast` projection not followed by `Field`" + ), + ); + } + } + } + + if let ClearCrossCrate::Set(box LocalInfo::DerefTemp) = + self.body.local_decls[place.local].local_info + && !place.is_indirect_first_projection() + { + if cntxt != PlaceContext::MutatingUse(MutatingUseContext::Store) + || place.as_local().is_none() + { + self.fail( + location, + format!("`DerefTemp` locals must only be dereferenced or directly assigned to"), + ); + } + } + + if self.body.phase < MirPhase::Runtime(RuntimePhase::Initial) + && let Some(i) = place + .projection + .iter() + .position(|elem| matches!(elem, ProjectionElem::Subslice { .. })) + && let Some(tail) = place.projection.get(i + 1..) + && tail.iter().any(|elem| { + matches!( + elem, + ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } + ) + }) + { + self.fail( + location, + format!("place {place:?} has `ConstantIndex` or `Subslice` after `Subslice`"), + ); + } + + self.super_place(place, cntxt, location); + } + + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + match rvalue { + Rvalue::Use(_) => {} + Rvalue::CopyForDeref(_) => { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail(location, "`CopyForDeref` should have been removed in runtime MIR"); + } + } + Rvalue::Aggregate(kind, fields) => match **kind { + AggregateKind::Tuple => {} + AggregateKind::Array(_) => {} + AggregateKind::Adt(def_id, _, _, _, Some(_)) => { + let adt_def = self.tcx.adt_def(def_id); + assert!(adt_def.is_union()); + if fields.len() != 1 { + self.fail(location, "unions should have one initialized field"); + } + } + AggregateKind::Adt(def_id, idx, _, _, None) => { + let adt_def = self.tcx.adt_def(def_id); + assert!(!adt_def.is_union()); + let variant = &adt_def.variants()[idx]; + if variant.fields.len() != fields.len() { + self.fail(location, format!( + "adt {def_id:?} has the wrong number of initialized fields, expected {}, found {}", + fields.len(), + variant.fields.len(), + )); + } + } + AggregateKind::Closure(_, args) => { + let upvars = args.as_closure().upvar_tys(); + if upvars.len() != fields.len() { + self.fail(location, "closure has the wrong number of initialized fields"); + } + } + AggregateKind::Coroutine(_, args) => { + let upvars = args.as_coroutine().upvar_tys(); + if upvars.len() != fields.len() { + self.fail(location, "coroutine has the wrong number of initialized fields"); + } + } + AggregateKind::CoroutineClosure(_, args) => { + let upvars = args.as_coroutine_closure().upvar_tys(); + if upvars.len() != fields.len() { + self.fail( + location, + "coroutine-closure has the wrong number of initialized fields", + ); + } + } + AggregateKind::RawPtr(..) => { + if !matches!(self.body.phase, MirPhase::Runtime(_)) { + // It would probably be fine to support this in earlier phases, but at the + // time of writing it's only ever introduced from intrinsic lowering, so + // earlier things just `bug!` on it. + self.fail(location, "RawPtr should be in runtime MIR only"); + } + + if fields.len() != 2 { + self.fail(location, "raw pointer aggregate must have 2 fields"); + } + } + }, + Rvalue::Ref(_, BorrowKind::Fake(_), _) => { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail( + location, + "`Assign` statement with a `Fake` borrow should have been removed in runtime MIR", + ); + } + } + Rvalue::Ref(..) => {} + Rvalue::BinaryOp(..) => {} + Rvalue::UnaryOp(..) => {} + Rvalue::ShallowInitBox(..) => { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail(location, format!("ShallowInitBox after ElaborateBoxDerefs")) + } + } + Rvalue::Cast(..) => {} + Rvalue::Repeat(_, _) + | Rvalue::ThreadLocalRef(_) + | Rvalue::RawPtr(_, _) + | Rvalue::Discriminant(_) => {} + + Rvalue::WrapUnsafeBinder(..) => {} + } + self.super_rvalue(rvalue, location); + } + fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { match &statement.kind { + StatementKind::Assign(box (dest, rvalue)) => { + if let Some(local) = dest.as_local() + && let ClearCrossCrate::Set(box LocalInfo::DerefTemp) = + self.body.local_decls[local].local_info + && !matches!(rvalue, Rvalue::CopyForDeref(_)) + { + self.fail(location, "assignment to a `DerefTemp` must use `CopyForDeref`") + } + } StatementKind::AscribeUserType(..) => { if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { self.fail( @@ -346,8 +558,7 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { ); } } - StatementKind::Assign(..) - | StatementKind::StorageLive(_) + StatementKind::StorageLive(_) | StatementKind::StorageDead(_) | StatementKind::Intrinsic(_) | StatementKind::ConstEvalCounter @@ -390,72 +601,32 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { self.check_edge(location, *drop, EdgeKind::Normal); } } - TerminatorKind::Call { func, args, .. } - | TerminatorKind::TailCall { func, args, .. } => { - // FIXME(explicit_tail_calls): refactor this & add tail-call specific checks - if let TerminatorKind::Call { target, unwind, destination, .. } = terminator.kind { - if let Some(target) = target { - self.check_edge(location, target, EdgeKind::Normal); - } - self.check_unwind_edge(location, unwind); - - // The code generation assumes that there are no critical call edges. The - // assumption is used to simplify inserting code that should be executed along - // the return edge from the call. FIXME(tmiasko): Since this is a strictly code - // generation concern, the code generation should be responsible for handling - // it. - if self.body.phase >= MirPhase::Runtime(RuntimePhase::Optimized) - && self.is_critical_call_edge(target, unwind) - { - self.fail( - location, - format!( - "encountered critical edge in `Call` terminator {:?}", - terminator.kind, - ), - ); - } - - // The call destination place and Operand::Move place used as an argument might - // be passed by a reference to the callee. Consequently they cannot be packed. - if most_packed_projection(self.tcx, &self.body.local_decls, destination) - .is_some() - { - // This is bad! The callee will expect the memory to be aligned. - self.fail( - location, - format!( - "encountered packed place in `Call` terminator destination: {:?}", - terminator.kind, - ), - ); - } - } - - for arg in args { - if let Operand::Move(place) = &arg.node { - if most_packed_projection(self.tcx, &self.body.local_decls, *place) - .is_some() - { - // This is bad! The callee will expect the memory to be aligned. - self.fail( - location, - format!( - "encountered `Move` of a packed place in `Call` terminator: {:?}", - terminator.kind, - ), - ); - } - } + TerminatorKind::Call { target, unwind, .. } => { + if let Some(target) = *target { + self.check_edge(location, target, EdgeKind::Normal); } + self.check_unwind_edge(location, *unwind); - if let ty::FnDef(did, ..) = func.ty(&self.body.local_decls, self.tcx).kind() - && self.body.phase >= MirPhase::Runtime(RuntimePhase::Optimized) - && matches!(self.tcx.codegen_fn_attrs(did).inline, InlineAttr::Force { .. }) + // The code generation assumes that there are no critical call edges. The + // assumption is used to simplify inserting code that should be executed along + // the return edge from the call. FIXME(tmiasko): Since this is a strictly code + // generation concern, the code generation should be responsible for handling + // it. + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Optimized) + && self.is_critical_call_edge(*target, *unwind) { - self.fail(location, "`#[rustc_force_inline]`-annotated function not inlined"); + self.fail( + location, + format!( + "encountered critical edge in `Call` terminator {:?}", + terminator.kind, + ), + ); } } + TerminatorKind::TailCall { .. } => { + // FIXME(explicit_tail_calls): refactor this & add tail-call specific checks + } TerminatorKind::Assert { target, unwind, .. } => { self.check_edge(location, *target, EdgeKind::Normal); self.check_unwind_edge(location, *unwind); @@ -536,6 +707,19 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { self.super_terminator(terminator, location); } + fn visit_local_decl(&mut self, local: Local, local_decl: &LocalDecl<'tcx>) { + if let ClearCrossCrate::Set(box LocalInfo::DerefTemp) = local_decl.local_info { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail( + START_BLOCK.start_location(), + "`DerefTemp` should have been removed in runtime MIR", + ); + } + } + + self.super_local_decl(local, local_decl); + } + fn visit_source_scope(&mut self, scope: SourceScope) { if self.body.source_scopes.get(scope).is_none() { self.tcx.dcx().span_bug( @@ -828,7 +1012,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { self.fail(location, format!("bad index ({index_ty} != usize)")) } } - ProjectionElem::ConstantIndex { offset, min_length, from_end } => { + ProjectionElem::ConstantIndex { from_end, .. } => { let indexed_ty = place_ref.ty(&self.body.local_decls, self.tcx).ty; match indexed_ty.kind() { ty::Array(_, _) => { @@ -839,28 +1023,8 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ty::Slice(_) => {} _ => self.fail(location, format!("{indexed_ty:?} cannot be indexed")), } - - if from_end { - if offset > min_length { - self.fail( - location, - format!( - "constant index with offset -{offset} out of bounds of min length {min_length}" - ), - ); - } - } else { - if offset >= min_length { - self.fail( - location, - format!( - "constant index with offset {offset} out of bounds of min length {min_length}" - ), - ); - } - } } - ProjectionElem::Subslice { from, to, from_end } => { + ProjectionElem::Subslice { from_end, .. } => { let indexed_ty = place_ref.ty(&self.body.local_decls, self.tcx).ty; match indexed_ty.kind() { ty::Array(_, _) => { @@ -875,18 +1039,6 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } _ => self.fail(location, format!("{indexed_ty:?} cannot be indexed")), } - - if !from_end && from > to { - self.fail(location, "backwards subslice {from}..{to}"); - } - } - ProjectionElem::OpaqueCast(ty) - if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) => - { - self.fail( - location, - format!("explicit opaque type cast to `{ty}` after `PostAnalysisNormalize`"), - ) } ProjectionElem::UnwrapUnsafeBinder(unwrapped_ty) => { let binder_ty = place_ref.ty(&self.body.local_decls, self.tcx); @@ -953,66 +1105,6 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { fn visit_place(&mut self, place: &Place<'tcx>, cntxt: PlaceContext, location: Location) { // Set off any `bug!`s in the type computation code let _ = place.ty(&self.body.local_decls, self.tcx); - - if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) - && place.projection.len() > 1 - && cntxt != PlaceContext::NonUse(NonUseContext::VarDebugInfo) - && place.projection[1..].contains(&ProjectionElem::Deref) - { - self.fail( - location, - format!("place {place:?} has deref as a later projection (it is only permitted as the first projection)"), - ); - } - - // Ensure all downcast projections are followed by field projections. - let mut projections_iter = place.projection.iter(); - while let Some(proj) = projections_iter.next() { - if matches!(proj, ProjectionElem::Downcast(..)) { - if !matches!(projections_iter.next(), Some(ProjectionElem::Field(..))) { - self.fail( - location, - format!( - "place {place:?} has `Downcast` projection not followed by `Field`" - ), - ); - } - } - } - - if let ClearCrossCrate::Set(box LocalInfo::DerefTemp) = - self.body.local_decls[place.local].local_info - && !place.is_indirect_first_projection() - { - if cntxt != PlaceContext::MutatingUse(MutatingUseContext::Store) - || place.as_local().is_none() - { - self.fail( - location, - format!("`DerefTemp` locals must only be dereferenced or directly assigned to"), - ); - } - } - - if self.body.phase < MirPhase::Runtime(RuntimePhase::Initial) - && let Some(i) = place - .projection - .iter() - .position(|elem| matches!(elem, ProjectionElem::Subslice { .. })) - && let Some(tail) = place.projection.get(i + 1..) - && tail.iter().any(|elem| { - matches!( - elem, - ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } - ) - }) - { - self.fail( - location, - format!("place {place:?} has `ConstantIndex` or `Subslice` after `Subslice`"), - ); - } - self.super_place(place, cntxt, location); } @@ -1026,11 +1118,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } match rvalue { Rvalue::Use(_) => {} - Rvalue::CopyForDeref(_) => { - if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { - self.fail(location, "`CopyForDeref` should have been removed in runtime MIR"); - } - } + Rvalue::CopyForDeref(_) => {} Rvalue::Aggregate(kind, fields) => match **kind { AggregateKind::Tuple => {} AggregateKind::Array(dest) => { @@ -1061,13 +1149,6 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { let adt_def = self.tcx.adt_def(def_id); assert!(!adt_def.is_union()); let variant = &adt_def.variants()[idx]; - if variant.fields.len() != fields.len() { - self.fail(location, format!( - "adt {def_id:?} has the wrong number of initialized fields, expected {}, found {}", - fields.len(), - variant.fields.len(), - )); - } for (src, dest) in std::iter::zip(fields, &variant.fields) { let dest_ty = self .tcx @@ -1114,13 +1195,6 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } AggregateKind::RawPtr(pointee_ty, mutability) => { - if !matches!(self.body.phase, MirPhase::Runtime(_)) { - // It would probably be fine to support this in earlier phases, but at the - // time of writing it's only ever introduced from intrinsic lowering, so - // earlier things just `bug!` on it. - self.fail(location, "RawPtr should be in runtime MIR only"); - } - if let [data_ptr, metadata] = fields.raw.as_slice() { let data_ptr_ty = data_ptr.ty(self.body, self.tcx); let metadata_ty = metadata.ty(self.body, self.tcx); @@ -1155,14 +1229,6 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } }, - Rvalue::Ref(_, BorrowKind::Fake(_), _) => { - if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { - self.fail( - location, - "`Assign` statement with a `Fake` borrow should have been removed in runtime MIR", - ); - } - } Rvalue::Ref(..) => {} Rvalue::BinaryOp(op, vals) => { use BinOp::*; @@ -1269,10 +1335,6 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } Rvalue::ShallowInitBox(operand, _) => { - if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { - self.fail(location, format!("ShallowInitBox after ElaborateBoxDerefs")) - } - let a = operand.ty(&self.body.local_decls, self.tcx); check_kinds!(a, "Cannot shallow init type {:?}", ty::RawPtr(..)); } @@ -1489,30 +1551,6 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ), ); } - - if let Some(local) = dest.as_local() - && let ClearCrossCrate::Set(box LocalInfo::DerefTemp) = - self.body.local_decls[local].local_info - && !matches!(rvalue, Rvalue::CopyForDeref(_)) - { - self.fail(location, "assignment to a `DerefTemp` must use `CopyForDeref`") - } - } - StatementKind::AscribeUserType(..) => { - if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { - self.fail( - location, - "`AscribeUserType` should have been removed after drop lowering phase", - ); - } - } - StatementKind::FakeRead(..) => { - if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { - self.fail( - location, - "`FakeRead` should have been removed after drop lowering phase", - ); - } } StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => { let ty = op.ty(&self.body.local_decls, self.tcx); @@ -1558,9 +1596,6 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } StatementKind::SetDiscriminant { place, .. } => { - if self.body.phase < MirPhase::Runtime(RuntimePhase::Initial) { - self.fail(location, "`SetDiscriminant`is not allowed until deaggregation"); - } let pty = place.ty(&self.body.local_decls, self.tcx).ty; if !matches!( pty.kind(), @@ -1574,15 +1609,10 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ); } } - StatementKind::Retag(kind, _) => { - // FIXME(JakobDegen) The validator should check that `self.body.phase < - // DropsLowered`. However, this causes ICEs with generation of drop shims, which - // seem to fail to set their `MirPhase` correctly. - if matches!(kind, RetagKind::TwoPhase) { - self.fail(location, format!("explicit `{kind:?}` is forbidden")); - } - } - StatementKind::StorageLive(_) + StatementKind::AscribeUserType(..) + | StatementKind::FakeRead(..) + | StatementKind::Retag(..) + | StatementKind::StorageLive(_) | StatementKind::StorageDead(_) | StatementKind::Coverage(_) | StatementKind::ConstEvalCounter @@ -1622,7 +1652,8 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } } - TerminatorKind::Call { func, .. } | TerminatorKind::TailCall { func, .. } => { + TerminatorKind::Call { func, args, .. } + | TerminatorKind::TailCall { func, args, .. } => { let func_ty = func.ty(&self.body.local_decls, self.tcx); match func_ty.kind() { ty::FnPtr(..) | ty::FnDef(..) => {} @@ -1635,10 +1666,52 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ), } + if let ty::FnDef(did, ..) = func.ty(&self.body.local_decls, self.tcx).kind() + && self.body.phase >= MirPhase::Runtime(RuntimePhase::Optimized) + && matches!(self.tcx.codegen_fn_attrs(did).inline, InlineAttr::Force { .. }) + { + self.fail(location, "`#[rustc_force_inline]`-annotated function not inlined"); + } + if let TerminatorKind::TailCall { .. } = terminator.kind { // FIXME(explicit_tail_calls): implement tail-call specific checks here (such // as signature matching, forbidding closures, etc) } + + // FIXME(explicit_tail_calls): refactor this & add tail-call specific checks + if let TerminatorKind::Call { destination, .. } = terminator.kind { + // The call destination place and Operand::Move place used as an argument might + // be passed by a reference to the callee. Consequently they cannot be packed. + if most_packed_projection(self.tcx, &self.body.local_decls, destination) + .is_some() + { + // This is bad! The callee will expect the memory to be aligned. + self.fail( + location, + format!( + "encountered packed place in `Call` terminator destination: {:?}", + terminator.kind, + ), + ); + } + } + + for arg in args { + if let Operand::Move(place) = &arg.node { + if most_packed_projection(self.tcx, &self.body.local_decls, *place) + .is_some() + { + // This is bad! The callee will expect the memory to be aligned. + self.fail( + location, + format!( + "encountered `Move` of a packed place in `Call` terminator: {:?}", + terminator.kind, + ), + ); + } + } + } } TerminatorKind::Assert { cond, .. } => { let cond_ty = cond.ty(&self.body.local_decls, self.tcx); @@ -1669,12 +1742,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { fn visit_local_decl(&mut self, local: Local, local_decl: &LocalDecl<'tcx>) { if let ClearCrossCrate::Set(box LocalInfo::DerefTemp) = local_decl.local_info { - if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { - self.fail( - START_BLOCK.start_location(), - "`DerefTemp` should have been removed in runtime MIR", - ); - } else if local_decl.ty.builtin_deref(true).is_none() { + if local_decl.ty.builtin_deref(true).is_none() { self.fail( START_BLOCK.start_location(), "`DerefTemp` should only be used for dereferenceable types",