diff options
Diffstat (limited to 'compiler/rustc_const_eval/src')
6 files changed, 154 insertions, 148 deletions
diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 1129b5caec5..916929b6c0b 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -18,13 +18,12 @@ use rustc_middle::span_bug; use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::{self, Ty, TypeVisitableExt}; use rustc_mir_dataflow::Analysis; -use rustc_mir_dataflow::impls::MaybeStorageLive; -use rustc_mir_dataflow::storage::always_storage_live_locals; +use rustc_mir_dataflow::impls::{MaybeStorageLive, always_storage_live_locals}; use rustc_span::{Span, Symbol, sym}; use rustc_trait_selection::traits::{ Obligation, ObligationCause, ObligationCauseCode, ObligationCtxt, }; -use tracing::{debug, instrument, trace}; +use tracing::{instrument, trace}; use super::ops::{self, NonConstOp, Status}; use super::qualifs::{self, HasMutInterior, NeedsDrop, NeedsNonConstDrop}; @@ -47,7 +46,7 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> { /// Returns `true` if `local` is `NeedsDrop` at the given `Location`. /// /// Only updates the cursor if absolutely necessary - fn needs_drop( + pub(crate) fn needs_drop( &mut self, ccx: &'mir ConstCx<'mir, 'tcx>, local: Local, @@ -421,6 +420,43 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { true } + + pub fn check_drop_terminator( + &mut self, + dropped_place: Place<'tcx>, + location: Location, + terminator_span: Span, + ) { + let ty_of_dropped_place = dropped_place.ty(self.body, self.tcx).ty; + + let needs_drop = if let Some(local) = dropped_place.as_local() { + self.qualifs.needs_drop(self.ccx, local, location) + } else { + qualifs::NeedsDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place) + }; + // If this type doesn't need a drop at all, then there's nothing to enforce. + if !needs_drop { + return; + } + + let mut err_span = self.span; + let needs_non_const_drop = if let Some(local) = dropped_place.as_local() { + // Use the span where the local was declared as the span of the drop error. + err_span = self.body.local_decls[local].source_info.span; + self.qualifs.needs_non_const_drop(self.ccx, local, location) + } else { + qualifs::NeedsNonConstDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place) + }; + + self.check_op_spanned( + ops::LiveDrop { + dropped_at: terminator_span, + dropped_ty: ty_of_dropped_place, + needs_non_const_drop, + }, + err_span, + ); + } } impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { @@ -866,35 +902,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { return; } - let mut err_span = self.span; - let ty_of_dropped_place = dropped_place.ty(self.body, self.tcx).ty; - - let ty_needs_non_const_drop = - qualifs::NeedsNonConstDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place); - - debug!(?ty_of_dropped_place, ?ty_needs_non_const_drop); - - if !ty_needs_non_const_drop { - return; - } - - let needs_non_const_drop = if let Some(local) = dropped_place.as_local() { - // Use the span where the local was declared as the span of the drop error. - err_span = self.body.local_decls[local].source_info.span; - self.qualifs.needs_non_const_drop(self.ccx, local, location) - } else { - true - }; - - if needs_non_const_drop { - self.check_op_spanned( - ops::LiveDrop { - dropped_at: Some(terminator.source_info.span), - dropped_ty: ty_of_dropped_place, - }, - err_span, - ); - } + self.check_drop_terminator(*dropped_place, location, terminator.source_info.span); } TerminatorKind::InlineAsm { .. } => self.check_op(ops::InlineAsm), diff --git a/compiler/rustc_const_eval/src/check_consts/ops.rs b/compiler/rustc_const_eval/src/check_consts/ops.rs index 8ba6b89aad4..ab81e60a33f 100644 --- a/compiler/rustc_const_eval/src/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/check_consts/ops.rs @@ -459,17 +459,43 @@ impl<'tcx> NonConstOp<'tcx> for InlineAsm { #[derive(Debug)] pub(crate) struct LiveDrop<'tcx> { - pub dropped_at: Option<Span>, + pub dropped_at: Span, pub dropped_ty: Ty<'tcx>, + pub needs_non_const_drop: bool, } impl<'tcx> NonConstOp<'tcx> for LiveDrop<'tcx> { + fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status { + if self.needs_non_const_drop { + Status::Forbidden + } else { + Status::Unstable { + gate: sym::const_destruct, + gate_already_checked: false, + safe_to_expose_on_stable: false, + is_function_call: false, + } + } + } + fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> { - ccx.dcx().create_err(errors::LiveDrop { - span, - dropped_ty: self.dropped_ty, - kind: ccx.const_kind(), - dropped_at: self.dropped_at, - }) + if self.needs_non_const_drop { + ccx.dcx().create_err(errors::LiveDrop { + span, + dropped_ty: self.dropped_ty, + kind: ccx.const_kind(), + dropped_at: self.dropped_at, + }) + } else { + ccx.tcx.sess.create_feature_err( + errors::LiveDrop { + span, + dropped_ty: self.dropped_ty, + kind: ccx.const_kind(), + dropped_at: self.dropped_at, + }, + sym::const_destruct, + ) + } } } diff --git a/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs b/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs index f6eb130fbd3..951e19b470b 100644 --- a/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs +++ b/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs @@ -1,14 +1,11 @@ use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::{self, BasicBlock, Location}; -use rustc_middle::ty::{Ty, TyCtxt}; -use rustc_span::Span; +use rustc_middle::ty::TyCtxt; use rustc_span::symbol::sym; use tracing::trace; use super::ConstCx; -use super::check::Qualifs; -use super::ops::{self, NonConstOp}; -use super::qualifs::{NeedsNonConstDrop, Qualif}; +use crate::check_consts::check::Checker; use crate::check_consts::rustc_allow_const_fn_unstable; /// Returns `true` if we should use the more precise live drop checker that runs after drop @@ -45,29 +42,16 @@ pub fn check_live_drops<'tcx>(tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>) { return; } - let mut visitor = CheckLiveDrops { ccx: &ccx, qualifs: Qualifs::default() }; + // I know it's not great to be creating a new const checker, but I'd + // rather use it so we can deduplicate the error emitting logic that + // it contains. + let mut visitor = CheckLiveDrops { checker: Checker::new(&ccx) }; visitor.visit_body(body); } struct CheckLiveDrops<'mir, 'tcx> { - ccx: &'mir ConstCx<'mir, 'tcx>, - qualifs: Qualifs<'mir, 'tcx>, -} - -// So we can access `body` and `tcx`. -impl<'mir, 'tcx> std::ops::Deref for CheckLiveDrops<'mir, 'tcx> { - type Target = ConstCx<'mir, 'tcx>; - - fn deref(&self) -> &Self::Target { - self.ccx - } -} - -impl<'tcx> CheckLiveDrops<'_, 'tcx> { - fn check_live_drop(&self, span: Span, dropped_ty: Ty<'tcx>) { - ops::LiveDrop { dropped_at: None, dropped_ty }.build_error(self.ccx, span).emit(); - } + checker: Checker<'mir, 'tcx>, } impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> { @@ -87,28 +71,11 @@ 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) { - // Instead of throwing a bug, we just return here. This is because we have to - // run custom `const Drop` impls. - return; - } - - if dropped_place.is_indirect() { - self.check_live_drop(terminator.source_info.span, dropped_ty); - return; - } - - // Drop elaboration is not precise enough to accept code like - // `tests/ui/consts/control-flow/drop-pass.rs`; e.g., when an `Option<Vec<T>>` is - // initialized with `None` and never changed, it still emits drop glue. - // Hence we additionally check the qualifs here to allow more code to pass. - if self.qualifs.needs_non_const_drop(self.ccx, dropped_place.local, location) { - // Use the span where the dropped local was declared for the error. - let span = self.body.local_decls[dropped_place.local].source_info.span; - self.check_live_drop(span, dropped_ty); - } + self.checker.check_drop_terminator( + *dropped_place, + location, + terminator.source_info.span, + ); } mir::TerminatorKind::UnwindTerminate(_) diff --git a/compiler/rustc_const_eval/src/check_consts/qualifs.rs b/compiler/rustc_const_eval/src/check_consts/qualifs.rs index bc416acc58d..e244b50a4b5 100644 --- a/compiler/rustc_const_eval/src/check_consts/qualifs.rs +++ b/compiler/rustc_const_eval/src/check_consts/qualifs.rs @@ -2,11 +2,14 @@ //! //! See the `Qualif` trait for more info. +// FIXME(const_trait_impl): This API should be really reworked. It's dangerously general for +// having basically only two use-cases that act in different ways. + use rustc_errors::ErrorGuaranteed; use rustc_hir::LangItem; use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::mir::*; -use rustc_middle::ty::{self, AdtDef, GenericArgsRef, Ty}; +use rustc_middle::ty::{self, AdtDef, Ty}; use rustc_middle::{bug, mir}; use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt}; use tracing::instrument; @@ -59,26 +62,12 @@ pub trait Qualif { /// It also determines the `Qualif`s for primitive types. fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool; - /// Returns `true` if this `Qualif` is inherent to the given struct or enum. - /// - /// By default, `Qualif`s propagate into ADTs in a structural way: An ADT only becomes - /// qualified if part of it is assigned a value with that `Qualif`. However, some ADTs *always* - /// have a certain `Qualif`, regardless of whether their fields have it. For example, a type - /// with a custom `Drop` impl is inherently `NeedsDrop`. - /// - /// Returning `true` for `in_adt_inherently` but `false` for `in_any_value_of_ty` is unsound. - fn in_adt_inherently<'tcx>( - cx: &ConstCx<'_, 'tcx>, - adt: AdtDef<'tcx>, - args: GenericArgsRef<'tcx>, - ) -> bool; - - /// Returns `true` if this `Qualif` behaves sructurally for pointers and references: - /// the pointer/reference qualifies if and only if the pointee qualifies. + /// Returns `true` if the `Qualif` is structural in an ADT's fields, i.e. if we may + /// recurse into an operand *value* to determine whether it has this `Qualif`. /// - /// (This is currently `false` for all our instances, but that may change in the future. Also, - /// by keeping it abstract, the handling of `Deref` in `in_place` becomes more clear.) - fn deref_structural<'tcx>(cx: &ConstCx<'_, 'tcx>) -> bool; + /// If this returns false, `in_any_value_of_ty` will be invoked to determine the + /// final qualif for this ADT. + fn is_structural_in_adt_value<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool; } /// Constant containing interior mutability (`UnsafeCell<T>`). @@ -101,6 +90,11 @@ impl Qualif for HasMutInterior { return false; } + // Avoid selecting for `UnsafeCell` either. + if ty.ty_adt_def().is_some_and(|adt| adt.is_unsafe_cell()) { + return true; + } + // We do not use `ty.is_freeze` here, because that requires revealing opaque types, which // requires borrowck, which in turn will invoke mir_const_qualifs again, causing a cycle error. // Instead we invoke an obligation context manually, and provide the opaque type inference settings @@ -129,18 +123,10 @@ impl Qualif for HasMutInterior { !errors.is_empty() } - fn in_adt_inherently<'tcx>( - _cx: &ConstCx<'_, 'tcx>, - adt: AdtDef<'tcx>, - _: GenericArgsRef<'tcx>, - ) -> bool { + fn is_structural_in_adt_value<'tcx>(_cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool { // Exactly one type, `UnsafeCell`, has the `HasMutInterior` qualif inherently. // It arises structurally for all other types. - adt.is_unsafe_cell() - } - - fn deref_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>) -> bool { - false + !adt.is_unsafe_cell() } } @@ -154,6 +140,7 @@ pub struct NeedsDrop; impl Qualif for NeedsDrop { const ANALYSIS_NAME: &'static str = "flow_needs_drop"; const IS_CLEARED_ON_MOVE: bool = true; + const ALLOW_PROMOTED: bool = true; fn in_qualifs(qualifs: &ConstQualifs) -> bool { qualifs.needs_drop @@ -163,16 +150,8 @@ impl Qualif for NeedsDrop { ty.needs_drop(cx.tcx, cx.typing_env) } - fn in_adt_inherently<'tcx>( - cx: &ConstCx<'_, 'tcx>, - adt: AdtDef<'tcx>, - _: GenericArgsRef<'tcx>, - ) -> bool { - adt.has_dtor(cx.tcx) - } - - fn deref_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>) -> bool { - false + fn is_structural_in_adt_value<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool { + !adt.has_dtor(cx.tcx) } } @@ -191,25 +170,46 @@ impl Qualif for NeedsNonConstDrop { #[instrument(level = "trace", skip(cx), ret)] fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool { - // Avoid selecting for simple cases, such as builtin types. - if ty::util::is_trivially_const_drop(ty) { + // If this doesn't need drop at all, then don't select `~const Destruct`. + if !ty.needs_drop(cx.tcx, cx.typing_env) { return false; } - // FIXME(const_trait_impl): Reimplement const drop checking. - NeedsDrop::in_any_value_of_ty(cx, ty) - } - - fn in_adt_inherently<'tcx>( - cx: &ConstCx<'_, 'tcx>, - adt: AdtDef<'tcx>, - _: GenericArgsRef<'tcx>, - ) -> bool { - adt.has_non_const_dtor(cx.tcx) + // We check that the type is `~const Destruct` since that will verify that + // the type is both `~const Drop` (if a drop impl exists for the adt), *and* + // that the components of this type are also `~const Destruct`. This + // amounts to verifying that there are no values in this ADT that may have + // a non-const drop. + let destruct_def_id = cx.tcx.require_lang_item(LangItem::Destruct, Some(cx.body.span)); + let (infcx, param_env) = cx.tcx.infer_ctxt().build_with_typing_env(cx.typing_env); + let ocx = ObligationCtxt::new(&infcx); + ocx.register_obligation(Obligation::new( + cx.tcx, + ObligationCause::misc(cx.body.span, cx.def_id()), + param_env, + ty::Binder::dummy(ty::TraitRef::new(cx.tcx, destruct_def_id, [ty])) + .to_host_effect_clause(cx.tcx, match cx.const_kind() { + rustc_hir::ConstContext::ConstFn => ty::BoundConstness::Maybe, + rustc_hir::ConstContext::Static(_) | rustc_hir::ConstContext::Const { .. } => { + ty::BoundConstness::Const + } + }), + )); + !ocx.select_all_or_error().is_empty() } - fn deref_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>) -> bool { - false + fn is_structural_in_adt_value<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool { + // As soon as an ADT has a destructor, then the drop becomes non-structural + // in its value since: + // 1. The destructor may have `~const` bounds which are not present on the type. + // Someone needs to check that those are satisfied. + // While this could be instead satisfied by checking that the `~const Drop` + // impl holds (i.e. replicating part of the `in_any_value_of_ty` logic above), + // even in this case, we have another problem, which is, + // 2. The destructor may *modify* the operand being dropped, so even if we + // did recurse on the components of the operand, we may not be even dropping + // the same values that were present before the custom destructor was invoked. + !adt.has_dtor(cx.tcx) } } @@ -261,14 +261,15 @@ 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, _, args, ..) = **kind { + if let AggregateKind::Adt(adt_did, ..) = **kind { let def = cx.tcx.adt_def(adt_did); - if Q::in_adt_inherently(cx, def, args) { - return true; - } // Don't do any value-based reasoning for unions. - if def.is_union() && Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx)) { - return true; + // Also, if the ADT is not structural in its fields, + // then we cannot recurse on its fields. Instead, + // we fall back to checking the qualif for *any* value + // of the ADT. + if def.is_union() || !Q::is_structural_in_adt_value(cx, def) { + return Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx)); } } @@ -305,7 +306,11 @@ where return false; } - if matches!(elem, ProjectionElem::Deref) && !Q::deref_structural(cx) { + // `Deref` currently unconditionally "qualifies" if `in_any_value_of_ty` returns true, + // i.e., we treat all qualifs as non-structural for deref projections. Generally, + // we can say very little about `*ptr` even if we know that `ptr` satisfies all + // sorts of properties. + if matches!(elem, ProjectionElem::Deref) { // We have to assume that this qualifies. return true; } diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 604e5ed61a3..80236ee05b7 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -411,7 +411,7 @@ pub struct LiveDrop<'tcx> { pub kind: ConstContext, pub dropped_ty: Ty<'tcx>, #[label(const_eval_dropped_at_label)] - pub dropped_at: Option<Span>, + pub dropped_at: Span, } #[derive(Diagnostic)] diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index 8ce11c71b8b..a9ebf386617 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -10,7 +10,7 @@ use rustc_index::IndexVec; use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::{bug, mir}; -use rustc_mir_dataflow::storage::always_storage_live_locals; +use rustc_mir_dataflow::impls::always_storage_live_locals; use rustc_span::Span; use tracing::{info_span, instrument, trace}; |
