diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:59:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:59:35 +0000 |
commit | d1b2d29528b7794b41e66fc2136e395a02f8529b (patch) | |
tree | a4a17504b260206dec3cf55b2dca82929a348ac2 /compiler/rustc_const_eval/src/transform | |
parent | Releasing progress-linux version 1.72.1+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.tar.xz rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.zip |
Merging upstream version 1.73.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_const_eval/src/transform')
8 files changed, 479 insertions, 332 deletions
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/check.rs b/compiler/rustc_const_eval/src/transform/check_consts/check.rs index 14540e8df..fae047bff 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs @@ -8,8 +8,9 @@ use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::{ImplSource, Obligation, ObligationCause}; use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; -use rustc_middle::ty::subst::{GenericArgKind, InternalSubsts}; +use rustc_middle::traits::BuiltinImplSource; use rustc_middle::ty::{self, adjustment::PointerCoercion, Instance, InstanceDef, Ty, TyCtxt}; +use rustc_middle::ty::{GenericArgKind, GenericArgs}; use rustc_middle::ty::{TraitRef, TypeVisitableExt}; use rustc_mir_dataflow::{self, Analysis}; use rustc_span::{sym, Span, Symbol}; @@ -20,7 +21,7 @@ use std::mem; use std::ops::Deref; use super::ops::{self, NonConstOp, Status}; -use super::qualifs::{self, CustomEq, HasMutInterior, NeedsDrop, NeedsNonConstDrop}; +use super::qualifs::{self, CustomEq, HasMutInterior, NeedsDrop}; use super::resolver::FlowSensitiveAnalysis; use super::{ConstCx, Qualif}; use crate::const_eval::is_unstable_const_fn; @@ -33,7 +34,7 @@ type QualifResults<'mir, 'tcx, Q> = pub struct Qualifs<'mir, 'tcx> { has_mut_interior: Option<QualifResults<'mir, 'tcx, HasMutInterior>>, needs_drop: Option<QualifResults<'mir, 'tcx, NeedsDrop>>, - needs_non_const_drop: Option<QualifResults<'mir, 'tcx, NeedsNonConstDrop>>, + // needs_non_const_drop: Option<QualifResults<'mir, 'tcx, NeedsNonConstDrop>>, } impl<'mir, 'tcx> Qualifs<'mir, 'tcx> { @@ -76,15 +77,17 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> { local: Local, location: Location, ) -> bool { + // FIXME(effects) replace with `NeedsNonconstDrop` after const traits work again + /* let ty = ccx.body.local_decls[local].ty; - if !NeedsNonConstDrop::in_any_value_of_ty(ccx, ty) { + if !NeedsDrop::in_any_value_of_ty(ccx, ty) { return false; } let needs_non_const_drop = self.needs_non_const_drop.get_or_insert_with(|| { let ConstCx { tcx, body, .. } = *ccx; - FlowSensitiveAnalysis::new(NeedsNonConstDrop, ccx) + FlowSensitiveAnalysis::new(NeedsDrop, ccx) .into_engine(tcx, &body) .iterate_to_fixpoint() .into_results_cursor(&body) @@ -92,6 +95,9 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> { needs_non_const_drop.seek_before_primary_effect(location); needs_non_const_drop.get().contains(local) + */ + + self.needs_drop(ccx, local, location) } /// Returns `true` if `local` is `HasMutInterior` at the given `Location`. @@ -701,8 +707,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { let fn_ty = func.ty(body, tcx); - let (mut callee, mut substs) = match *fn_ty.kind() { - ty::FnDef(def_id, substs) => (def_id, substs), + let (mut callee, mut fn_args) = match *fn_ty.kind() { + ty::FnDef(def_id, fn_args) => (def_id, fn_args), ty::FnPtr(_) => { self.check_op(ops::FnCallIndirect); @@ -721,7 +727,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { let infcx = tcx.infer_ctxt().build(); let ocx = ObligationCtxt::new(&infcx); - let predicates = tcx.predicates_of(callee).instantiate(tcx, substs); + let predicates = tcx.predicates_of(callee).instantiate(tcx, fn_args); let cause = ObligationCause::new( terminator.source_info.span, self.body.source.def_id().expect_local(), @@ -740,13 +746,14 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } // Attempting to call a trait method? + // FIXME(effects) do we need this? if let Some(trait_id) = tcx.trait_of_item(callee) { trace!("attempting to call a trait method"); if !self.tcx.features().const_trait_impl { self.check_op(ops::FnCallNonConst { caller, callee, - substs, + args: fn_args, span: *fn_span, call_source: *call_source, feature: Some(sym::const_trait_impl), @@ -754,8 +761,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { return; } - let trait_ref = TraitRef::from_method(tcx, trait_id, substs); - let trait_ref = trait_ref.with_constness(ty::BoundConstness::ConstIfConst); + let trait_ref = TraitRef::from_method(tcx, trait_id, fn_args); let obligation = Obligation::new(tcx, ObligationCause::dummy(), param_env, trait_ref); @@ -766,7 +772,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { }; match implsrc { - Ok(Some(ImplSource::Param(_, ty::BoundConstness::ConstIfConst))) => { + Ok(Some(ImplSource::Param(_))) if tcx.features().effects => { debug!( "const_trait_impl: provided {:?} via where-clause in {:?}", trait_ref, param_env @@ -774,12 +780,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { return; } // Closure: Fn{Once|Mut} - Ok(Some(ImplSource::Builtin(_))) + Ok(Some(ImplSource::Builtin(BuiltinImplSource::Misc, _))) if trait_ref.self_ty().is_closure() && tcx.fn_trait_kind_from_def_id(trait_id).is_some() => { - let ty::Closure(closure_def_id, substs) = - *trait_ref.self_ty().kind() + let ty::Closure(closure_def_id, fn_args) = *trait_ref.self_ty().kind() else { unreachable!() }; @@ -787,7 +792,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { self.check_op(ops::FnCallNonConst { caller, callee, - substs, + args: fn_args, span: *fn_span, call_source: *call_source, feature: None, @@ -798,28 +803,29 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } Ok(Some(ImplSource::UserDefined(data))) => { let callee_name = tcx.item_name(callee); - if let Some(&did) = tcx - .associated_item_def_ids(data.impl_def_id) - .iter() - .find(|did| tcx.item_name(**did) == callee_name) - { - // using internal substs is ok here, since this is only - // used for the `resolve` call below - substs = InternalSubsts::identity_for_item(tcx, did); - callee = did; - } if let hir::Constness::NotConst = tcx.constness(data.impl_def_id) { self.check_op(ops::FnCallNonConst { caller, callee, - substs, + args: fn_args, span: *fn_span, call_source: *call_source, feature: None, }); return; } + + if let Some(&did) = tcx + .associated_item_def_ids(data.impl_def_id) + .iter() + .find(|did| tcx.item_name(**did) == callee_name) + { + // using internal args is ok here, since this is only + // used for the `resolve` call below + fn_args = GenericArgs::identity_for_item(tcx, did); + callee = did; + } } _ if !tcx.is_const_fn_raw(callee) => { // At this point, it is only legal when the caller is in a trait @@ -829,7 +835,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { && tcx.has_attr(callee_trait, sym::const_trait) && Some(callee_trait) == tcx.trait_of_item(caller.to_def_id()) // Can only call methods when it's `<Self as TheTrait>::f`. - && tcx.types.self_param == substs.type_at(0) + && tcx.types.self_param == fn_args.type_at(0) { nonconst_call_permission = true; } @@ -856,7 +862,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { self.check_op(ops::FnCallNonConst { caller, callee, - substs, + args: fn_args, span: *fn_span, call_source: *call_source, feature: None, @@ -869,7 +875,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { // Resolve a trait method call to its concrete implementation, which may be in a // `const` trait impl. - let instance = Instance::resolve(tcx, param_env, callee, substs); + let instance = Instance::resolve(tcx, param_env, callee, fn_args); debug!("Resolving ({:?}) -> {:?}", callee, instance); if let Ok(Some(func)) = instance { if let InstanceDef::Item(def) = func.def { @@ -916,7 +922,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { self.check_op(ops::FnCallNonConst { caller, callee, - substs, + args: fn_args, span: *fn_span, call_source: *call_source, feature: None, @@ -996,8 +1002,9 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { let mut err_span = self.span; let ty_of_dropped_place = dropped_place.ty(self.body, self.tcx).ty; + // FIXME(effects) replace with `NeedsNonConstDrop` once we fix const traits let ty_needs_non_const_drop = - qualifs::NeedsNonConstDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place); + qualifs::NeedsDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place); debug!(?ty_of_dropped_place, ?ty_needs_non_const_drop); diff --git a/compiler/rustc_const_eval/src/transform/check_consts/mod.rs b/compiler/rustc_const_eval/src/transform/check_consts/mod.rs index 8ebfee887..e51082e1e 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/mod.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/mod.rs @@ -68,11 +68,11 @@ impl<'mir, 'tcx> ConstCx<'mir, 'tcx> { pub fn fn_sig(&self) -> PolyFnSig<'tcx> { let did = self.def_id().to_def_id(); if self.tcx.is_closure(did) { - let ty = self.tcx.type_of(did).subst_identity(); - let ty::Closure(_, substs) = ty.kind() else { bug!("type_of closure not ty::Closure") }; - substs.as_closure().sig() + let ty = self.tcx.type_of(did).instantiate_identity(); + let ty::Closure(_, args) = ty.kind() else { bug!("type_of closure not ty::Closure") }; + args.as_closure().sig() } else { - self.tcx.fn_sig(did).subst_identity() + self.tcx.fn_sig(did).instantiate_identity() } } } @@ -127,15 +127,8 @@ fn is_parent_const_stable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> bool { let hir_id = tcx.local_def_id_to_hir_id(local_def_id); let Some(parent) = tcx.hir().opt_parent_id(hir_id) else { return false }; - let parent_def = tcx.hir().get(parent); - - if !matches!( - parent_def, - hir::Node::Item(hir::Item { - kind: hir::ItemKind::Impl(hir::Impl { constness: hir::Constness::Const, .. }), - .. - }) - ) { + + if !tcx.is_const_trait_impl_raw(parent.owner.def_id.to_def_id()) { return false; } diff --git a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs index 4eb278252..1f3cda35c 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs @@ -9,9 +9,9 @@ use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::{ImplSource, Obligation, ObligationCause}; use rustc_middle::mir::{self, CallSource}; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::subst::{GenericArgKind, SubstsRef}; use rustc_middle::ty::TraitRef; use rustc_middle::ty::{suggest_constraining_type_param, Adt, Closure, FnDef, FnPtr, Param, Ty}; +use rustc_middle::ty::{GenericArgKind, GenericArgsRef}; use rustc_middle::util::{call_kind, CallDesugaringKind, CallKind}; use rustc_session::parse::feature_err; use rustc_span::symbol::sym; @@ -98,7 +98,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallIndirect { pub struct FnCallNonConst<'tcx> { pub caller: LocalDefId, pub callee: DefId, - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, pub span: Span, pub call_source: CallSource, pub feature: Option<Symbol>, @@ -110,11 +110,11 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> { ccx: &ConstCx<'_, 'tcx>, _: Span, ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { - let FnCallNonConst { caller, callee, substs, span, call_source, feature } = *self; + let FnCallNonConst { caller, callee, args, span, call_source, feature } = *self; let ConstCx { tcx, param_env, .. } = *ccx; let diag_trait = |err, self_ty: Ty<'_>, trait_id| { - let trait_ref = TraitRef::from_method(tcx, trait_id, substs); + let trait_ref = TraitRef::from_method(tcx, trait_id, args); match self_ty.kind() { Param(param_ty) => { @@ -145,8 +145,11 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> { let implsrc = selcx.select(&obligation); if let Ok(Some(ImplSource::UserDefined(data))) = implsrc { - let span = tcx.def_span(data.impl_def_id); - err.subdiagnostic(errors::NonConstImplNote { span }); + // FIXME(effects) revisit this + if !tcx.is_const_trait_impl_raw(data.impl_def_id) { + let span = tcx.def_span(data.impl_def_id); + err.subdiagnostic(errors::NonConstImplNote { span }); + } } } _ => {} @@ -154,7 +157,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> { }; let call_kind = - call_kind(tcx, ccx.param_env, callee, substs, span, call_source.from_hir_call(), None); + call_kind(tcx, ccx.param_env, callee, args, span, call_source.from_hir_call(), None); debug!(?call_kind); @@ -226,7 +229,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> { let mut sugg = None; if Some(trait_id) == ccx.tcx.lang_items().eq_trait() { - match (substs[0].unpack(), substs[1].unpack()) { + match (args[0].unpack(), args[1].unpack()) { (GenericArgKind::Type(self_ty), GenericArgKind::Type(rhs_ty)) if self_ty == rhs_ty && self_ty.is_ref() @@ -297,7 +300,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> { .create_err(errors::NonConstFmtMacroCall { span, kind: ccx.const_kind() }), _ => ccx.tcx.sess.create_err(errors::NonConstFnCall { span, - def_path_str: ccx.tcx.def_path_str_with_substs(callee, substs), + def_path_str: ccx.tcx.def_path_str_with_args(callee, args), kind: ccx.const_kind(), }), }; @@ -310,8 +313,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> { if let Some(feature) = feature && ccx.tcx.sess.is_nightly_build() { err.help(format!( - "add `#![feature({})]` to the crate attributes to enable", - feature, + "add `#![feature({feature})]` to the crate attributes to enable", )); } @@ -346,10 +348,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallUnstable { err.help("const-stable functions can only call other const-stable functions"); } else if ccx.tcx.sess.is_nightly_build() { if let Some(feature) = feature { - err.help(format!( - "add `#![feature({})]` to the crate attributes to enable", - feature - )); + err.help(format!("add `#![feature({feature})]` to the crate attributes to enable")); } } diff --git a/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs b/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs index 1f1640fd8..e3377bd10 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs @@ -5,7 +5,7 @@ use rustc_span::{symbol::sym, Span}; use super::check::Qualifs; use super::ops::{self, NonConstOp}; -use super::qualifs::{NeedsNonConstDrop, Qualif}; +use super::qualifs::{NeedsDrop, Qualif}; use super::ConstCx; /// Returns `true` if we should use the more precise live drop checker that runs after drop @@ -82,7 +82,9 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> { match &terminator.kind { mir::TerminatorKind::Drop { place: dropped_place, .. } => { let dropped_ty = dropped_place.ty(self.body, self.tcx).ty; - if !NeedsNonConstDrop::in_any_value_of_ty(self.ccx, dropped_ty) { + + // FIXME(effects) use `NeedsNonConstDrop` + if !NeedsDrop::in_any_value_of_ty(self.ccx, dropped_ty) { // Instead of throwing a bug, we just return here. This is because we have to // run custom `const Drop` impls. return; diff --git a/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs b/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs index 015a4aa94..b1b2859ef 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs @@ -7,7 +7,8 @@ use rustc_hir::LangItem; use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::mir; use rustc_middle::mir::*; -use rustc_middle::ty::{self, subst::SubstsRef, AdtDef, Ty}; +use rustc_middle::traits::BuiltinImplSource; +use rustc_middle::ty::{self, AdtDef, GenericArgsRef, Ty}; use rustc_trait_selection::traits::{ self, ImplSource, Obligation, ObligationCause, ObligationCtxt, SelectionContext, }; @@ -22,7 +23,8 @@ pub fn in_any_value_of_ty<'tcx>( ConstQualifs { has_mut_interior: HasMutInterior::in_any_value_of_ty(cx, ty), needs_drop: NeedsDrop::in_any_value_of_ty(cx, ty), - needs_non_const_drop: NeedsNonConstDrop::in_any_value_of_ty(cx, ty), + // FIXME(effects) + needs_non_const_drop: NeedsDrop::in_any_value_of_ty(cx, ty), custom_eq: CustomEq::in_any_value_of_ty(cx, ty), tainted_by_errors, } @@ -72,7 +74,7 @@ pub trait Qualif { fn in_adt_inherently<'tcx>( cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> bool; } @@ -97,7 +99,7 @@ impl Qualif for HasMutInterior { fn in_adt_inherently<'tcx>( _cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>, - _: SubstsRef<'tcx>, + _: GenericArgsRef<'tcx>, ) -> bool { // Exactly one type, `UnsafeCell`, has the `HasMutInterior` qualif inherently. // It arises structurally for all other types. @@ -127,7 +129,7 @@ impl Qualif for NeedsDrop { fn in_adt_inherently<'tcx>( cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>, - _: SubstsRef<'tcx>, + _: GenericArgsRef<'tcx>, ) -> bool { adt.has_dtor(cx.tcx) } @@ -153,12 +155,12 @@ impl Qualif for NeedsNonConstDrop { return false; } + // FIXME(effects) constness let obligation = Obligation::new( cx.tcx, ObligationCause::dummy_with_span(cx.body.span), cx.param_env, - ty::TraitRef::from_lang_item(cx.tcx, LangItem::Destruct, cx.body.span, [ty]) - .with_constness(ty::BoundConstness::ConstIfConst), + ty::TraitRef::from_lang_item(cx.tcx, LangItem::Destruct, cx.body.span, [ty]), ); let infcx = cx.tcx.infer_ctxt().build(); @@ -172,7 +174,7 @@ impl Qualif for NeedsNonConstDrop { if !matches!( impl_src, - ImplSource::Builtin(_) | ImplSource::Param(_, ty::BoundConstness::ConstIfConst) + ImplSource::Builtin(BuiltinImplSource::Misc, _) | ImplSource::Param(_) ) { // If our const destruct candidate is not ConstDestruct or implied by the param env, // then it's bad @@ -193,7 +195,7 @@ impl Qualif for NeedsNonConstDrop { fn in_adt_inherently<'tcx>( cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>, - _: SubstsRef<'tcx>, + _: GenericArgsRef<'tcx>, ) -> bool { adt.has_non_const_dtor(cx.tcx) } @@ -221,9 +223,9 @@ impl Qualif for CustomEq { fn in_adt_inherently<'tcx>( cx: &ConstCx<'_, 'tcx>, def: AdtDef<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> bool { - let ty = Ty::new_adt(cx.tcx, def, substs); + let ty = Ty::new_adt(cx.tcx, def, args); !ty.is_structural_eq_shallow(cx.tcx) } } @@ -276,9 +278,9 @@ where Rvalue::Aggregate(kind, operands) => { // Return early if we know that the struct or enum being constructed is always // qualified. - if let AggregateKind::Adt(adt_did, _, substs, ..) = **kind { + if let AggregateKind::Adt(adt_did, _, args, ..) = **kind { let def = cx.tcx.adt_def(adt_did); - if Q::in_adt_inherently(cx, def, substs) { + if Q::in_adt_inherently(cx, def, args) { return true; } if def.is_union() && Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx)) { @@ -360,7 +362,7 @@ where ConstantKind::Val(..) => None, }; - if let Some(mir::UnevaluatedConst { def, substs: _, promoted }) = uneval { + if let Some(mir::UnevaluatedConst { def, args: _, promoted }) = uneval { // Use qualifs of the type for the promoted. Promoteds in MIR body should be possible // only for `NeedsNonConstDrop` with precise drop checking. This is the only const // check performed after the promotion. Verify that with an assertion. diff --git a/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs b/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs index 3a869f7f5..a137f84b7 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs @@ -4,10 +4,12 @@ use rustc_index::bit_set::BitSet; use rustc_middle::mir::visit::Visitor; -use rustc_middle::mir::{self, BasicBlock, Local, Location, Statement, StatementKind}; +use rustc_middle::mir::{ + self, BasicBlock, CallReturnPlaces, Local, Location, Statement, StatementKind, TerminatorEdges, +}; use rustc_mir_dataflow::fmt::DebugWithContext; use rustc_mir_dataflow::JoinSemiLattice; -use rustc_mir_dataflow::{Analysis, AnalysisDomain, CallReturnPlaces}; +use rustc_mir_dataflow::{Analysis, AnalysisDomain}; use std::fmt; use std::marker::PhantomData; @@ -345,13 +347,14 @@ where self.transfer_function(state).visit_statement(statement, location); } - fn apply_terminator_effect( + fn apply_terminator_effect<'mir>( &mut self, state: &mut Self::Domain, - terminator: &mir::Terminator<'tcx>, + terminator: &'mir mir::Terminator<'tcx>, location: Location, - ) { + ) -> TerminatorEdges<'mir, 'tcx> { self.transfer_function(state).visit_terminator(terminator, location); + terminator.edges() } fn apply_call_return_effect( diff --git a/compiler/rustc_const_eval/src/transform/promote_consts.rs b/compiler/rustc_const_eval/src/transform/promote_consts.rs index 1b39a76e4..d79c65f1d 100644 --- a/compiler/rustc_const_eval/src/transform/promote_consts.rs +++ b/compiler/rustc_const_eval/src/transform/promote_consts.rs @@ -16,7 +16,7 @@ use rustc_hir as hir; use rustc_middle::mir; use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; -use rustc_middle::ty::subst::InternalSubsts; +use rustc_middle::ty::GenericArgs; use rustc_middle::ty::{self, List, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::Span; @@ -759,11 +759,7 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> { let (mut rvalue, source_info) = { let statement = &mut self.source[loc.block].statements[loc.statement_index]; let StatementKind::Assign(box (_, rhs)) = &mut statement.kind else { - span_bug!( - statement.source_info.span, - "{:?} is not an assignment", - statement - ); + span_bug!(statement.source_info.span, "{:?} is not an assignment", statement); }; ( @@ -845,8 +841,8 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> { let mut promoted_operand = |ty, span| { promoted.span = span; promoted.local_decls[RETURN_PLACE] = LocalDecl::new(ty, span); - let substs = tcx.erase_regions(InternalSubsts::identity_for_item(tcx, def)); - let uneval = mir::UnevaluatedConst { def, substs, promoted: Some(promoted_id) }; + let args = tcx.erase_regions(GenericArgs::identity_for_item(tcx, def)); + let uneval = mir::UnevaluatedConst { def, args, promoted: Some(promoted_id) }; Operand::Constant(Box::new(Constant { span, @@ -859,7 +855,9 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> { let local_decls = &mut self.source.local_decls; let loc = candidate.location; let statement = &mut blocks[loc.block].statements[loc.statement_index]; - let StatementKind::Assign(box (_, Rvalue::Ref(region, borrow_kind, place))) = &mut statement.kind else { + let StatementKind::Assign(box (_, Rvalue::Ref(region, borrow_kind, place))) = + &mut statement.kind + else { bug!() }; diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs index 4cc923cd9..783b52d00 100644 --- a/compiler/rustc_const_eval/src/transform/validate.rs +++ b/compiler/rustc_const_eval/src/transform/validate.rs @@ -18,6 +18,7 @@ use rustc_mir_dataflow::impls::MaybeStorageLive; use rustc_mir_dataflow::storage::always_storage_live_locals; use rustc_mir_dataflow::{Analysis, ResultsCursor}; use rustc_target::abi::{Size, FIRST_VARIANT}; +use rustc_target::spec::abi::Abi; #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum EdgeKind { @@ -58,25 +59,48 @@ impl<'tcx> MirPass<'tcx> for Validator { .iterate_to_fixpoint() .into_results_cursor(body); - let mut checker = TypeChecker { + let can_unwind = if mir_phase <= MirPhase::Runtime(RuntimePhase::Initial) { + // In this case `AbortUnwindingCalls` haven't yet been executed. + true + } else if !tcx.def_kind(def_id).is_fn_like() { + true + } else { + let body_ty = tcx.type_of(def_id).skip_binder(); + let body_abi = match body_ty.kind() { + ty::FnDef(..) => body_ty.fn_sig(tcx).abi(), + ty::Closure(..) => Abi::RustCall, + ty::Generator(..) => Abi::Rust, + _ => { + span_bug!(body.span, "unexpected body ty: {:?} phase {:?}", body_ty, mir_phase) + } + }; + + ty::layout::fn_can_unwind(tcx, Some(def_id), body_abi) + }; + + let mut cfg_checker = CfgChecker { when: &self.when, body, tcx, - param_env, mir_phase, unwind_edge_count: 0, reachable_blocks: traversal::reachable_as_bitset(body), storage_liveness, place_cache: FxHashSet::default(), value_cache: FxHashSet::default(), + can_unwind, }; - checker.visit_body(body); - checker.check_cleanup_control_flow(); + cfg_checker.visit_body(body); + cfg_checker.check_cleanup_control_flow(); + + for (location, msg) in validate_types(tcx, self.mir_phase, param_env, body) { + cfg_checker.fail(location, msg); + } if let MirPhase::Runtime(_) = body.phase { if let ty::InstanceDef::Item(_) = body.source.instance { if body.has_free_regions() { - checker.fail( + cfg_checker.fail( Location::START, format!("Free regions in optimized {} MIR", body.phase.name()), ); @@ -86,20 +110,22 @@ impl<'tcx> MirPass<'tcx> for Validator { } } -struct TypeChecker<'a, 'tcx> { +struct CfgChecker<'a, 'tcx> { when: &'a str, body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, - param_env: ParamEnv<'tcx>, mir_phase: MirPhase, unwind_edge_count: usize, reachable_blocks: BitSet<BasicBlock>, storage_liveness: ResultsCursor<'a, 'tcx, MaybeStorageLive<'static>>, place_cache: FxHashSet<PlaceRef<'tcx>>, value_cache: FxHashSet<u128>, + // If `false`, then the MIR must not contain `UnwindAction::Continue` or + // `TerminatorKind::Resume`. + can_unwind: bool, } -impl<'a, 'tcx> TypeChecker<'a, 'tcx> { +impl<'a, 'tcx> CfgChecker<'a, 'tcx> { #[track_caller] fn fail(&self, location: Location, msg: impl AsRef<str>) { let span = self.body.source_info(location).span; @@ -147,7 +173,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } } } else { - self.fail(location, format!("encountered jump to invalid basic block {:?}", bb)) + self.fail(location, format!("encountered jump to invalid basic block {bb:?}")) } } @@ -214,16 +240,13 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { stack.clear(); stack.insert(bb); loop { - let Some(parent)= parent[bb].take() else { - break - }; + let Some(parent) = parent[bb].take() else { break }; let no_cycle = stack.insert(parent); if !no_cycle { self.fail( Location { block: bb, statement_index: 0 }, format!( - "Cleanup control flow violation: Cycle involving edge {:?} -> {:?}", - bb, parent, + "Cleanup control flow violation: Cycle involving edge {bb:?} -> {parent:?}", ), ); break; @@ -238,47 +261,30 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { match unwind { UnwindAction::Cleanup(unwind) => { if is_cleanup { - self.fail(location, "unwind on cleanup block"); + self.fail(location, "`UnwindAction::Cleanup` in cleanup block"); } self.check_edge(location, unwind, EdgeKind::Unwind); } UnwindAction::Continue => { if is_cleanup { - self.fail(location, "unwind on cleanup block"); + self.fail(location, "`UnwindAction::Continue` in cleanup block"); + } + + if !self.can_unwind { + self.fail(location, "`UnwindAction::Continue` in no-unwind function"); } } UnwindAction::Unreachable | UnwindAction::Terminate => (), } } - - /// Check if src can be assigned into dest. - /// This is not precise, it will accept some incorrect assignments. - fn mir_assign_valid_types(&self, src: Ty<'tcx>, dest: Ty<'tcx>) -> bool { - // Fast path before we normalize. - if src == dest { - // Equal types, all is good. - return true; - } - - // We sometimes have to use `defining_opaque_types` for subtyping - // to succeed here and figuring out how exactly that should work - // is annoying. It is harmless enough to just not validate anything - // in that case. We still check this after analysis as all opaque - // types have been revealed at this point. - if (src, dest).has_opaque_types() { - return true; - } - - crate::util::is_subtype(self.tcx, self.param_env, src, dest) - } } -impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { +impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) { if self.body.local_decls.get(local).is_none() { self.fail( location, - format!("local {:?} has no corresponding declaration in `body.local_decls`", local), + format!("local {local:?} has no corresponding declaration in `body.local_decls`"), ); } @@ -293,11 +299,286 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { self.storage_liveness.seek_after_primary_effect(location); let locals_with_storage = self.storage_liveness.get(); if !locals_with_storage.contains(local) { - self.fail(location, format!("use of local {:?}, which has no storage here", local)); + self.fail(location, format!("use of local {local:?}, which has no storage here")); + } + } + } + + fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { + match &statement.kind { + StatementKind::Assign(box (dest, rvalue)) => { + // FIXME(JakobDegen): Check this for all rvalues, not just this one. + if let Rvalue::Use(Operand::Copy(src) | Operand::Move(src)) = rvalue { + // The sides of an assignment must not alias. Currently this just checks whether + // the places are identical. + if dest == src { + self.fail( + location, + "encountered `Assign` statement with overlapping memory", + ); + } + } + } + StatementKind::AscribeUserType(..) => { + if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail( + location, + "`AscribeUserType` should have been removed after drop lowering phase", + ); + } + } + StatementKind::FakeRead(..) => { + if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail( + location, + "`FakeRead` should have been removed after drop lowering phase", + ); + } + } + StatementKind::SetDiscriminant { .. } => { + if self.mir_phase < MirPhase::Runtime(RuntimePhase::Initial) { + self.fail(location, "`SetDiscriminant`is not allowed until deaggregation"); + } + } + StatementKind::Deinit(..) => { + if self.mir_phase < MirPhase::Runtime(RuntimePhase::Initial) { + self.fail(location, "`Deinit`is not allowed until deaggregation"); + } + } + StatementKind::Retag(kind, _) => { + // FIXME(JakobDegen) The validator should check that `self.mir_phase < + // DropsLowered`. However, this causes ICEs with generation of drop shims, which + // seem to fail to set their `MirPhase` correctly. + if matches!(kind, RetagKind::Raw | RetagKind::TwoPhase) { + self.fail(location, format!("explicit `{kind:?}` is forbidden")); + } + } + StatementKind::StorageLive(local) => { + // We check that the local is not live when entering a `StorageLive` for it. + // Technically, violating this restriction is only UB and not actually indicative + // of not well-formed MIR. This means that an optimization which turns MIR that + // already has UB into MIR that fails this check is not necessarily wrong. However, + // we have no such optimizations at the moment, and so we include this check anyway + // to help us catch bugs. If you happen to write an optimization that might cause + // this to incorrectly fire, feel free to remove this check. + if self.reachable_blocks.contains(location.block) { + self.storage_liveness.seek_before_primary_effect(location); + let locals_with_storage = self.storage_liveness.get(); + if locals_with_storage.contains(*local) { + self.fail( + location, + format!("StorageLive({local:?}) which already has storage here"), + ); + } + } + } + StatementKind::StorageDead(_) + | StatementKind::Intrinsic(_) + | StatementKind::Coverage(_) + | StatementKind::ConstEvalCounter + | StatementKind::PlaceMention(..) + | StatementKind::Nop => {} + } + + self.super_statement(statement, location); + } + + fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { + match &terminator.kind { + TerminatorKind::Goto { target } => { + self.check_edge(location, *target, EdgeKind::Normal); + } + TerminatorKind::SwitchInt { targets, discr: _ } => { + for (_, target) in targets.iter() { + self.check_edge(location, target, EdgeKind::Normal); + } + self.check_edge(location, targets.otherwise(), EdgeKind::Normal); + + self.value_cache.clear(); + self.value_cache.extend(targets.iter().map(|(value, _)| value)); + let has_duplicates = targets.iter().len() != self.value_cache.len(); + if has_duplicates { + self.fail( + location, + format!( + "duplicated values in `SwitchInt` terminator: {:?}", + terminator.kind, + ), + ); + } + } + TerminatorKind::Drop { target, unwind, .. } => { + self.check_edge(location, *target, EdgeKind::Normal); + self.check_unwind_edge(location, *unwind); + } + TerminatorKind::Call { args, destination, target, unwind, .. } => { + if let Some(target) = target { + self.check_edge(location, *target, EdgeKind::Normal); + } + self.check_unwind_edge(location, *unwind); + + // The call destination place and Operand::Move place used as an argument might be + // passed by a reference to the callee. Consequently they must be non-overlapping. + // Currently this simply checks for duplicate places. + self.place_cache.clear(); + self.place_cache.insert(destination.as_ref()); + let mut has_duplicates = false; + for arg in args { + if let Operand::Move(place) = arg { + has_duplicates |= !self.place_cache.insert(place.as_ref()); + } + } + + if has_duplicates { + self.fail( + location, + format!( + "encountered overlapping memory in `Call` terminator: {:?}", + terminator.kind, + ), + ); + } + } + TerminatorKind::Assert { target, unwind, .. } => { + self.check_edge(location, *target, EdgeKind::Normal); + self.check_unwind_edge(location, *unwind); + } + TerminatorKind::Yield { resume, drop, .. } => { + if self.body.generator.is_none() { + self.fail(location, "`Yield` cannot appear outside generator bodies"); + } + if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail(location, "`Yield` should have been replaced by generator lowering"); + } + self.check_edge(location, *resume, EdgeKind::Normal); + if let Some(drop) = drop { + self.check_edge(location, *drop, EdgeKind::Normal); + } + } + TerminatorKind::FalseEdge { real_target, imaginary_target } => { + if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail( + location, + "`FalseEdge` should have been removed after drop elaboration", + ); + } + self.check_edge(location, *real_target, EdgeKind::Normal); + self.check_edge(location, *imaginary_target, EdgeKind::Normal); + } + TerminatorKind::FalseUnwind { real_target, unwind } => { + if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail( + location, + "`FalseUnwind` should have been removed after drop elaboration", + ); + } + self.check_edge(location, *real_target, EdgeKind::Normal); + self.check_unwind_edge(location, *unwind); + } + TerminatorKind::InlineAsm { destination, unwind, .. } => { + if let Some(destination) = destination { + self.check_edge(location, *destination, EdgeKind::Normal); + } + self.check_unwind_edge(location, *unwind); + } + TerminatorKind::GeneratorDrop => { + if self.body.generator.is_none() { + self.fail(location, "`GeneratorDrop` cannot appear outside generator bodies"); + } + if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail( + location, + "`GeneratorDrop` should have been replaced by generator lowering", + ); + } + } + TerminatorKind::Resume => { + let bb = location.block; + if !self.body.basic_blocks[bb].is_cleanup { + self.fail(location, "Cannot `Resume` from non-cleanup basic block") + } + if !self.can_unwind { + self.fail(location, "Cannot `Resume` in a function that cannot unwind") + } + } + TerminatorKind::Terminate => { + let bb = location.block; + if !self.body.basic_blocks[bb].is_cleanup { + self.fail(location, "Cannot `Terminate` from non-cleanup basic block") + } + } + TerminatorKind::Return => { + let bb = location.block; + if self.body.basic_blocks[bb].is_cleanup { + self.fail(location, "Cannot `Return` from cleanup basic block") + } } + TerminatorKind::Unreachable => {} + } + + self.super_terminator(terminator, location); + } + + fn visit_source_scope(&mut self, scope: SourceScope) { + if self.body.source_scopes.get(scope).is_none() { + self.tcx.sess.diagnostic().delay_span_bug( + self.body.span, + format!( + "broken MIR in {:?} ({}):\ninvalid source scope {:?}", + self.body.source.instance, self.when, scope, + ), + ); } } +} + +pub fn validate_types<'tcx>( + tcx: TyCtxt<'tcx>, + mir_phase: MirPhase, + param_env: ty::ParamEnv<'tcx>, + body: &Body<'tcx>, +) -> Vec<(Location, String)> { + let mut type_checker = TypeChecker { body, tcx, param_env, mir_phase, failures: Vec::new() }; + type_checker.visit_body(body); + type_checker.failures +} + +struct TypeChecker<'a, 'tcx> { + body: &'a Body<'tcx>, + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, + mir_phase: MirPhase, + failures: Vec<(Location, String)>, +} + +impl<'a, 'tcx> TypeChecker<'a, 'tcx> { + fn fail(&mut self, location: Location, msg: impl Into<String>) { + self.failures.push((location, msg.into())); + } + + /// Check if src can be assigned into dest. + /// This is not precise, it will accept some incorrect assignments. + fn mir_assign_valid_types(&self, src: Ty<'tcx>, dest: Ty<'tcx>) -> bool { + // Fast path before we normalize. + if src == dest { + // Equal types, all is good. + return true; + } + // We sometimes have to use `defining_opaque_types` for subtyping + // to succeed here and figuring out how exactly that should work + // is annoying. It is harmless enough to just not validate anything + // in that case. We still check this after analysis as all opaque + // types have been revealed at this point. + if (src, dest).has_opaque_types() { + return true; + } + + crate::util::is_subtype(self.tcx, self.param_env, src, dest) + } +} + +impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { // This check is somewhat expensive, so only run it when -Zvalidate-mir is passed. if self.tcx.sess.opts.unstable_opts.validate_mir @@ -308,7 +589,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { let ty = place.ty(&self.body.local_decls, self.tcx).ty; if !ty.is_copy_modulo_regions(self.tcx, self.param_env) { - self.fail(location, format!("`Operand::Copy` with non-`Copy` type {}", ty)); + self.fail(location, format!("`Operand::Copy` with non-`Copy` type {ty}")); } } } @@ -327,7 +608,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ProjectionElem::Index(index) => { let index_ty = self.body.local_decls[index].ty; if index_ty != self.tcx.types.usize { - self.fail(location, format!("bad index ({:?} != usize)", index_ty)) + self.fail(location, format!("bad index ({index_ty:?} != usize)")) } } ProjectionElem::Deref @@ -338,30 +619,29 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { if base_ty.is_box() { self.fail( location, - format!("{:?} dereferenced after ElaborateBoxDerefs", base_ty), + format!("{base_ty:?} dereferenced after ElaborateBoxDerefs"), ) } } ProjectionElem::Field(f, ty) => { let parent_ty = place_ref.ty(&self.body.local_decls, self.tcx); - let fail_out_of_bounds = |this: &Self, location| { - this.fail(location, format!("Out of bounds field {:?} for {:?}", f, parent_ty)); + let fail_out_of_bounds = |this: &mut Self, location| { + this.fail(location, format!("Out of bounds field {f:?} for {parent_ty:?}")); }; - let check_equal = |this: &Self, location, f_ty| { + let check_equal = |this: &mut Self, location, f_ty| { if !this.mir_assign_valid_types(ty, f_ty) { this.fail( location, format!( - "Field projection `{:?}.{:?}` specified type `{:?}`, but actual type is `{:?}`", - place_ref, f, ty, f_ty + "Field projection `{place_ref:?}.{f:?}` specified type `{ty:?}`, but actual type is `{f_ty:?}`" ) ) } }; let kind = match parent_ty.ty.kind() { - &ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { - self.tcx.type_of(def_id).subst(self.tcx, substs).kind() + &ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => { + self.tcx.type_of(def_id).instantiate(self.tcx, args).kind() } kind => kind, }; @@ -374,23 +654,23 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { }; check_equal(self, location, *f_ty); } - ty::Adt(adt_def, substs) => { + ty::Adt(adt_def, args) => { let var = parent_ty.variant_index.unwrap_or(FIRST_VARIANT); let Some(field) = adt_def.variant(var).fields.get(f) else { fail_out_of_bounds(self, location); return; }; - check_equal(self, location, field.ty(self.tcx, substs)); + check_equal(self, location, field.ty(self.tcx, args)); } - ty::Closure(_, substs) => { - let substs = substs.as_closure(); - let Some(f_ty) = substs.upvar_tys().nth(f.as_usize()) else { + ty::Closure(_, args) => { + let args = args.as_closure(); + let Some(&f_ty) = args.upvar_tys().get(f.as_usize()) else { fail_out_of_bounds(self, location); return; }; check_equal(self, location, f_ty); } - &ty::Generator(def_id, substs, _) => { + &ty::Generator(def_id, args, _) => { let f_ty = if let Some(var) = parent_ty.variant_index { let gen_body = if def_id == self.body.source.def_id() { self.body @@ -399,7 +679,10 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { }; let Some(layout) = gen_body.generator_layout() else { - self.fail(location, format!("No generator layout for {:?}", parent_ty)); + self.fail( + location, + format!("No generator layout for {parent_ty:?}"), + ); return; }; @@ -409,13 +692,17 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { }; let Some(f_ty) = layout.field_tys.get(local) else { - self.fail(location, format!("Out of bounds local {:?} for {:?}", local, parent_ty)); + self.fail( + location, + format!("Out of bounds local {local:?} for {parent_ty:?}"), + ); return; }; - f_ty.ty + ty::EarlyBinder::bind(f_ty.ty).instantiate(self.tcx, args) } else { - let Some(f_ty) = substs.as_generator().prefix_tys().nth(f.index()) else { + let Some(&f_ty) = args.as_generator().prefix_tys().get(f.index()) + else { fail_out_of_bounds(self, location); return; }; @@ -436,9 +723,9 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } fn visit_var_debug_info(&mut self, debuginfo: &VarDebugInfo<'tcx>) { - let check_place = |place: Place<'_>| { + let check_place = |this: &mut Self, place: Place<'_>| { if place.projection.iter().any(|p| !p.can_use_in_debuginfo()) { - self.fail( + this.fail( START_BLOCK.start_location(), format!("illegal place {:?} in debuginfo for {:?}", place, debuginfo.name), ); @@ -447,21 +734,15 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { match debuginfo.value { VarDebugInfoContents::Const(_) => {} VarDebugInfoContents::Place(place) => { - check_place(place); - if debuginfo.references != 0 && place.projection.last() == Some(&PlaceElem::Deref) { - self.fail( - START_BLOCK.start_location(), - format!("debuginfo {:?}, has both ref and deref", debuginfo), - ); - } + check_place(self, place); } VarDebugInfoContents::Composite { ty, ref fragments } => { for f in fragments { - check_place(f.contents); + check_place(self, f.contents); if ty.is_union() || ty.is_enum() { self.fail( START_BLOCK.start_location(), - format!("invalid type {:?} for composite debuginfo", ty), + format!("invalid type {ty:?} for composite debuginfo"), ); } if f.projection.iter().any(|p| !matches!(p, PlaceElem::Field(..))) { @@ -488,7 +769,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { && cntxt != PlaceContext::NonUse(NonUseContext::VarDebugInfo) && place.projection[1..].contains(&ProjectionElem::Deref) { - self.fail(location, format!("{:?}, has deref at the wrong place", place)); + self.fail(location, format!("{place:?}, has deref at the wrong place")); } self.super_place(place, cntxt, location); @@ -548,7 +829,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { Offset => { check_kinds!(a, "Cannot offset non-pointer type {:?}", ty::RawPtr(..)); if b != self.tcx.types.isize && b != self.tcx.types.usize { - self.fail(location, format!("Cannot offset by non-isize type {:?}", b)); + self.fail(location, format!("Cannot offset by non-isize type {b:?}")); } } Eq | Lt | Le | Ne | Ge | Gt => { @@ -613,13 +894,12 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { self.fail( location, format!( - "Cannot perform checked arithmetic on unequal types {:?} and {:?}", - a, b + "Cannot perform checked arithmetic on unequal types {a:?} and {b:?}" ), ); } } - _ => self.fail(location, format!("There is no checked version of {:?}", op)), + _ => self.fail(location, format!("There is no checked version of {op:?}")), } } Rvalue::UnaryOp(op, operand) => { @@ -714,7 +994,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } Rvalue::NullaryOp(NullOp::OffsetOf(fields), container) => { - let fail_out_of_bounds = |this: &Self, location, field, ty| { + let fail_out_of_bounds = |this: &mut Self, location, field, ty| { this.fail(location, format!("Out of bounds field {field:?} for {ty:?}")); }; @@ -730,7 +1010,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { current_ty = self.tcx.normalize_erasing_regions(self.param_env, f_ty); } - ty::Adt(adt_def, substs) => { + ty::Adt(adt_def, args) => { if adt_def.is_enum() { self.fail( location, @@ -744,7 +1024,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { return; }; - let f_ty = field.ty(self.tcx, substs); + let f_ty = field.ty(self.tcx, args); current_ty = self.tcx.normalize_erasing_regions(self.param_env, f_ty); } _ => { @@ -824,7 +1104,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { if !ty.is_bool() { self.fail( location, - format!("`assume` argument must be `bool`, but got: `{}`", ty), + format!("`assume` argument must be `bool`, but got: `{ty}`"), ); } } @@ -837,7 +1117,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } else { self.fail( location, - format!("Expected src to be ptr in copy_nonoverlapping, got: {}", src_ty), + format!("Expected src to be ptr in copy_nonoverlapping, got: {src_ty}"), ); return; }; @@ -847,19 +1127,19 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } else { self.fail( location, - format!("Expected dst to be ptr in copy_nonoverlapping, got: {}", dst_ty), + format!("Expected dst to be ptr in copy_nonoverlapping, got: {dst_ty}"), ); return; }; // since CopyNonOverlapping is parametrized by 1 type, // we only need to check that they are equal and not keep an extra parameter. if !self.mir_assign_valid_types(op_src_ty, op_dst_ty) { - self.fail(location, format!("bad arg ({:?} != {:?})", op_src_ty, op_dst_ty)); + self.fail(location, format!("bad arg ({op_src_ty:?} != {op_dst_ty:?})")); } let op_cnt_ty = count.ty(&self.body.local_decls, self.tcx); if op_cnt_ty != self.tcx.types.usize { - self.fail(location, format!("bad arg ({:?} != usize)", op_cnt_ty)) + self.fail(location, format!("bad arg ({op_cnt_ty:?} != usize)")) } } StatementKind::SetDiscriminant { place, .. } => { @@ -871,8 +1151,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { self.fail( location, format!( - "`SetDiscriminant` is only allowed on ADTs and generators, not {:?}", - pty + "`SetDiscriminant` is only allowed on ADTs and generators, not {pty:?}" ), ); } @@ -887,29 +1166,11 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { // DropsLowered`. However, this causes ICEs with generation of drop shims, which // seem to fail to set their `MirPhase` correctly. if matches!(kind, RetagKind::Raw | RetagKind::TwoPhase) { - self.fail(location, format!("explicit `{:?}` is forbidden", kind)); + self.fail(location, format!("explicit `{kind:?}` is forbidden")); } } - StatementKind::StorageLive(local) => { - // We check that the local is not live when entering a `StorageLive` for it. - // Technically, violating this restriction is only UB and not actually indicative - // of not well-formed MIR. This means that an optimization which turns MIR that - // already has UB into MIR that fails this check is not necessarily wrong. However, - // we have no such optimizations at the moment, and so we include this check anyway - // to help us catch bugs. If you happen to write an optimization that might cause - // this to incorrectly fire, feel free to remove this check. - if self.reachable_blocks.contains(location.block) { - self.storage_liveness.seek_before_primary_effect(location); - let locals_with_storage = self.storage_liveness.get(); - if locals_with_storage.contains(*local) { - self.fail( - location, - format!("StorageLive({local:?}) which already has storage here"), - ); - } - } - } - StatementKind::StorageDead(_) + StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) | StatementKind::Coverage(_) | StatementKind::ConstEvalCounter | StatementKind::PlaceMention(..) @@ -921,9 +1182,6 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { match &terminator.kind { - TerminatorKind::Goto { target } => { - self.check_edge(location, *target, EdgeKind::Normal); - } TerminatorKind::SwitchInt { targets, discr } => { let switch_ty = discr.ty(&self.body.local_decls, self.tcx); @@ -937,164 +1195,49 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { other => bug!("unhandled type: {:?}", other), }); - for (value, target) in targets.iter() { + for (value, _) in targets.iter() { if Scalar::<()>::try_from_uint(value, size).is_none() { self.fail( location, - format!("the value {:#x} is not a proper {:?}", value, switch_ty), + format!("the value {value:#x} is not a proper {switch_ty:?}"), ) } - - self.check_edge(location, target, EdgeKind::Normal); - } - self.check_edge(location, targets.otherwise(), EdgeKind::Normal); - - self.value_cache.clear(); - self.value_cache.extend(targets.iter().map(|(value, _)| value)); - let has_duplicates = targets.iter().len() != self.value_cache.len(); - if has_duplicates { - self.fail( - location, - format!( - "duplicated values in `SwitchInt` terminator: {:?}", - terminator.kind, - ), - ); } } - TerminatorKind::Drop { target, unwind, .. } => { - self.check_edge(location, *target, EdgeKind::Normal); - self.check_unwind_edge(location, *unwind); - } - TerminatorKind::Call { func, args, destination, target, unwind, .. } => { + TerminatorKind::Call { func, .. } => { let func_ty = func.ty(&self.body.local_decls, self.tcx); match func_ty.kind() { ty::FnPtr(..) | ty::FnDef(..) => {} _ => self.fail( location, - format!("encountered non-callable type {} in `Call` terminator", func_ty), + format!("encountered non-callable type {func_ty} in `Call` terminator"), ), } - if let Some(target) = target { - self.check_edge(location, *target, EdgeKind::Normal); - } - self.check_unwind_edge(location, *unwind); - - // The call destination place and Operand::Move place used as an argument might be - // passed by a reference to the callee. Consequently they must be non-overlapping. - // Currently this simply checks for duplicate places. - self.place_cache.clear(); - self.place_cache.insert(destination.as_ref()); - let mut has_duplicates = false; - for arg in args { - if let Operand::Move(place) = arg { - has_duplicates |= !self.place_cache.insert(place.as_ref()); - } - } - - if has_duplicates { - self.fail( - location, - format!( - "encountered overlapping memory in `Call` terminator: {:?}", - terminator.kind, - ), - ); - } } - TerminatorKind::Assert { cond, target, unwind, .. } => { + TerminatorKind::Assert { cond, .. } => { let cond_ty = cond.ty(&self.body.local_decls, self.tcx); if cond_ty != self.tcx.types.bool { self.fail( location, format!( - "encountered non-boolean condition of type {} in `Assert` terminator", - cond_ty + "encountered non-boolean condition of type {cond_ty} in `Assert` terminator" ), ); } - self.check_edge(location, *target, EdgeKind::Normal); - self.check_unwind_edge(location, *unwind); - } - TerminatorKind::Yield { resume, drop, .. } => { - if self.body.generator.is_none() { - self.fail(location, "`Yield` cannot appear outside generator bodies"); - } - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { - self.fail(location, "`Yield` should have been replaced by generator lowering"); - } - self.check_edge(location, *resume, EdgeKind::Normal); - if let Some(drop) = drop { - self.check_edge(location, *drop, EdgeKind::Normal); - } } - TerminatorKind::FalseEdge { real_target, imaginary_target } => { - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { - self.fail( - location, - "`FalseEdge` should have been removed after drop elaboration", - ); - } - self.check_edge(location, *real_target, EdgeKind::Normal); - self.check_edge(location, *imaginary_target, EdgeKind::Normal); - } - TerminatorKind::FalseUnwind { real_target, unwind } => { - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { - self.fail( - location, - "`FalseUnwind` should have been removed after drop elaboration", - ); - } - self.check_edge(location, *real_target, EdgeKind::Normal); - self.check_unwind_edge(location, *unwind); - } - TerminatorKind::InlineAsm { destination, unwind, .. } => { - if let Some(destination) = destination { - self.check_edge(location, *destination, EdgeKind::Normal); - } - self.check_unwind_edge(location, *unwind); - } - TerminatorKind::GeneratorDrop => { - if self.body.generator.is_none() { - self.fail(location, "`GeneratorDrop` cannot appear outside generator bodies"); - } - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { - self.fail( - location, - "`GeneratorDrop` should have been replaced by generator lowering", - ); - } - } - TerminatorKind::Resume | TerminatorKind::Terminate => { - let bb = location.block; - if !self.body.basic_blocks[bb].is_cleanup { - self.fail( - location, - "Cannot `Resume` or `Terminate` from non-cleanup basic block", - ) - } - } - TerminatorKind::Return => { - let bb = location.block; - if self.body.basic_blocks[bb].is_cleanup { - self.fail(location, "Cannot `Return` from cleanup basic block") - } - } - TerminatorKind::Unreachable => {} + TerminatorKind::Goto { .. } + | TerminatorKind::Drop { .. } + | TerminatorKind::Yield { .. } + | TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::InlineAsm { .. } + | TerminatorKind::GeneratorDrop + | TerminatorKind::Resume + | TerminatorKind::Terminate + | TerminatorKind::Return + | TerminatorKind::Unreachable => {} } self.super_terminator(terminator, location); } - - fn visit_source_scope(&mut self, scope: SourceScope) { - if self.body.source_scopes.get(scope).is_none() { - self.tcx.sess.diagnostic().delay_span_bug( - self.body.span, - format!( - "broken MIR in {:?} ({}):\ninvalid source scope {:?}", - self.body.source.instance, self.when, scope, - ), - ); - } - } } |