summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_const_eval/src/transform/check_consts
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_const_eval/src/transform/check_consts')
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/check.rs1032
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/mod.rs132
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/ops.rs771
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs123
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs384
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/resolver.rs384
6 files changed, 2826 insertions, 0 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
new file mode 100644
index 000000000..0adb88a18
--- /dev/null
+++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
@@ -0,0 +1,1032 @@
+//! The `Visitor` responsible for actually checking a `mir::Body` for invalid operations.
+
+use rustc_errors::{Diagnostic, ErrorGuaranteed};
+use rustc_hir as hir;
+use rustc_hir::def_id::DefId;
+use rustc_index::bit_set::BitSet;
+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::ty::{self, adjustment::PointerCast, Instance, InstanceDef, Ty, TyCtxt};
+use rustc_middle::ty::{Binder, TraitPredicate, TraitRef, TypeVisitable};
+use rustc_mir_dataflow::{self, Analysis};
+use rustc_span::{sym, Span, Symbol};
+use rustc_trait_selection::traits::error_reporting::InferCtxtExt;
+use rustc_trait_selection::traits::SelectionContext;
+
+use std::mem;
+use std::ops::Deref;
+
+use super::ops::{self, NonConstOp, Status};
+use super::qualifs::{self, CustomEq, HasMutInterior, NeedsDrop, NeedsNonConstDrop};
+use super::resolver::FlowSensitiveAnalysis;
+use super::{ConstCx, Qualif};
+use crate::const_eval::is_unstable_const_fn;
+use crate::errors::UnstableInStable;
+
+type QualifResults<'mir, 'tcx, Q> =
+ rustc_mir_dataflow::ResultsCursor<'mir, 'tcx, FlowSensitiveAnalysis<'mir, 'mir, 'tcx, Q>>;
+
+#[derive(Default)]
+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>>,
+}
+
+impl<'mir, 'tcx> Qualifs<'mir, 'tcx> {
+ /// Returns `true` if `local` is `NeedsDrop` at the given `Location`.
+ ///
+ /// Only updates the cursor if absolutely necessary
+ pub fn needs_drop(
+ &mut self,
+ ccx: &'mir ConstCx<'mir, 'tcx>,
+ local: Local,
+ location: Location,
+ ) -> bool {
+ let ty = ccx.body.local_decls[local].ty;
+ // Peeking into opaque types causes cycles if the current function declares said opaque
+ // type. Thus we avoid short circuiting on the type and instead run the more expensive
+ // analysis that looks at the actual usage within this function
+ if !ty.has_opaque_types() && !NeedsDrop::in_any_value_of_ty(ccx, ty) {
+ return false;
+ }
+
+ let needs_drop = self.needs_drop.get_or_insert_with(|| {
+ let ConstCx { tcx, body, .. } = *ccx;
+
+ FlowSensitiveAnalysis::new(NeedsDrop, ccx)
+ .into_engine(tcx, &body)
+ .iterate_to_fixpoint()
+ .into_results_cursor(&body)
+ });
+
+ needs_drop.seek_before_primary_effect(location);
+ needs_drop.get().contains(local)
+ }
+
+ /// Returns `true` if `local` is `NeedsNonConstDrop` at the given `Location`.
+ ///
+ /// Only updates the cursor if absolutely necessary
+ pub fn needs_non_const_drop(
+ &mut self,
+ ccx: &'mir ConstCx<'mir, 'tcx>,
+ local: Local,
+ location: Location,
+ ) -> bool {
+ let ty = ccx.body.local_decls[local].ty;
+ if !NeedsNonConstDrop::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)
+ .into_engine(tcx, &body)
+ .iterate_to_fixpoint()
+ .into_results_cursor(&body)
+ });
+
+ needs_non_const_drop.seek_before_primary_effect(location);
+ needs_non_const_drop.get().contains(local)
+ }
+
+ /// Returns `true` if `local` is `HasMutInterior` at the given `Location`.
+ ///
+ /// Only updates the cursor if absolutely necessary.
+ pub fn has_mut_interior(
+ &mut self,
+ ccx: &'mir ConstCx<'mir, 'tcx>,
+ local: Local,
+ location: Location,
+ ) -> bool {
+ let ty = ccx.body.local_decls[local].ty;
+ // Peeking into opaque types causes cycles if the current function declares said opaque
+ // type. Thus we avoid short circuiting on the type and instead run the more expensive
+ // analysis that looks at the actual usage within this function
+ if !ty.has_opaque_types() && !HasMutInterior::in_any_value_of_ty(ccx, ty) {
+ return false;
+ }
+
+ let has_mut_interior = self.has_mut_interior.get_or_insert_with(|| {
+ let ConstCx { tcx, body, .. } = *ccx;
+
+ FlowSensitiveAnalysis::new(HasMutInterior, ccx)
+ .into_engine(tcx, &body)
+ .iterate_to_fixpoint()
+ .into_results_cursor(&body)
+ });
+
+ has_mut_interior.seek_before_primary_effect(location);
+ has_mut_interior.get().contains(local)
+ }
+
+ fn in_return_place(
+ &mut self,
+ ccx: &'mir ConstCx<'mir, 'tcx>,
+ tainted_by_errors: Option<ErrorGuaranteed>,
+ ) -> ConstQualifs {
+ // Find the `Return` terminator if one exists.
+ //
+ // If no `Return` terminator exists, this MIR is divergent. Just return the conservative
+ // qualifs for the return type.
+ let return_block = ccx
+ .body
+ .basic_blocks()
+ .iter_enumerated()
+ .find(|(_, block)| matches!(block.terminator().kind, TerminatorKind::Return))
+ .map(|(bb, _)| bb);
+
+ let Some(return_block) = return_block else {
+ return qualifs::in_any_value_of_ty(ccx, ccx.body.return_ty(), tainted_by_errors);
+ };
+
+ let return_loc = ccx.body.terminator_loc(return_block);
+
+ let custom_eq = match ccx.const_kind() {
+ // We don't care whether a `const fn` returns a value that is not structurally
+ // matchable. Functions calls are opaque and always use type-based qualification, so
+ // this value should never be used.
+ hir::ConstContext::ConstFn => true,
+
+ // If we know that all values of the return type are structurally matchable, there's no
+ // need to run dataflow.
+ // Opaque types do not participate in const generics or pattern matching, so we can safely count them out.
+ _ if ccx.body.return_ty().has_opaque_types()
+ || !CustomEq::in_any_value_of_ty(ccx, ccx.body.return_ty()) =>
+ {
+ false
+ }
+
+ hir::ConstContext::Const | hir::ConstContext::Static(_) => {
+ let mut cursor = FlowSensitiveAnalysis::new(CustomEq, ccx)
+ .into_engine(ccx.tcx, &ccx.body)
+ .iterate_to_fixpoint()
+ .into_results_cursor(&ccx.body);
+
+ cursor.seek_after_primary_effect(return_loc);
+ cursor.get().contains(RETURN_PLACE)
+ }
+ };
+
+ ConstQualifs {
+ needs_drop: self.needs_drop(ccx, RETURN_PLACE, return_loc),
+ needs_non_const_drop: self.needs_non_const_drop(ccx, RETURN_PLACE, return_loc),
+ has_mut_interior: self.has_mut_interior(ccx, RETURN_PLACE, return_loc),
+ custom_eq,
+ tainted_by_errors,
+ }
+ }
+}
+
+pub struct Checker<'mir, 'tcx> {
+ ccx: &'mir ConstCx<'mir, 'tcx>,
+ qualifs: Qualifs<'mir, 'tcx>,
+
+ /// The span of the current statement.
+ span: Span,
+
+ /// A set that stores for each local whether it has a `StorageDead` for it somewhere.
+ local_has_storage_dead: Option<BitSet<Local>>,
+
+ error_emitted: Option<ErrorGuaranteed>,
+ secondary_errors: Vec<Diagnostic>,
+}
+
+impl<'mir, 'tcx> Deref for Checker<'mir, 'tcx> {
+ type Target = ConstCx<'mir, 'tcx>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.ccx
+ }
+}
+
+impl<'mir, 'tcx> Checker<'mir, 'tcx> {
+ pub fn new(ccx: &'mir ConstCx<'mir, 'tcx>) -> Self {
+ Checker {
+ span: ccx.body.span,
+ ccx,
+ qualifs: Default::default(),
+ local_has_storage_dead: None,
+ error_emitted: None,
+ secondary_errors: Vec::new(),
+ }
+ }
+
+ pub fn check_body(&mut self) {
+ let ConstCx { tcx, body, .. } = *self.ccx;
+ let def_id = self.ccx.def_id();
+
+ // `async` functions cannot be `const fn`. This is checked during AST lowering, so there's
+ // no need to emit duplicate errors here.
+ if self.ccx.is_async() || body.generator.is_some() {
+ tcx.sess.delay_span_bug(body.span, "`async` functions cannot be `const fn`");
+ return;
+ }
+
+ // The local type and predicate checks are not free and only relevant for `const fn`s.
+ if self.const_kind() == hir::ConstContext::ConstFn {
+ for (idx, local) in body.local_decls.iter_enumerated() {
+ // Handle the return place below.
+ if idx == RETURN_PLACE || local.internal {
+ continue;
+ }
+
+ self.span = local.source_info.span;
+ self.check_local_or_return_ty(local.ty, idx);
+ }
+
+ // impl trait is gone in MIR, so check the return type of a const fn by its signature
+ // instead of the type of the return place.
+ self.span = body.local_decls[RETURN_PLACE].source_info.span;
+ let return_ty = tcx.fn_sig(def_id).output();
+ self.check_local_or_return_ty(return_ty.skip_binder(), RETURN_PLACE);
+ }
+
+ if !tcx.has_attr(def_id.to_def_id(), sym::rustc_do_not_const_check) {
+ self.visit_body(&body);
+ }
+
+ // If we got through const-checking without emitting any "primary" errors, emit any
+ // "secondary" errors if they occurred.
+ let secondary_errors = mem::take(&mut self.secondary_errors);
+ if self.error_emitted.is_none() {
+ for mut error in secondary_errors {
+ self.tcx.sess.diagnostic().emit_diagnostic(&mut error);
+ }
+ } else {
+ assert!(self.tcx.sess.has_errors().is_some());
+ }
+ }
+
+ fn local_has_storage_dead(&mut self, local: Local) -> bool {
+ let ccx = self.ccx;
+ self.local_has_storage_dead
+ .get_or_insert_with(|| {
+ struct StorageDeads {
+ locals: BitSet<Local>,
+ }
+ impl<'tcx> Visitor<'tcx> for StorageDeads {
+ fn visit_statement(&mut self, stmt: &Statement<'tcx>, _: Location) {
+ if let StatementKind::StorageDead(l) = stmt.kind {
+ self.locals.insert(l);
+ }
+ }
+ }
+ let mut v = StorageDeads { locals: BitSet::new_empty(ccx.body.local_decls.len()) };
+ v.visit_body(ccx.body);
+ v.locals
+ })
+ .contains(local)
+ }
+
+ pub fn qualifs_in_return_place(&mut self) -> ConstQualifs {
+ self.qualifs.in_return_place(self.ccx, self.error_emitted)
+ }
+
+ /// Emits an error if an expression cannot be evaluated in the current context.
+ pub fn check_op(&mut self, op: impl NonConstOp<'tcx>) {
+ self.check_op_spanned(op, self.span);
+ }
+
+ /// Emits an error at the given `span` if an expression cannot be evaluated in the current
+ /// context.
+ pub fn check_op_spanned<O: NonConstOp<'tcx>>(&mut self, op: O, span: Span) {
+ let gate = match op.status_in_item(self.ccx) {
+ Status::Allowed => return,
+
+ Status::Unstable(gate) if self.tcx.features().enabled(gate) => {
+ let unstable_in_stable = self.ccx.is_const_stable_const_fn()
+ && !super::rustc_allow_const_fn_unstable(self.tcx, self.def_id(), gate);
+ if unstable_in_stable {
+ emit_unstable_in_stable_error(self.ccx, span, gate);
+ }
+
+ return;
+ }
+
+ Status::Unstable(gate) => Some(gate),
+ Status::Forbidden => None,
+ };
+
+ if self.tcx.sess.opts.unstable_opts.unleash_the_miri_inside_of_you {
+ self.tcx.sess.miri_unleashed_feature(span, gate);
+ return;
+ }
+
+ let mut err = op.build_error(self.ccx, span);
+ assert!(err.is_error());
+
+ match op.importance() {
+ ops::DiagnosticImportance::Primary => {
+ let reported = err.emit();
+ self.error_emitted = Some(reported);
+ }
+
+ ops::DiagnosticImportance::Secondary => err.buffer(&mut self.secondary_errors),
+ }
+ }
+
+ fn check_static(&mut self, def_id: DefId, span: Span) {
+ if self.tcx.is_thread_local_static(def_id) {
+ self.tcx.sess.delay_span_bug(span, "tls access is checked in `Rvalue::ThreadLocalRef");
+ }
+ self.check_op_spanned(ops::StaticAccess, span)
+ }
+
+ fn check_local_or_return_ty(&mut self, ty: Ty<'tcx>, local: Local) {
+ let kind = self.body.local_kind(local);
+
+ for ty in ty.walk() {
+ let ty = match ty.unpack() {
+ GenericArgKind::Type(ty) => ty,
+
+ // No constraints on lifetimes or constants, except potentially
+ // constants' types, but `walk` will get to them as well.
+ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue,
+ };
+
+ match *ty.kind() {
+ ty::Ref(_, _, hir::Mutability::Mut) => self.check_op(ops::ty::MutRef(kind)),
+ _ => {}
+ }
+ }
+ }
+
+ fn check_mut_borrow(&mut self, local: Local, kind: hir::BorrowKind) {
+ match self.const_kind() {
+ // In a const fn all borrows are transient or point to the places given via
+ // references in the arguments (so we already checked them with
+ // TransientMutBorrow/MutBorrow as appropriate).
+ // The borrow checker guarantees that no new non-transient borrows are created.
+ // NOTE: Once we have heap allocations during CTFE we need to figure out
+ // how to prevent `const fn` to create long-lived allocations that point
+ // to mutable memory.
+ hir::ConstContext::ConstFn => self.check_op(ops::TransientMutBorrow(kind)),
+ _ => {
+ // Locals with StorageDead do not live beyond the evaluation and can
+ // thus safely be borrowed without being able to be leaked to the final
+ // value of the constant.
+ if self.local_has_storage_dead(local) {
+ self.check_op(ops::TransientMutBorrow(kind));
+ } else {
+ self.check_op(ops::MutBorrow(kind));
+ }
+ }
+ }
+ }
+}
+
+impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
+ fn visit_basic_block_data(&mut self, bb: BasicBlock, block: &BasicBlockData<'tcx>) {
+ trace!("visit_basic_block_data: bb={:?} is_cleanup={:?}", bb, block.is_cleanup);
+
+ // We don't const-check basic blocks on the cleanup path since we never unwind during
+ // const-eval: a panic causes an immediate compile error. In other words, cleanup blocks
+ // are unreachable during const-eval.
+ //
+ // We can't be more conservative (e.g., by const-checking cleanup blocks anyways) because
+ // locals that would never be dropped during normal execution are sometimes dropped during
+ // unwinding, which means backwards-incompatible live-drop errors.
+ if block.is_cleanup {
+ return;
+ }
+
+ self.super_basic_block_data(bb, block);
+ }
+
+ fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
+ trace!("visit_rvalue: rvalue={:?} location={:?}", rvalue, location);
+
+ // Special-case reborrows to be more like a copy of a reference.
+ match *rvalue {
+ Rvalue::Ref(_, kind, place) => {
+ if let Some(reborrowed_place_ref) = place_as_reborrow(self.tcx, self.body, place) {
+ let ctx = match kind {
+ BorrowKind::Shared => {
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow)
+ }
+ BorrowKind::Shallow => {
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::ShallowBorrow)
+ }
+ BorrowKind::Unique => {
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::UniqueBorrow)
+ }
+ BorrowKind::Mut { .. } => {
+ PlaceContext::MutatingUse(MutatingUseContext::Borrow)
+ }
+ };
+ self.visit_local(reborrowed_place_ref.local, ctx, location);
+ self.visit_projection(reborrowed_place_ref, ctx, location);
+ return;
+ }
+ }
+ Rvalue::AddressOf(mutbl, place) => {
+ if let Some(reborrowed_place_ref) = place_as_reborrow(self.tcx, self.body, place) {
+ let ctx = match mutbl {
+ Mutability::Not => {
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::AddressOf)
+ }
+ Mutability::Mut => PlaceContext::MutatingUse(MutatingUseContext::AddressOf),
+ };
+ self.visit_local(reborrowed_place_ref.local, ctx, location);
+ self.visit_projection(reborrowed_place_ref, ctx, location);
+ return;
+ }
+ }
+ _ => {}
+ }
+
+ self.super_rvalue(rvalue, location);
+
+ match *rvalue {
+ Rvalue::ThreadLocalRef(_) => self.check_op(ops::ThreadLocalAccess),
+
+ Rvalue::Use(_)
+ | Rvalue::CopyForDeref(..)
+ | Rvalue::Repeat(..)
+ | Rvalue::Discriminant(..)
+ | Rvalue::Len(_)
+ | Rvalue::Aggregate(..) => {}
+
+ Rvalue::Ref(_, kind @ BorrowKind::Mut { .. }, ref place)
+ | Rvalue::Ref(_, kind @ BorrowKind::Unique, ref place) => {
+ let ty = place.ty(self.body, self.tcx).ty;
+ let is_allowed = match ty.kind() {
+ // Inside a `static mut`, `&mut [...]` is allowed.
+ ty::Array(..) | ty::Slice(_)
+ if self.const_kind() == hir::ConstContext::Static(hir::Mutability::Mut) =>
+ {
+ true
+ }
+
+ // FIXME(ecstaticmorse): We could allow `&mut []` inside a const context given
+ // that this is merely a ZST and it is already eligible for promotion.
+ // This may require an RFC?
+ /*
+ ty::Array(_, len) if len.try_eval_usize(cx.tcx, cx.param_env) == Some(0)
+ => true,
+ */
+ _ => false,
+ };
+
+ if !is_allowed {
+ if let BorrowKind::Mut { .. } = kind {
+ self.check_mut_borrow(place.local, hir::BorrowKind::Ref)
+ } else {
+ self.check_op(ops::CellBorrow);
+ }
+ }
+ }
+
+ Rvalue::AddressOf(Mutability::Mut, ref place) => {
+ self.check_mut_borrow(place.local, hir::BorrowKind::Raw)
+ }
+
+ Rvalue::Ref(_, BorrowKind::Shared | BorrowKind::Shallow, ref place)
+ | Rvalue::AddressOf(Mutability::Not, ref place) => {
+ let borrowed_place_has_mut_interior = qualifs::in_place::<HasMutInterior, _>(
+ &self.ccx,
+ &mut |local| self.qualifs.has_mut_interior(self.ccx, local, location),
+ place.as_ref(),
+ );
+
+ if borrowed_place_has_mut_interior {
+ match self.const_kind() {
+ // In a const fn all borrows are transient or point to the places given via
+ // references in the arguments (so we already checked them with
+ // TransientCellBorrow/CellBorrow as appropriate).
+ // The borrow checker guarantees that no new non-transient borrows are created.
+ // NOTE: Once we have heap allocations during CTFE we need to figure out
+ // how to prevent `const fn` to create long-lived allocations that point
+ // to (interior) mutable memory.
+ hir::ConstContext::ConstFn => self.check_op(ops::TransientCellBorrow),
+ _ => {
+ // Locals with StorageDead are definitely not part of the final constant value, and
+ // it is thus inherently safe to permit such locals to have their
+ // address taken as we can't end up with a reference to them in the
+ // final value.
+ // Note: This is only sound if every local that has a `StorageDead` has a
+ // `StorageDead` in every control flow path leading to a `return` terminator.
+ if self.local_has_storage_dead(place.local) {
+ self.check_op(ops::TransientCellBorrow);
+ } else {
+ self.check_op(ops::CellBorrow);
+ }
+ }
+ }
+ }
+ }
+
+ Rvalue::Cast(
+ CastKind::Pointer(
+ PointerCast::MutToConstPointer
+ | PointerCast::ArrayToPointer
+ | PointerCast::UnsafeFnPointer
+ | PointerCast::ClosureFnPointer(_)
+ | PointerCast::ReifyFnPointer,
+ ),
+ _,
+ _,
+ ) => {
+ // These are all okay; they only change the type, not the data.
+ }
+
+ Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), _, _) => {
+ // Unsizing is implemented for CTFE.
+ }
+
+ Rvalue::Cast(CastKind::PointerExposeAddress, _, _) => {
+ self.check_op(ops::RawPtrToIntCast);
+ }
+ Rvalue::Cast(CastKind::PointerFromExposedAddress, _, _) => {
+ // Since no pointer can ever get exposed (rejected above), this is easy to support.
+ }
+
+ Rvalue::Cast(CastKind::Misc, _, _) => {}
+
+ Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _) => {}
+ Rvalue::ShallowInitBox(_, _) => {}
+
+ Rvalue::UnaryOp(_, ref operand) => {
+ let ty = operand.ty(self.body, self.tcx);
+ if is_int_bool_or_char(ty) {
+ // Int, bool, and char operations are fine.
+ } else if ty.is_floating_point() {
+ self.check_op(ops::FloatingPointOp);
+ } else {
+ span_bug!(self.span, "non-primitive type in `Rvalue::UnaryOp`: {:?}", ty);
+ }
+ }
+
+ Rvalue::BinaryOp(op, box (ref lhs, ref rhs))
+ | Rvalue::CheckedBinaryOp(op, box (ref lhs, ref rhs)) => {
+ let lhs_ty = lhs.ty(self.body, self.tcx);
+ let rhs_ty = rhs.ty(self.body, self.tcx);
+
+ if is_int_bool_or_char(lhs_ty) && is_int_bool_or_char(rhs_ty) {
+ // Int, bool, and char operations are fine.
+ } else if lhs_ty.is_fn_ptr() || lhs_ty.is_unsafe_ptr() {
+ assert_eq!(lhs_ty, rhs_ty);
+ assert!(
+ op == BinOp::Eq
+ || op == BinOp::Ne
+ || op == BinOp::Le
+ || op == BinOp::Lt
+ || op == BinOp::Ge
+ || op == BinOp::Gt
+ || op == BinOp::Offset
+ );
+
+ self.check_op(ops::RawPtrComparison);
+ } else if lhs_ty.is_floating_point() || rhs_ty.is_floating_point() {
+ self.check_op(ops::FloatingPointOp);
+ } else {
+ span_bug!(
+ self.span,
+ "non-primitive type in `Rvalue::BinaryOp`: {:?} ⚬ {:?}",
+ lhs_ty,
+ rhs_ty
+ );
+ }
+ }
+ }
+ }
+
+ fn visit_operand(&mut self, op: &Operand<'tcx>, location: Location) {
+ self.super_operand(op, location);
+ if let Operand::Constant(c) = op {
+ if let Some(def_id) = c.check_static_ptr(self.tcx) {
+ self.check_static(def_id, self.span);
+ }
+ }
+ }
+ fn visit_projection_elem(
+ &mut self,
+ place_local: Local,
+ proj_base: &[PlaceElem<'tcx>],
+ elem: PlaceElem<'tcx>,
+ context: PlaceContext,
+ location: Location,
+ ) {
+ trace!(
+ "visit_projection_elem: place_local={:?} proj_base={:?} elem={:?} \
+ context={:?} location={:?}",
+ place_local,
+ proj_base,
+ elem,
+ context,
+ location,
+ );
+
+ self.super_projection_elem(place_local, proj_base, elem, context, location);
+
+ match elem {
+ ProjectionElem::Deref => {
+ let base_ty = Place::ty_from(place_local, proj_base, self.body, self.tcx).ty;
+ if base_ty.is_unsafe_ptr() {
+ if proj_base.is_empty() {
+ let decl = &self.body.local_decls[place_local];
+ if let Some(box LocalInfo::StaticRef { def_id, .. }) = decl.local_info {
+ let span = decl.source_info.span;
+ self.check_static(def_id, span);
+ return;
+ }
+ }
+
+ // `*const T` is stable, `*mut T` is not
+ if !base_ty.is_mutable_ptr() {
+ return;
+ }
+
+ self.check_op(ops::RawMutPtrDeref);
+ }
+
+ if context.is_mutating_use() {
+ self.check_op(ops::MutDeref);
+ }
+ }
+
+ ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::Downcast(..)
+ | ProjectionElem::Subslice { .. }
+ | ProjectionElem::Field(..)
+ | ProjectionElem::Index(_) => {}
+ }
+ }
+
+ fn visit_source_info(&mut self, source_info: &SourceInfo) {
+ trace!("visit_source_info: source_info={:?}", source_info);
+ self.span = source_info.span;
+ }
+
+ fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
+ trace!("visit_statement: statement={:?} location={:?}", statement, location);
+
+ self.super_statement(statement, location);
+
+ match statement.kind {
+ StatementKind::Assign(..)
+ | StatementKind::SetDiscriminant { .. }
+ | StatementKind::Deinit(..)
+ | StatementKind::FakeRead(..)
+ | StatementKind::StorageLive(_)
+ | StatementKind::StorageDead(_)
+ | StatementKind::Retag { .. }
+ | StatementKind::AscribeUserType(..)
+ | StatementKind::Coverage(..)
+ | StatementKind::CopyNonOverlapping(..)
+ | StatementKind::Nop => {}
+ }
+ }
+
+ #[instrument(level = "debug", skip(self))]
+ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
+ self.super_terminator(terminator, location);
+
+ match &terminator.kind {
+ TerminatorKind::Call { func, args, fn_span, from_hir_call, .. } => {
+ let ConstCx { tcx, body, param_env, .. } = *self.ccx;
+ let caller = self.def_id();
+
+ let fn_ty = func.ty(body, tcx);
+
+ let (mut callee, mut substs) = match *fn_ty.kind() {
+ ty::FnDef(def_id, substs) => (def_id, substs),
+
+ ty::FnPtr(_) => {
+ self.check_op(ops::FnCallIndirect);
+ return;
+ }
+ _ => {
+ span_bug!(terminator.source_info.span, "invalid callee of type {:?}", fn_ty)
+ }
+ };
+
+ // Attempting to call a trait method?
+ 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,
+ span: *fn_span,
+ from_hir_call: *from_hir_call,
+ });
+ return;
+ }
+
+ let trait_ref = TraitRef::from_method(tcx, trait_id, substs);
+ let poly_trait_pred = Binder::dummy(TraitPredicate {
+ trait_ref,
+ constness: ty::BoundConstness::ConstIfConst,
+ polarity: ty::ImplPolarity::Positive,
+ });
+ let obligation =
+ Obligation::new(ObligationCause::dummy(), param_env, poly_trait_pred);
+
+ let implsrc = tcx.infer_ctxt().enter(|infcx| {
+ let mut selcx = SelectionContext::new(&infcx);
+ selcx.select(&obligation)
+ });
+
+ match implsrc {
+ Ok(Some(ImplSource::Param(_, ty::BoundConstness::ConstIfConst))) => {
+ debug!(
+ "const_trait_impl: provided {:?} via where-clause in {:?}",
+ trait_ref, param_env
+ );
+ return;
+ }
+ 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,
+ span: *fn_span,
+ from_hir_call: *from_hir_call,
+ });
+ return;
+ }
+ }
+ _ if !tcx.is_const_fn_raw(callee) => {
+ // At this point, it is only legal when the caller is in a trait
+ // marked with #[const_trait], and the callee is in the same trait.
+ let mut nonconst_call_permission = false;
+ if let Some(callee_trait) = tcx.trait_of_item(callee)
+ && 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)
+ {
+ nonconst_call_permission = true;
+ }
+
+ if !nonconst_call_permission {
+ let obligation = Obligation::new(
+ ObligationCause::dummy_with_span(*fn_span),
+ param_env,
+ tcx.mk_predicate(
+ poly_trait_pred.map_bound(ty::PredicateKind::Trait),
+ ),
+ );
+
+ // improve diagnostics by showing what failed. Our requirements are stricter this time
+ // as we are going to error again anyways.
+ tcx.infer_ctxt().enter(|infcx| {
+ if let Err(e) = implsrc {
+ infcx.report_selection_error(
+ obligation.clone(),
+ &obligation,
+ &e,
+ false,
+ );
+ }
+ });
+
+ self.check_op(ops::FnCallNonConst {
+ caller,
+ callee,
+ substs,
+ span: *fn_span,
+ from_hir_call: *from_hir_call,
+ });
+ return;
+ }
+ }
+ _ => {}
+ }
+
+ // 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);
+ debug!("Resolving ({:?}) -> {:?}", callee, instance);
+ if let Ok(Some(func)) = instance {
+ if let InstanceDef::Item(def) = func.def {
+ callee = def.did;
+ }
+ }
+ }
+
+ // At this point, we are calling a function, `callee`, whose `DefId` is known...
+
+ // `begin_panic` and `panic_display` are generic functions that accept
+ // types other than str. Check to enforce that only str can be used in
+ // const-eval.
+
+ // const-eval of the `begin_panic` fn assumes the argument is `&str`
+ if Some(callee) == tcx.lang_items().begin_panic_fn() {
+ match args[0].ty(&self.ccx.body.local_decls, tcx).kind() {
+ ty::Ref(_, ty, _) if ty.is_str() => return,
+ _ => self.check_op(ops::PanicNonStr),
+ }
+ }
+
+ // const-eval of the `panic_display` fn assumes the argument is `&&str`
+ if Some(callee) == tcx.lang_items().panic_display() {
+ match args[0].ty(&self.ccx.body.local_decls, tcx).kind() {
+ ty::Ref(_, ty, _) if matches!(ty.kind(), ty::Ref(_, ty, _) if ty.is_str()) =>
+ {
+ return;
+ }
+ _ => self.check_op(ops::PanicNonStr),
+ }
+ }
+
+ if Some(callee) == tcx.lang_items().exchange_malloc_fn() {
+ self.check_op(ops::HeapAllocation);
+ return;
+ }
+
+ // `async` blocks get lowered to `std::future::from_generator(/* a closure */)`.
+ let is_async_block = Some(callee) == tcx.lang_items().from_generator_fn();
+ if is_async_block {
+ let kind = hir::GeneratorKind::Async(hir::AsyncGeneratorKind::Block);
+ self.check_op(ops::Generator(kind));
+ return;
+ }
+
+ let is_intrinsic = tcx.is_intrinsic(callee);
+
+ if !tcx.is_const_fn_raw(callee) {
+ if !tcx.is_const_default_method(callee) {
+ // To get to here we must have already found a const impl for the
+ // trait, but for it to still be non-const can be that the impl is
+ // using default method bodies.
+ self.check_op(ops::FnCallNonConst {
+ caller,
+ callee,
+ substs,
+ span: *fn_span,
+ from_hir_call: *from_hir_call,
+ });
+ return;
+ }
+ }
+
+ // If the `const fn` we are trying to call is not const-stable, ensure that we have
+ // the proper feature gate enabled.
+ if let Some(gate) = is_unstable_const_fn(tcx, callee) {
+ trace!(?gate, "calling unstable const fn");
+ if self.span.allows_unstable(gate) {
+ return;
+ }
+
+ // Calling an unstable function *always* requires that the corresponding gate
+ // be enabled, even if the function has `#[rustc_allow_const_fn_unstable(the_gate)]`.
+ if !tcx.features().declared_lib_features.iter().any(|&(sym, _)| sym == gate) {
+ self.check_op(ops::FnCallUnstable(callee, Some(gate)));
+ return;
+ }
+
+ // If this crate is not using stability attributes, or the caller is not claiming to be a
+ // stable `const fn`, that is all that is required.
+ if !self.ccx.is_const_stable_const_fn() {
+ trace!("crate not using stability attributes or caller not stably const");
+ return;
+ }
+
+ // Otherwise, we are something const-stable calling a const-unstable fn.
+
+ if super::rustc_allow_const_fn_unstable(tcx, caller, gate) {
+ trace!("rustc_allow_const_fn_unstable gate active");
+ return;
+ }
+
+ self.check_op(ops::FnCallUnstable(callee, Some(gate)));
+ return;
+ }
+
+ // FIXME(ecstaticmorse); For compatibility, we consider `unstable` callees that
+ // have no `rustc_const_stable` attributes to be const-unstable as well. This
+ // should be fixed later.
+ let callee_is_unstable_unmarked = tcx.lookup_const_stability(callee).is_none()
+ && tcx.lookup_stability(callee).map_or(false, |s| s.is_unstable());
+ if callee_is_unstable_unmarked {
+ trace!("callee_is_unstable_unmarked");
+ // We do not use `const` modifiers for intrinsic "functions", as intrinsics are
+ // `extern` functions, and these have no way to get marked `const`. So instead we
+ // use `rustc_const_(un)stable` attributes to mean that the intrinsic is `const`
+ if self.ccx.is_const_stable_const_fn() || is_intrinsic {
+ self.check_op(ops::FnCallUnstable(callee, None));
+ return;
+ }
+ }
+ trace!("permitting call");
+ }
+
+ // Forbid all `Drop` terminators unless the place being dropped is a local with no
+ // projections that cannot be `NeedsNonConstDrop`.
+ TerminatorKind::Drop { place: dropped_place, .. }
+ | TerminatorKind::DropAndReplace { place: dropped_place, .. } => {
+ // If we are checking live drops after drop-elaboration, don't emit duplicate
+ // errors here.
+ if super::post_drop_elaboration::checking_enabled(self.ccx) {
+ 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) },
+ err_span,
+ );
+ }
+ }
+
+ TerminatorKind::InlineAsm { .. } => self.check_op(ops::InlineAsm),
+
+ TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => {
+ self.check_op(ops::Generator(hir::GeneratorKind::Gen))
+ }
+
+ TerminatorKind::Abort => {
+ // Cleanup blocks are skipped for const checking (see `visit_basic_block_data`).
+ span_bug!(self.span, "`Abort` terminator outside of cleanup block")
+ }
+
+ TerminatorKind::Assert { .. }
+ | TerminatorKind::FalseEdge { .. }
+ | TerminatorKind::FalseUnwind { .. }
+ | TerminatorKind::Goto { .. }
+ | TerminatorKind::Resume
+ | TerminatorKind::Return
+ | TerminatorKind::SwitchInt { .. }
+ | TerminatorKind::Unreachable => {}
+ }
+ }
+}
+
+fn place_as_reborrow<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ place: Place<'tcx>,
+) -> Option<PlaceRef<'tcx>> {
+ match place.as_ref().last_projection() {
+ Some((place_base, ProjectionElem::Deref)) => {
+ // A borrow of a `static` also looks like `&(*_1)` in the MIR, but `_1` is a `const`
+ // that points to the allocation for the static. Don't treat these as reborrows.
+ if body.local_decls[place_base.local].is_ref_to_static() {
+ None
+ } else {
+ // Ensure the type being derefed is a reference and not a raw pointer.
+ // This is sufficient to prevent an access to a `static mut` from being marked as a
+ // reborrow, even if the check above were to disappear.
+ let inner_ty = place_base.ty(body, tcx).ty;
+
+ if let ty::Ref(..) = inner_ty.kind() {
+ return Some(place_base);
+ } else {
+ return None;
+ }
+ }
+ }
+ _ => None,
+ }
+}
+
+fn is_int_bool_or_char(ty: Ty<'_>) -> bool {
+ ty.is_bool() || ty.is_integral() || ty.is_char()
+}
+
+fn emit_unstable_in_stable_error(ccx: &ConstCx<'_, '_>, span: Span, gate: Symbol) {
+ let attr_span = ccx.tcx.def_span(ccx.def_id()).shrink_to_lo();
+
+ ccx.tcx.sess.emit_err(UnstableInStable { gate: gate.to_string(), span, attr_span });
+}
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/mod.rs b/compiler/rustc_const_eval/src/transform/check_consts/mod.rs
new file mode 100644
index 000000000..25b420bed
--- /dev/null
+++ b/compiler/rustc_const_eval/src/transform/check_consts/mod.rs
@@ -0,0 +1,132 @@
+//! Check the bodies of `const`s, `static`s and `const fn`s for illegal operations.
+//!
+//! This module will eventually replace the parts of `qualify_consts.rs` that check whether a local
+//! has interior mutability or needs to be dropped, as well as the visitor that emits errors when
+//! it finds operations that are invalid in a certain context.
+
+use rustc_attr as attr;
+use rustc_hir as hir;
+use rustc_hir::def_id::{DefId, LocalDefId};
+use rustc_middle::mir;
+use rustc_middle::ty::{self, TyCtxt};
+use rustc_span::Symbol;
+
+pub use self::qualifs::Qualif;
+
+pub mod check;
+mod ops;
+pub mod post_drop_elaboration;
+pub mod qualifs;
+mod resolver;
+
+/// Information about the item currently being const-checked, as well as a reference to the global
+/// context.
+pub struct ConstCx<'mir, 'tcx> {
+ pub body: &'mir mir::Body<'tcx>,
+ pub tcx: TyCtxt<'tcx>,
+ pub param_env: ty::ParamEnv<'tcx>,
+ pub const_kind: Option<hir::ConstContext>,
+}
+
+impl<'mir, 'tcx> ConstCx<'mir, 'tcx> {
+ pub fn new(tcx: TyCtxt<'tcx>, body: &'mir mir::Body<'tcx>) -> Self {
+ let def_id = body.source.def_id().expect_local();
+ let param_env = tcx.param_env(def_id);
+ Self::new_with_param_env(tcx, body, param_env)
+ }
+
+ pub fn new_with_param_env(
+ tcx: TyCtxt<'tcx>,
+ body: &'mir mir::Body<'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ ) -> Self {
+ let const_kind = tcx.hir().body_const_context(body.source.def_id().expect_local());
+ ConstCx { body, tcx, param_env, const_kind }
+ }
+
+ pub fn def_id(&self) -> LocalDefId {
+ self.body.source.def_id().expect_local()
+ }
+
+ /// Returns the kind of const context this `Item` represents (`const`, `static`, etc.).
+ ///
+ /// Panics if this `Item` is not const.
+ pub fn const_kind(&self) -> hir::ConstContext {
+ self.const_kind.expect("`const_kind` must not be called on a non-const fn")
+ }
+
+ pub fn is_const_stable_const_fn(&self) -> bool {
+ self.const_kind == Some(hir::ConstContext::ConstFn)
+ && self.tcx.features().staged_api
+ && is_const_stable_const_fn(self.tcx, self.def_id().to_def_id())
+ }
+
+ fn is_async(&self) -> bool {
+ self.tcx.asyncness(self.def_id()) == hir::IsAsync::Async
+ }
+}
+
+pub fn rustc_allow_const_fn_unstable(
+ tcx: TyCtxt<'_>,
+ def_id: LocalDefId,
+ feature_gate: Symbol,
+) -> bool {
+ let attrs = tcx.hir().attrs(tcx.hir().local_def_id_to_hir_id(def_id));
+ attr::rustc_allow_const_fn_unstable(&tcx.sess, attrs).any(|name| name == feature_gate)
+}
+
+// Returns `true` if the given `const fn` is "const-stable".
+//
+// Panics if the given `DefId` does not refer to a `const fn`.
+//
+// Const-stability is only relevant for `const fn` within a `staged_api` crate. Only "const-stable"
+// functions can be called in a const-context by users of the stable compiler. "const-stable"
+// functions are subject to more stringent restrictions than "const-unstable" functions: They
+// cannot use unstable features and can only call other "const-stable" functions.
+pub fn is_const_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
+ // A default body in a `#[const_trait]` is not const-stable because const
+ // trait fns currently cannot be const-stable. We shouldn't
+ // restrict default bodies to only call const-stable functions.
+ if tcx.is_const_default_method(def_id) {
+ return false;
+ }
+
+ // Const-stability is only relevant for `const fn`.
+ assert!(tcx.is_const_fn_raw(def_id));
+
+ // A function is only const-stable if it has `#[rustc_const_stable]` or it the trait it belongs
+ // to is const-stable.
+ match tcx.lookup_const_stability(def_id) {
+ Some(stab) => stab.is_const_stable(),
+ None if is_parent_const_stable_trait(tcx, def_id) => {
+ // Remove this when `#![feature(const_trait_impl)]` is stabilized,
+ // returning `true` unconditionally.
+ tcx.sess.delay_span_bug(
+ tcx.def_span(def_id),
+ "trait implementations cannot be const stable yet",
+ );
+ true
+ }
+ None => false, // By default, items are not const stable.
+ }
+}
+
+fn is_parent_const_stable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
+ let local_def_id = def_id.expect_local();
+ let hir_id = tcx.local_def_id_to_hir_id(local_def_id);
+
+ let Some(parent) = tcx.hir().find_parent_node(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, .. }),
+ ..
+ })
+ ) {
+ return false;
+ }
+
+ tcx.lookup_const_stability(parent.owner).map_or(false, |stab| stab.is_const_stable())
+}
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
new file mode 100644
index 000000000..338022616
--- /dev/null
+++ b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
@@ -0,0 +1,771 @@
+//! Concrete error types for all operations which may be invalid in a certain const context.
+
+use hir::def_id::LocalDefId;
+use rustc_errors::{
+ error_code, struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed,
+};
+use rustc_hir as hir;
+use rustc_hir::def_id::DefId;
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_infer::traits::{ImplSource, Obligation, ObligationCause};
+use rustc_middle::mir;
+use rustc_middle::ty::print::with_no_trimmed_paths;
+use rustc_middle::ty::subst::{GenericArgKind, SubstsRef};
+use rustc_middle::ty::{
+ suggest_constraining_type_param, Adt, Closure, DefIdTree, FnDef, FnPtr, Param, TraitPredicate,
+ Ty,
+};
+use rustc_middle::ty::{Binder, BoundConstness, ImplPolarity, TraitRef};
+use rustc_session::parse::feature_err;
+use rustc_span::symbol::sym;
+use rustc_span::{BytePos, Pos, Span, Symbol};
+use rustc_trait_selection::traits::SelectionContext;
+
+use super::ConstCx;
+use crate::errors::{
+ MutDerefErr, NonConstOpErr, PanicNonStrErr, RawPtrToIntErr, StaticAccessErr,
+ TransientMutBorrowErr, TransientMutBorrowErrRaw,
+};
+use crate::util::{call_kind, CallDesugaringKind, CallKind};
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Status {
+ Allowed,
+ Unstable(Symbol),
+ Forbidden,
+}
+
+#[derive(Clone, Copy)]
+pub enum DiagnosticImportance {
+ /// An operation that must be removed for const-checking to pass.
+ Primary,
+
+ /// An operation that causes const-checking to fail, but is usually a side-effect of a `Primary` operation elsewhere.
+ Secondary,
+}
+
+/// An operation that is not *always* allowed in a const context.
+pub trait NonConstOp<'tcx>: std::fmt::Debug {
+ /// Returns an enum indicating whether this operation is allowed within the given item.
+ fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
+ Status::Forbidden
+ }
+
+ fn importance(&self) -> DiagnosticImportance {
+ DiagnosticImportance::Primary
+ }
+
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed>;
+}
+
+#[derive(Debug)]
+pub struct FloatingPointOp;
+impl<'tcx> NonConstOp<'tcx> for FloatingPointOp {
+ fn status_in_item(&self, ccx: &ConstCx<'_, 'tcx>) -> Status {
+ if ccx.const_kind() == hir::ConstContext::ConstFn {
+ Status::Unstable(sym::const_fn_floating_point_arithmetic)
+ } else {
+ Status::Allowed
+ }
+ }
+
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ feature_err(
+ &ccx.tcx.sess.parse_sess,
+ sym::const_fn_floating_point_arithmetic,
+ span,
+ &format!("floating point arithmetic is not allowed in {}s", ccx.const_kind()),
+ )
+ }
+}
+
+/// A function call where the callee is a pointer.
+#[derive(Debug)]
+pub struct FnCallIndirect;
+impl<'tcx> NonConstOp<'tcx> for FnCallIndirect {
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ ccx.tcx.sess.struct_span_err(
+ span,
+ &format!("function pointer calls are not allowed in {}s", ccx.const_kind()),
+ )
+ }
+}
+
+/// A function call where the callee is not marked as `const`.
+#[derive(Debug, Clone, Copy)]
+pub struct FnCallNonConst<'tcx> {
+ pub caller: LocalDefId,
+ pub callee: DefId,
+ pub substs: SubstsRef<'tcx>,
+ pub span: Span,
+ pub from_hir_call: bool,
+}
+
+impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ _: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ let FnCallNonConst { caller, callee, substs, span, from_hir_call } = *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);
+
+ match self_ty.kind() {
+ Param(param_ty) => {
+ debug!(?param_ty);
+ let caller_hir_id = tcx.hir().local_def_id_to_hir_id(caller);
+ if let Some(generics) = tcx.hir().get(caller_hir_id).generics() {
+ let constraint = with_no_trimmed_paths!(format!(
+ "~const {}",
+ trait_ref.print_only_trait_path()
+ ));
+ suggest_constraining_type_param(
+ tcx,
+ generics,
+ err,
+ &param_ty.name.as_str(),
+ &constraint,
+ None,
+ );
+ }
+ }
+ Adt(..) => {
+ let obligation = Obligation::new(
+ ObligationCause::dummy(),
+ param_env,
+ Binder::dummy(TraitPredicate {
+ trait_ref,
+ constness: BoundConstness::NotConst,
+ polarity: ImplPolarity::Positive,
+ }),
+ );
+
+ let implsrc = tcx.infer_ctxt().enter(|infcx| {
+ let mut selcx = SelectionContext::new(&infcx);
+ selcx.select(&obligation)
+ });
+
+ if let Ok(Some(ImplSource::UserDefined(data))) = implsrc {
+ let span = tcx.def_span(data.impl_def_id);
+ err.span_note(span, "impl defined here, but it is not `const`");
+ }
+ }
+ _ => {}
+ }
+ };
+
+ let call_kind = call_kind(tcx, ccx.param_env, callee, substs, span, from_hir_call, None);
+
+ debug!(?call_kind);
+
+ let mut err = match call_kind {
+ CallKind::Normal { desugaring: Some((kind, self_ty)), .. } => {
+ macro_rules! error {
+ ($fmt:literal) => {
+ struct_span_err!(tcx.sess, span, E0015, $fmt, self_ty, ccx.const_kind())
+ };
+ }
+
+ let mut err = match kind {
+ CallDesugaringKind::ForLoopIntoIter => {
+ error!("cannot convert `{}` into an iterator in {}s")
+ }
+ CallDesugaringKind::QuestionBranch => {
+ error!("`?` cannot determine the branch of `{}` in {}s")
+ }
+ CallDesugaringKind::QuestionFromResidual => {
+ error!("`?` cannot convert from residual of `{}` in {}s")
+ }
+ CallDesugaringKind::TryBlockFromOutput => {
+ error!("`try` block cannot convert `{}` to the result in {}s")
+ }
+ };
+
+ diag_trait(&mut err, self_ty, kind.trait_def_id(tcx));
+ err
+ }
+ CallKind::FnCall { fn_trait_id, self_ty } => {
+ let mut err = struct_span_err!(
+ tcx.sess,
+ span,
+ E0015,
+ "cannot call non-const closure in {}s",
+ ccx.const_kind(),
+ );
+
+ match self_ty.kind() {
+ FnDef(def_id, ..) => {
+ let span = tcx.def_span(*def_id);
+ if ccx.tcx.is_const_fn_raw(*def_id) {
+ span_bug!(span, "calling const FnDef errored when it shouldn't");
+ }
+
+ err.span_note(span, "function defined here, but it is not `const`");
+ }
+ FnPtr(..) => {
+ err.note(&format!(
+ "function pointers need an RFC before allowed to be called in {}s",
+ ccx.const_kind()
+ ));
+ }
+ Closure(..) => {
+ err.note(&format!(
+ "closures need an RFC before allowed to be called in {}s",
+ ccx.const_kind()
+ ));
+ }
+ _ => {}
+ }
+
+ diag_trait(&mut err, self_ty, fn_trait_id);
+ err
+ }
+ CallKind::Operator { trait_id, self_ty, .. } => {
+ let mut err = struct_span_err!(
+ tcx.sess,
+ span,
+ E0015,
+ "cannot call non-const operator in {}s",
+ ccx.const_kind()
+ );
+
+ if Some(trait_id) == ccx.tcx.lang_items().eq_trait() {
+ match (substs[0].unpack(), substs[1].unpack()) {
+ (GenericArgKind::Type(self_ty), GenericArgKind::Type(rhs_ty))
+ if self_ty == rhs_ty
+ && self_ty.is_ref()
+ && self_ty.peel_refs().is_primitive() =>
+ {
+ let mut num_refs = 0;
+ let mut tmp_ty = self_ty;
+ while let rustc_middle::ty::Ref(_, inner_ty, _) = tmp_ty.kind() {
+ num_refs += 1;
+ tmp_ty = *inner_ty;
+ }
+ let deref = "*".repeat(num_refs);
+
+ if let Ok(call_str) = ccx.tcx.sess.source_map().span_to_snippet(span) {
+ if let Some(eq_idx) = call_str.find("==") {
+ if let Some(rhs_idx) =
+ call_str[(eq_idx + 2)..].find(|c: char| !c.is_whitespace())
+ {
+ let rhs_pos =
+ span.lo() + BytePos::from_usize(eq_idx + 2 + rhs_idx);
+ let rhs_span = span.with_lo(rhs_pos).with_hi(rhs_pos);
+ err.multipart_suggestion(
+ "consider dereferencing here",
+ vec![
+ (span.shrink_to_lo(), deref.clone()),
+ (rhs_span, deref),
+ ],
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+
+ diag_trait(&mut err, self_ty, trait_id);
+ err
+ }
+ CallKind::DerefCoercion { deref_target, deref_target_ty, self_ty } => {
+ let mut err = struct_span_err!(
+ tcx.sess,
+ span,
+ E0015,
+ "cannot perform deref coercion on `{}` in {}s",
+ self_ty,
+ ccx.const_kind()
+ );
+
+ err.note(&format!("attempting to deref into `{}`", deref_target_ty));
+
+ // Check first whether the source is accessible (issue #87060)
+ if tcx.sess.source_map().is_span_accessible(deref_target) {
+ err.span_note(deref_target, "deref defined here");
+ }
+
+ diag_trait(&mut err, self_ty, tcx.lang_items().deref_trait().unwrap());
+ err
+ }
+ _ if tcx.opt_parent(callee) == tcx.get_diagnostic_item(sym::ArgumentV1Methods) => {
+ struct_span_err!(
+ ccx.tcx.sess,
+ span,
+ E0015,
+ "cannot call non-const formatting macro in {}s",
+ ccx.const_kind(),
+ )
+ }
+ _ => struct_span_err!(
+ ccx.tcx.sess,
+ span,
+ E0015,
+ "cannot call non-const fn `{}` in {}s",
+ ccx.tcx.def_path_str_with_substs(callee, substs),
+ ccx.const_kind(),
+ ),
+ };
+
+ err.note(&format!(
+ "calls in {}s are limited to constant functions, \
+ tuple structs and tuple variants",
+ ccx.const_kind(),
+ ));
+
+ err
+ }
+}
+
+/// A call to an `#[unstable]` const fn or `#[rustc_const_unstable]` function.
+///
+/// Contains the name of the feature that would allow the use of this function.
+#[derive(Debug)]
+pub struct FnCallUnstable(pub DefId, pub Option<Symbol>);
+
+impl<'tcx> NonConstOp<'tcx> for FnCallUnstable {
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ let FnCallUnstable(def_id, feature) = *self;
+
+ let mut err = ccx.tcx.sess.struct_span_err(
+ span,
+ &format!("`{}` is not yet stable as a const fn", ccx.tcx.def_path_str(def_id)),
+ );
+
+ if ccx.is_const_stable_const_fn() {
+ 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
+ }
+}
+
+#[derive(Debug)]
+pub struct Generator(pub hir::GeneratorKind);
+impl<'tcx> NonConstOp<'tcx> for Generator {
+ fn status_in_item(&self, _: &ConstCx<'_, 'tcx>) -> Status {
+ if let hir::GeneratorKind::Async(hir::AsyncGeneratorKind::Block) = self.0 {
+ Status::Unstable(sym::const_async_blocks)
+ } else {
+ Status::Forbidden
+ }
+ }
+
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ let msg = format!("{}s are not allowed in {}s", self.0, ccx.const_kind());
+ if let hir::GeneratorKind::Async(hir::AsyncGeneratorKind::Block) = self.0 {
+ feature_err(&ccx.tcx.sess.parse_sess, sym::const_async_blocks, span, &msg)
+ } else {
+ ccx.tcx.sess.struct_span_err(span, &msg)
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct HeapAllocation;
+impl<'tcx> NonConstOp<'tcx> for HeapAllocation {
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ let mut err = struct_span_err!(
+ ccx.tcx.sess,
+ span,
+ E0010,
+ "allocations are not allowed in {}s",
+ ccx.const_kind()
+ );
+ err.span_label(span, format!("allocation not allowed in {}s", ccx.const_kind()));
+ if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
+ err.note(
+ "The value of statics and constants must be known at compile time, \
+ and they live for the entire lifetime of a program. Creating a boxed \
+ value allocates memory on the heap at runtime, and therefore cannot \
+ be done at compile time.",
+ );
+ }
+ err
+ }
+}
+
+#[derive(Debug)]
+pub struct InlineAsm;
+impl<'tcx> NonConstOp<'tcx> for InlineAsm {
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ struct_span_err!(
+ ccx.tcx.sess,
+ span,
+ E0015,
+ "inline assembly is not allowed in {}s",
+ ccx.const_kind()
+ )
+ }
+}
+
+#[derive(Debug)]
+pub struct LiveDrop {
+ pub dropped_at: Option<Span>,
+}
+impl<'tcx> NonConstOp<'tcx> for LiveDrop {
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ let mut err = struct_span_err!(
+ ccx.tcx.sess,
+ span,
+ E0493,
+ "destructors cannot be evaluated at compile-time"
+ );
+ err.span_label(span, format!("{}s cannot evaluate destructors", ccx.const_kind()));
+ if let Some(span) = self.dropped_at {
+ err.span_label(span, "value is dropped here");
+ }
+ err
+ }
+}
+
+#[derive(Debug)]
+/// A borrow of a type that contains an `UnsafeCell` somewhere. The borrow never escapes to
+/// the final value of the constant.
+pub struct TransientCellBorrow;
+impl<'tcx> NonConstOp<'tcx> for TransientCellBorrow {
+ fn status_in_item(&self, _: &ConstCx<'_, 'tcx>) -> Status {
+ Status::Unstable(sym::const_refs_to_cell)
+ }
+ fn importance(&self) -> DiagnosticImportance {
+ // The cases that cannot possibly work will already emit a `CellBorrow`, so we should
+ // not additionally emit a feature gate error if activating the feature gate won't work.
+ DiagnosticImportance::Secondary
+ }
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ feature_err(
+ &ccx.tcx.sess.parse_sess,
+ sym::const_refs_to_cell,
+ span,
+ "cannot borrow here, since the borrowed element may contain interior mutability",
+ )
+ }
+}
+
+#[derive(Debug)]
+/// A borrow of a type that contains an `UnsafeCell` somewhere. The borrow might escape to
+/// the final value of the constant, and thus we cannot allow this (for now). We may allow
+/// it in the future for static items.
+pub struct CellBorrow;
+impl<'tcx> NonConstOp<'tcx> for CellBorrow {
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ let mut err = struct_span_err!(
+ ccx.tcx.sess,
+ span,
+ E0492,
+ "{}s cannot refer to interior mutable data",
+ ccx.const_kind(),
+ );
+ err.span_label(
+ span,
+ "this borrow of an interior mutable value may end up in the final value",
+ );
+ if let hir::ConstContext::Static(_) = ccx.const_kind() {
+ err.help(
+ "to fix this, the value can be extracted to a separate \
+ `static` item and then referenced",
+ );
+ }
+ if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
+ err.note(
+ "A constant containing interior mutable data behind a reference can allow you
+ to modify that data. This would make multiple uses of a constant to be able to
+ see different values and allow circumventing the `Send` and `Sync` requirements
+ for shared mutable data, which is unsound.",
+ );
+ }
+ err
+ }
+}
+
+#[derive(Debug)]
+/// This op is for `&mut` borrows in the trailing expression of a constant
+/// which uses the "enclosing scopes rule" to leak its locals into anonymous
+/// static or const items.
+pub struct MutBorrow(pub hir::BorrowKind);
+
+impl<'tcx> NonConstOp<'tcx> for MutBorrow {
+ fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
+ Status::Forbidden
+ }
+
+ fn importance(&self) -> DiagnosticImportance {
+ // If there were primary errors (like non-const function calls), do not emit further
+ // errors about mutable references.
+ DiagnosticImportance::Secondary
+ }
+
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ let raw = match self.0 {
+ hir::BorrowKind::Raw => "raw ",
+ hir::BorrowKind::Ref => "",
+ };
+
+ let mut err = struct_span_err!(
+ ccx.tcx.sess,
+ span,
+ E0764,
+ "{}mutable references are not allowed in the final value of {}s",
+ raw,
+ ccx.const_kind(),
+ );
+
+ if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
+ err.note(
+ "References in statics and constants may only refer \
+ to immutable values.\n\n\
+ Statics are shared everywhere, and if they refer to \
+ mutable data one might violate memory safety since \
+ holding multiple mutable references to shared data \
+ is not allowed.\n\n\
+ If you really want global mutable state, try using \
+ static mut or a global UnsafeCell.",
+ );
+ }
+ err
+ }
+}
+
+#[derive(Debug)]
+pub struct TransientMutBorrow(pub hir::BorrowKind);
+
+impl<'tcx> NonConstOp<'tcx> for TransientMutBorrow {
+ fn status_in_item(&self, _: &ConstCx<'_, 'tcx>) -> Status {
+ Status::Unstable(sym::const_mut_refs)
+ }
+
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ let kind = ccx.const_kind();
+ match self.0 {
+ hir::BorrowKind::Raw => ccx
+ .tcx
+ .sess
+ .create_feature_err(TransientMutBorrowErrRaw { span, kind }, sym::const_mut_refs),
+ hir::BorrowKind::Ref => ccx
+ .tcx
+ .sess
+ .create_feature_err(TransientMutBorrowErr { span, kind }, sym::const_mut_refs),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct MutDeref;
+impl<'tcx> NonConstOp<'tcx> for MutDeref {
+ fn status_in_item(&self, _: &ConstCx<'_, 'tcx>) -> Status {
+ Status::Unstable(sym::const_mut_refs)
+ }
+
+ fn importance(&self) -> DiagnosticImportance {
+ // Usually a side-effect of a `TransientMutBorrow` somewhere.
+ DiagnosticImportance::Secondary
+ }
+
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ ccx.tcx
+ .sess
+ .create_feature_err(MutDerefErr { span, kind: ccx.const_kind() }, sym::const_mut_refs)
+ }
+}
+
+/// A call to a `panic()` lang item where the first argument is _not_ a `&str`.
+#[derive(Debug)]
+pub struct PanicNonStr;
+impl<'tcx> NonConstOp<'tcx> for PanicNonStr {
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ ccx.tcx.sess.create_err(PanicNonStrErr { span })
+ }
+}
+
+/// Comparing raw pointers for equality.
+/// Not currently intended to ever be allowed, even behind a feature gate: operation depends on
+/// allocation base addresses that are not known at compile-time.
+#[derive(Debug)]
+pub struct RawPtrComparison;
+impl<'tcx> NonConstOp<'tcx> for RawPtrComparison {
+ fn build_error(
+ &self,
+ _: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ span_bug!(span, "raw ptr comparison should already be caught in the trait system");
+ }
+}
+
+#[derive(Debug)]
+pub struct RawMutPtrDeref;
+impl<'tcx> NonConstOp<'tcx> for RawMutPtrDeref {
+ fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
+ Status::Unstable(sym::const_mut_refs)
+ }
+
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ feature_err(
+ &ccx.tcx.sess.parse_sess,
+ sym::const_mut_refs,
+ span,
+ &format!("dereferencing raw mutable pointers in {}s is unstable", ccx.const_kind(),),
+ )
+ }
+}
+
+/// Casting raw pointer or function pointer to an integer.
+/// Not currently intended to ever be allowed, even behind a feature gate: operation depends on
+/// allocation base addresses that are not known at compile-time.
+#[derive(Debug)]
+pub struct RawPtrToIntCast;
+impl<'tcx> NonConstOp<'tcx> for RawPtrToIntCast {
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ ccx.tcx.sess.create_err(RawPtrToIntErr { span })
+ }
+}
+
+/// An access to a (non-thread-local) `static`.
+#[derive(Debug)]
+pub struct StaticAccess;
+impl<'tcx> NonConstOp<'tcx> for StaticAccess {
+ fn status_in_item(&self, ccx: &ConstCx<'_, 'tcx>) -> Status {
+ if let hir::ConstContext::Static(_) = ccx.const_kind() {
+ Status::Allowed
+ } else {
+ Status::Forbidden
+ }
+ }
+
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ ccx.tcx.sess.create_err(StaticAccessErr {
+ span,
+ kind: ccx.const_kind(),
+ teach: ccx.tcx.sess.teach(&error_code!(E0013)).then_some(()),
+ })
+ }
+}
+
+/// An access to a thread-local `static`.
+#[derive(Debug)]
+pub struct ThreadLocalAccess;
+impl<'tcx> NonConstOp<'tcx> for ThreadLocalAccess {
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ ccx.tcx.sess.create_err(NonConstOpErr { span })
+ }
+}
+
+// Types that cannot appear in the signature or locals of a `const fn`.
+pub mod ty {
+ use super::*;
+
+ #[derive(Debug)]
+ pub struct MutRef(pub mir::LocalKind);
+ impl<'tcx> NonConstOp<'tcx> for MutRef {
+ fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
+ Status::Unstable(sym::const_mut_refs)
+ }
+
+ fn importance(&self) -> DiagnosticImportance {
+ match self.0 {
+ mir::LocalKind::Var | mir::LocalKind::Temp => DiagnosticImportance::Secondary,
+ mir::LocalKind::ReturnPointer | mir::LocalKind::Arg => {
+ DiagnosticImportance::Primary
+ }
+ }
+ }
+
+ fn build_error(
+ &self,
+ ccx: &ConstCx<'_, 'tcx>,
+ span: Span,
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+ feature_err(
+ &ccx.tcx.sess.parse_sess,
+ sym::const_mut_refs,
+ span,
+ &format!("mutable references are not allowed in {}s", ccx.const_kind()),
+ )
+ }
+ }
+}
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
new file mode 100644
index 000000000..4e210f663
--- /dev/null
+++ b/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs
@@ -0,0 +1,123 @@
+use rustc_middle::mir::visit::Visitor;
+use rustc_middle::mir::{self, BasicBlock, Location};
+use rustc_middle::ty::TyCtxt;
+use rustc_span::{symbol::sym, Span};
+
+use super::check::Qualifs;
+use super::ops::{self, NonConstOp};
+use super::qualifs::{NeedsNonConstDrop, Qualif};
+use super::ConstCx;
+
+/// Returns `true` if we should use the more precise live drop checker that runs after drop
+/// elaboration.
+pub fn checking_enabled(ccx: &ConstCx<'_, '_>) -> bool {
+ // Const-stable functions must always use the stable live drop checker.
+ if ccx.is_const_stable_const_fn() {
+ return false;
+ }
+
+ ccx.tcx.features().const_precise_live_drops
+}
+
+/// Look for live drops in a const context.
+///
+/// This is separate from the rest of the const checking logic because it must run after drop
+/// elaboration.
+pub fn check_live_drops<'tcx>(tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>) {
+ let def_id = body.source.def_id().expect_local();
+ let const_kind = tcx.hir().body_const_context(def_id);
+ if const_kind.is_none() {
+ return;
+ }
+
+ if tcx.has_attr(def_id.to_def_id(), sym::rustc_do_not_const_check) {
+ return;
+ }
+
+ let ccx = ConstCx { body, tcx, const_kind, param_env: tcx.param_env(def_id) };
+ if !checking_enabled(&ccx) {
+ return;
+ }
+
+ let mut visitor = CheckLiveDrops { ccx: &ccx, qualifs: Qualifs::default() };
+
+ 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 CheckLiveDrops<'_, '_> {
+ fn check_live_drop(&self, span: Span) {
+ ops::LiveDrop { dropped_at: None }.build_error(self.ccx, span).emit();
+ }
+}
+
+impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
+ fn visit_basic_block_data(&mut self, bb: BasicBlock, block: &mir::BasicBlockData<'tcx>) {
+ trace!("visit_basic_block_data: bb={:?} is_cleanup={:?}", bb, block.is_cleanup);
+
+ // Ignore drop terminators in cleanup blocks.
+ if block.is_cleanup {
+ return;
+ }
+
+ self.super_basic_block_data(bb, block);
+ }
+
+ fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) {
+ trace!("visit_terminator: terminator={:?} location={:?}", terminator, location);
+
+ match &terminator.kind {
+ mir::TerminatorKind::Drop { place: dropped_place, .. }
+ | mir::TerminatorKind::DropAndReplace { 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);
+ return;
+ }
+
+ // Drop elaboration is not precise enough to accept code like
+ // `src/test/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);
+ }
+ }
+
+ mir::TerminatorKind::Abort
+ | mir::TerminatorKind::Call { .. }
+ | mir::TerminatorKind::Assert { .. }
+ | mir::TerminatorKind::FalseEdge { .. }
+ | mir::TerminatorKind::FalseUnwind { .. }
+ | mir::TerminatorKind::GeneratorDrop
+ | mir::TerminatorKind::Goto { .. }
+ | mir::TerminatorKind::InlineAsm { .. }
+ | mir::TerminatorKind::Resume
+ | mir::TerminatorKind::Return
+ | mir::TerminatorKind::SwitchInt { .. }
+ | mir::TerminatorKind::Unreachable
+ | mir::TerminatorKind::Yield { .. } => {}
+ }
+ }
+}
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs b/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs
new file mode 100644
index 000000000..c8a63c9c3
--- /dev/null
+++ b/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs
@@ -0,0 +1,384 @@
+//! Structural const qualification.
+//!
+//! See the `Qualif` trait for more info.
+
+use rustc_errors::ErrorGuaranteed;
+use rustc_hir::LangItem;
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_infer::traits::TraitEngine;
+use rustc_middle::mir::*;
+use rustc_middle::ty::{self, subst::SubstsRef, AdtDef, Ty};
+use rustc_span::DUMMY_SP;
+use rustc_trait_selection::traits::{
+ self, ImplSource, Obligation, ObligationCause, SelectionContext, TraitEngineExt,
+};
+
+use super::ConstCx;
+
+pub fn in_any_value_of_ty<'tcx>(
+ cx: &ConstCx<'_, 'tcx>,
+ ty: Ty<'tcx>,
+ tainted_by_errors: Option<ErrorGuaranteed>,
+) -> ConstQualifs {
+ 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),
+ custom_eq: CustomEq::in_any_value_of_ty(cx, ty),
+ tainted_by_errors,
+ }
+}
+
+/// A "qualif"(-ication) is a way to look for something "bad" in the MIR that would disqualify some
+/// code for promotion or prevent it from evaluating at compile time.
+///
+/// Normally, we would determine what qualifications apply to each type and error when an illegal
+/// operation is performed on such a type. However, this was found to be too imprecise, especially
+/// in the presence of `enum`s. If only a single variant of an enum has a certain qualification, we
+/// needn't reject code unless it actually constructs and operates on the qualified variant.
+///
+/// To accomplish this, const-checking and promotion use a value-based analysis (as opposed to a
+/// type-based one). Qualifications propagate structurally across variables: If a local (or a
+/// projection of a local) is assigned a qualified value, that local itself becomes qualified.
+pub trait Qualif {
+ /// The name of the file used to debug the dataflow analysis that computes this qualif.
+ const ANALYSIS_NAME: &'static str;
+
+ /// Whether this `Qualif` is cleared when a local is moved from.
+ const IS_CLEARED_ON_MOVE: bool = false;
+
+ /// Whether this `Qualif` might be evaluated after the promotion and can encounter a promoted.
+ const ALLOW_PROMOTED: bool = false;
+
+ /// Extracts the field of `ConstQualifs` that corresponds to this `Qualif`.
+ fn in_qualifs(qualifs: &ConstQualifs) -> bool;
+
+ /// Returns `true` if *any* value of the given type could possibly have this `Qualif`.
+ ///
+ /// This function determines `Qualif`s when we cannot do a value-based analysis. Since qualif
+ /// propagation is context-insensitive, this includes function arguments and values returned
+ /// from a call to another function.
+ ///
+ /// 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>,
+ substs: SubstsRef<'tcx>,
+ ) -> bool;
+}
+
+/// Constant containing interior mutability (`UnsafeCell<T>`).
+/// This must be ruled out to make sure that evaluating the constant at compile-time
+/// and at *any point* during the run-time would produce the same result. In particular,
+/// promotion of temporaries must not change program behavior; if the promoted could be
+/// written to, that would be a problem.
+pub struct HasMutInterior;
+
+impl Qualif for HasMutInterior {
+ const ANALYSIS_NAME: &'static str = "flow_has_mut_interior";
+
+ fn in_qualifs(qualifs: &ConstQualifs) -> bool {
+ qualifs.has_mut_interior
+ }
+
+ fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool {
+ !ty.is_freeze(cx.tcx.at(DUMMY_SP), cx.param_env)
+ }
+
+ fn in_adt_inherently<'tcx>(
+ _cx: &ConstCx<'_, 'tcx>,
+ adt: AdtDef<'tcx>,
+ _: SubstsRef<'tcx>,
+ ) -> bool {
+ // Exactly one type, `UnsafeCell`, has the `HasMutInterior` qualif inherently.
+ // It arises structurally for all other types.
+ adt.is_unsafe_cell()
+ }
+}
+
+/// Constant containing an ADT that implements `Drop`.
+/// This must be ruled out because implicit promotion would remove side-effects
+/// that occur as part of dropping that value. N.B., the implicit promotion has
+/// to reject const Drop implementations because even if side-effects are ruled
+/// out through other means, the execution of the drop could diverge.
+pub struct NeedsDrop;
+
+impl Qualif for NeedsDrop {
+ const ANALYSIS_NAME: &'static str = "flow_needs_drop";
+ const IS_CLEARED_ON_MOVE: bool = true;
+
+ fn in_qualifs(qualifs: &ConstQualifs) -> bool {
+ qualifs.needs_drop
+ }
+
+ fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool {
+ ty.needs_drop(cx.tcx, cx.param_env)
+ }
+
+ fn in_adt_inherently<'tcx>(
+ cx: &ConstCx<'_, 'tcx>,
+ adt: AdtDef<'tcx>,
+ _: SubstsRef<'tcx>,
+ ) -> bool {
+ adt.has_dtor(cx.tcx)
+ }
+}
+
+/// Constant containing an ADT that implements non-const `Drop`.
+/// This must be ruled out because we cannot run `Drop` during compile-time.
+pub struct NeedsNonConstDrop;
+
+impl Qualif for NeedsNonConstDrop {
+ const ANALYSIS_NAME: &'static str = "flow_needs_nonconst_drop";
+ const IS_CLEARED_ON_MOVE: bool = true;
+ const ALLOW_PROMOTED: bool = true;
+
+ fn in_qualifs(qualifs: &ConstQualifs) -> bool {
+ qualifs.needs_non_const_drop
+ }
+
+ 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) {
+ return false;
+ }
+
+ let destruct = cx.tcx.require_lang_item(LangItem::Destruct, None);
+
+ let obligation = Obligation::new(
+ ObligationCause::dummy(),
+ cx.param_env,
+ ty::Binder::dummy(ty::TraitPredicate {
+ trait_ref: ty::TraitRef {
+ def_id: destruct,
+ substs: cx.tcx.mk_substs_trait(ty, &[]),
+ },
+ constness: ty::BoundConstness::ConstIfConst,
+ polarity: ty::ImplPolarity::Positive,
+ }),
+ );
+
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ let mut selcx = SelectionContext::new(&infcx);
+ let Some(impl_src) = selcx.select(&obligation).ok().flatten() else {
+ // If we couldn't select a const destruct candidate, then it's bad
+ return true;
+ };
+
+ if !matches!(
+ impl_src,
+ ImplSource::ConstDestruct(_)
+ | ImplSource::Param(_, ty::BoundConstness::ConstIfConst)
+ ) {
+ // If our const destruct candidate is not ConstDestruct or implied by the param env,
+ // then it's bad
+ return true;
+ }
+
+ if impl_src.borrow_nested_obligations().is_empty() {
+ return false;
+ }
+
+ // If we successfully found one, then select all of the predicates
+ // implied by our const drop impl.
+ let mut fcx = <dyn TraitEngine<'tcx>>::new(cx.tcx);
+ for nested in impl_src.nested_obligations() {
+ fcx.register_predicate_obligation(&infcx, nested);
+ }
+
+ // If we had any errors, then it's bad
+ !fcx.select_all_or_error(&infcx).is_empty()
+ })
+ }
+
+ fn in_adt_inherently<'tcx>(
+ cx: &ConstCx<'_, 'tcx>,
+ adt: AdtDef<'tcx>,
+ _: SubstsRef<'tcx>,
+ ) -> bool {
+ adt.has_non_const_dtor(cx.tcx)
+ }
+}
+
+/// A constant that cannot be used as part of a pattern in a `match` expression.
+pub struct CustomEq;
+
+impl Qualif for CustomEq {
+ const ANALYSIS_NAME: &'static str = "flow_custom_eq";
+
+ fn in_qualifs(qualifs: &ConstQualifs) -> bool {
+ qualifs.custom_eq
+ }
+
+ fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool {
+ // If *any* component of a composite data type does not implement `Structural{Partial,}Eq`,
+ // we know that at least some values of that type are not structural-match. I say "some"
+ // because that component may be part of an enum variant (e.g.,
+ // `Option::<NonStructuralMatchTy>::Some`), in which case some values of this type may be
+ // structural-match (`Option::None`).
+ traits::search_for_structural_match_violation(cx.body.span, cx.tcx, ty).is_some()
+ }
+
+ fn in_adt_inherently<'tcx>(
+ cx: &ConstCx<'_, 'tcx>,
+ adt: AdtDef<'tcx>,
+ substs: SubstsRef<'tcx>,
+ ) -> bool {
+ let ty = cx.tcx.mk_ty(ty::Adt(adt, substs));
+ !ty.is_structural_eq_shallow(cx.tcx)
+ }
+}
+
+// FIXME: Use `mir::visit::Visitor` for the `in_*` functions if/when it supports early return.
+
+/// Returns `true` if this `Rvalue` contains qualif `Q`.
+pub fn in_rvalue<'tcx, Q, F>(
+ cx: &ConstCx<'_, 'tcx>,
+ in_local: &mut F,
+ rvalue: &Rvalue<'tcx>,
+) -> bool
+where
+ Q: Qualif,
+ F: FnMut(Local) -> bool,
+{
+ match rvalue {
+ Rvalue::ThreadLocalRef(_) | Rvalue::NullaryOp(..) => {
+ Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx))
+ }
+
+ Rvalue::Discriminant(place) | Rvalue::Len(place) => {
+ in_place::<Q, _>(cx, in_local, place.as_ref())
+ }
+
+ Rvalue::CopyForDeref(place) => in_place::<Q, _>(cx, in_local, place.as_ref()),
+
+ Rvalue::Use(operand)
+ | Rvalue::Repeat(operand, _)
+ | Rvalue::UnaryOp(_, operand)
+ | Rvalue::Cast(_, operand, _)
+ | Rvalue::ShallowInitBox(operand, _) => in_operand::<Q, _>(cx, in_local, operand),
+
+ Rvalue::BinaryOp(_, box (lhs, rhs)) | Rvalue::CheckedBinaryOp(_, box (lhs, rhs)) => {
+ in_operand::<Q, _>(cx, in_local, lhs) || in_operand::<Q, _>(cx, in_local, rhs)
+ }
+
+ Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
+ // Special-case reborrows to be more like a copy of the reference.
+ if let Some((place_base, ProjectionElem::Deref)) = place.as_ref().last_projection() {
+ let base_ty = place_base.ty(cx.body, cx.tcx).ty;
+ if let ty::Ref(..) = base_ty.kind() {
+ return in_place::<Q, _>(cx, in_local, place_base);
+ }
+ }
+
+ in_place::<Q, _>(cx, in_local, place.as_ref())
+ }
+
+ 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 {
+ let def = cx.tcx.adt_def(adt_did);
+ if Q::in_adt_inherently(cx, def, substs) {
+ return true;
+ }
+ if def.is_union() && Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx)) {
+ return true;
+ }
+ }
+
+ // Otherwise, proceed structurally...
+ operands.iter().any(|o| in_operand::<Q, _>(cx, in_local, o))
+ }
+ }
+}
+
+/// Returns `true` if this `Place` contains qualif `Q`.
+pub fn in_place<'tcx, Q, F>(cx: &ConstCx<'_, 'tcx>, in_local: &mut F, place: PlaceRef<'tcx>) -> bool
+where
+ Q: Qualif,
+ F: FnMut(Local) -> bool,
+{
+ let mut place = place;
+ while let Some((place_base, elem)) = place.last_projection() {
+ match elem {
+ ProjectionElem::Index(index) if in_local(index) => return true,
+
+ ProjectionElem::Deref
+ | ProjectionElem::Field(_, _)
+ | ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::Subslice { .. }
+ | ProjectionElem::Downcast(_, _)
+ | ProjectionElem::Index(_) => {}
+ }
+
+ let base_ty = place_base.ty(cx.body, cx.tcx);
+ let proj_ty = base_ty.projection_ty(cx.tcx, elem).ty;
+ if !Q::in_any_value_of_ty(cx, proj_ty) {
+ return false;
+ }
+
+ place = place_base;
+ }
+
+ assert!(place.projection.is_empty());
+ in_local(place.local)
+}
+
+/// Returns `true` if this `Operand` contains qualif `Q`.
+pub fn in_operand<'tcx, Q, F>(
+ cx: &ConstCx<'_, 'tcx>,
+ in_local: &mut F,
+ operand: &Operand<'tcx>,
+) -> bool
+where
+ Q: Qualif,
+ F: FnMut(Local) -> bool,
+{
+ let constant = match operand {
+ Operand::Copy(place) | Operand::Move(place) => {
+ return in_place::<Q, _>(cx, in_local, place.as_ref());
+ }
+
+ Operand::Constant(c) => c,
+ };
+
+ // Check the qualifs of the value of `const` items.
+ if let Some(ct) = constant.literal.const_for_ty() {
+ if let ty::ConstKind::Unevaluated(ty::Unevaluated { def, substs: _, promoted }) = ct.kind()
+ {
+ // 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.
+ assert!(promoted.is_none() || Q::ALLOW_PROMOTED);
+ // Don't peek inside trait associated constants.
+ if promoted.is_none() && cx.tcx.trait_of_item(def.did).is_none() {
+ let qualifs = if let Some((did, param_did)) = def.as_const_arg() {
+ cx.tcx.at(constant.span).mir_const_qualif_const_arg((did, param_did))
+ } else {
+ cx.tcx.at(constant.span).mir_const_qualif(def.did)
+ };
+
+ if !Q::in_qualifs(&qualifs) {
+ return false;
+ }
+
+ // Just in case the type is more specific than
+ // the definition, e.g., impl associated const
+ // with type parameters, take it into account.
+ }
+ }
+ }
+ // Otherwise use the qualifs of the type.
+ Q::in_any_value_of_ty(cx, constant.literal.ty())
+}
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs b/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs
new file mode 100644
index 000000000..60c1e4950
--- /dev/null
+++ b/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs
@@ -0,0 +1,384 @@
+//! Propagate `Qualif`s between locals and query the results.
+//!
+//! This contains the dataflow analysis used to track `Qualif`s on complex control-flow graphs.
+
+use rustc_index::bit_set::BitSet;
+use rustc_middle::mir::visit::Visitor;
+use rustc_middle::mir::{self, BasicBlock, Local, Location, Statement, StatementKind};
+use rustc_mir_dataflow::fmt::DebugWithContext;
+use rustc_mir_dataflow::JoinSemiLattice;
+use rustc_mir_dataflow::{Analysis, AnalysisDomain, CallReturnPlaces};
+use rustc_span::DUMMY_SP;
+
+use std::fmt;
+use std::marker::PhantomData;
+
+use super::{qualifs, ConstCx, Qualif};
+
+/// A `Visitor` that propagates qualifs between locals. This defines the transfer function of
+/// `FlowSensitiveAnalysis`.
+///
+/// To account for indirect assignments, data flow conservatively assumes that local becomes
+/// qualified immediately after it is borrowed or its address escapes. The borrow must allow for
+/// mutation, which includes shared borrows of places with interior mutability. The type of
+/// borrowed place must contain the qualif.
+struct TransferFunction<'a, 'mir, 'tcx, Q> {
+ ccx: &'a ConstCx<'mir, 'tcx>,
+ state: &'a mut State,
+ _qualif: PhantomData<Q>,
+}
+
+impl<'a, 'mir, 'tcx, Q> TransferFunction<'a, 'mir, 'tcx, Q>
+where
+ Q: Qualif,
+{
+ fn new(ccx: &'a ConstCx<'mir, 'tcx>, state: &'a mut State) -> Self {
+ TransferFunction { ccx, state, _qualif: PhantomData }
+ }
+
+ fn initialize_state(&mut self) {
+ self.state.qualif.clear();
+ self.state.borrow.clear();
+
+ for arg in self.ccx.body.args_iter() {
+ let arg_ty = self.ccx.body.local_decls[arg].ty;
+ if Q::in_any_value_of_ty(self.ccx, arg_ty) {
+ self.state.qualif.insert(arg);
+ }
+ }
+ }
+
+ fn assign_qualif_direct(&mut self, place: &mir::Place<'tcx>, mut value: bool) {
+ debug_assert!(!place.is_indirect());
+
+ if !value {
+ for (base, _elem) in place.iter_projections() {
+ let base_ty = base.ty(self.ccx.body, self.ccx.tcx);
+ if base_ty.ty.is_union() && Q::in_any_value_of_ty(self.ccx, base_ty.ty) {
+ value = true;
+ break;
+ }
+ }
+ }
+
+ match (value, place.as_ref()) {
+ (true, mir::PlaceRef { local, .. }) => {
+ self.state.qualif.insert(local);
+ }
+
+ // For now, we do not clear the qualif if a local is overwritten in full by
+ // an unqualified rvalue (e.g. `y = 5`). This is to be consistent
+ // with aggregates where we overwrite all fields with assignments, which would not
+ // get this feature.
+ (false, mir::PlaceRef { local: _, projection: &[] }) => {
+ // self.state.qualif.remove(*local);
+ }
+
+ _ => {}
+ }
+ }
+
+ fn apply_call_return_effect(
+ &mut self,
+ _block: BasicBlock,
+ return_places: CallReturnPlaces<'_, 'tcx>,
+ ) {
+ return_places.for_each(|place| {
+ // We cannot reason about another function's internals, so use conservative type-based
+ // qualification for the result of a function call.
+ let return_ty = place.ty(self.ccx.body, self.ccx.tcx).ty;
+ let qualif = Q::in_any_value_of_ty(self.ccx, return_ty);
+
+ if !place.is_indirect() {
+ self.assign_qualif_direct(&place, qualif);
+ }
+ });
+ }
+
+ fn address_of_allows_mutation(&self, _mt: mir::Mutability, _place: mir::Place<'tcx>) -> bool {
+ // Exact set of permissions granted by AddressOf is undecided. Conservatively assume that
+ // it might allow mutation until resolution of #56604.
+ true
+ }
+
+ fn ref_allows_mutation(&self, kind: mir::BorrowKind, place: mir::Place<'tcx>) -> bool {
+ match kind {
+ mir::BorrowKind::Mut { .. } => true,
+ mir::BorrowKind::Shared | mir::BorrowKind::Shallow | mir::BorrowKind::Unique => {
+ self.shared_borrow_allows_mutation(place)
+ }
+ }
+ }
+
+ /// `&` only allow mutation if the borrowed place is `!Freeze`.
+ ///
+ /// This assumes that it is UB to take the address of a struct field whose type is
+ /// `Freeze`, then use pointer arithmetic to derive a pointer to a *different* field of
+ /// that same struct whose type is `!Freeze`. If we decide that this is not UB, we will
+ /// have to check the type of the borrowed **local** instead of the borrowed **place**
+ /// below. See [rust-lang/unsafe-code-guidelines#134].
+ ///
+ /// [rust-lang/unsafe-code-guidelines#134]: https://github.com/rust-lang/unsafe-code-guidelines/issues/134
+ fn shared_borrow_allows_mutation(&self, place: mir::Place<'tcx>) -> bool {
+ !place
+ .ty(self.ccx.body, self.ccx.tcx)
+ .ty
+ .is_freeze(self.ccx.tcx.at(DUMMY_SP), self.ccx.param_env)
+ }
+}
+
+impl<'tcx, Q> Visitor<'tcx> for TransferFunction<'_, '_, 'tcx, Q>
+where
+ Q: Qualif,
+{
+ fn visit_operand(&mut self, operand: &mir::Operand<'tcx>, location: Location) {
+ self.super_operand(operand, location);
+
+ if !Q::IS_CLEARED_ON_MOVE {
+ return;
+ }
+
+ // If a local with no projections is moved from (e.g. `x` in `y = x`), record that
+ // it no longer needs to be dropped.
+ if let mir::Operand::Move(place) = operand {
+ if let Some(local) = place.as_local() {
+ // For backward compatibility with the MaybeMutBorrowedLocals used in an earlier
+ // implementation we retain qualif if a local had been borrowed before. This might
+ // not be strictly necessary since the local is no longer initialized.
+ if !self.state.borrow.contains(local) {
+ self.state.qualif.remove(local);
+ }
+ }
+ }
+ }
+
+ fn visit_assign(
+ &mut self,
+ place: &mir::Place<'tcx>,
+ rvalue: &mir::Rvalue<'tcx>,
+ location: Location,
+ ) {
+ let qualif =
+ qualifs::in_rvalue::<Q, _>(self.ccx, &mut |l| self.state.qualif.contains(l), rvalue);
+ if !place.is_indirect() {
+ self.assign_qualif_direct(place, qualif);
+ }
+
+ // We need to assign qualifs to the left-hand side before visiting `rvalue` since
+ // qualifs can be cleared on move.
+ self.super_assign(place, rvalue, location);
+ }
+
+ fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
+ self.super_rvalue(rvalue, location);
+
+ match rvalue {
+ mir::Rvalue::AddressOf(mt, borrowed_place) => {
+ if !borrowed_place.is_indirect()
+ && self.address_of_allows_mutation(*mt, *borrowed_place)
+ {
+ let place_ty = borrowed_place.ty(self.ccx.body, self.ccx.tcx).ty;
+ if Q::in_any_value_of_ty(self.ccx, place_ty) {
+ self.state.qualif.insert(borrowed_place.local);
+ self.state.borrow.insert(borrowed_place.local);
+ }
+ }
+ }
+
+ mir::Rvalue::Ref(_, kind, borrowed_place) => {
+ if !borrowed_place.is_indirect() && self.ref_allows_mutation(*kind, *borrowed_place)
+ {
+ let place_ty = borrowed_place.ty(self.ccx.body, self.ccx.tcx).ty;
+ if Q::in_any_value_of_ty(self.ccx, place_ty) {
+ self.state.qualif.insert(borrowed_place.local);
+ self.state.borrow.insert(borrowed_place.local);
+ }
+ }
+ }
+
+ mir::Rvalue::Cast(..)
+ | mir::Rvalue::ShallowInitBox(..)
+ | mir::Rvalue::Use(..)
+ | mir::Rvalue::CopyForDeref(..)
+ | mir::Rvalue::ThreadLocalRef(..)
+ | mir::Rvalue::Repeat(..)
+ | mir::Rvalue::Len(..)
+ | mir::Rvalue::BinaryOp(..)
+ | mir::Rvalue::CheckedBinaryOp(..)
+ | mir::Rvalue::NullaryOp(..)
+ | mir::Rvalue::UnaryOp(..)
+ | mir::Rvalue::Discriminant(..)
+ | mir::Rvalue::Aggregate(..) => {}
+ }
+ }
+
+ fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
+ match statement.kind {
+ StatementKind::StorageDead(local) => {
+ self.state.qualif.remove(local);
+ self.state.borrow.remove(local);
+ }
+ _ => self.super_statement(statement, location),
+ }
+ }
+
+ fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) {
+ // The effect of assignment to the return place in `TerminatorKind::Call` is not applied
+ // here; that occurs in `apply_call_return_effect`.
+
+ if let mir::TerminatorKind::DropAndReplace { value, place, .. } = &terminator.kind {
+ let qualif = qualifs::in_operand::<Q, _>(
+ self.ccx,
+ &mut |l| self.state.qualif.contains(l),
+ value,
+ );
+
+ if !place.is_indirect() {
+ self.assign_qualif_direct(place, qualif);
+ }
+ }
+
+ // We ignore borrow on drop because custom drop impls are not allowed in consts.
+ // FIXME: Reconsider if accounting for borrows in drops is necessary for const drop.
+
+ // We need to assign qualifs to the dropped location before visiting the operand that
+ // replaces it since qualifs can be cleared on move.
+ self.super_terminator(terminator, location);
+ }
+}
+
+/// The dataflow analysis used to propagate qualifs on arbitrary CFGs.
+pub(super) struct FlowSensitiveAnalysis<'a, 'mir, 'tcx, Q> {
+ ccx: &'a ConstCx<'mir, 'tcx>,
+ _qualif: PhantomData<Q>,
+}
+
+impl<'a, 'mir, 'tcx, Q> FlowSensitiveAnalysis<'a, 'mir, 'tcx, Q>
+where
+ Q: Qualif,
+{
+ pub(super) fn new(_: Q, ccx: &'a ConstCx<'mir, 'tcx>) -> Self {
+ FlowSensitiveAnalysis { ccx, _qualif: PhantomData }
+ }
+
+ fn transfer_function(&self, state: &'a mut State) -> TransferFunction<'a, 'mir, 'tcx, Q> {
+ TransferFunction::<Q>::new(self.ccx, state)
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub(super) struct State {
+ /// Describes whether a local contains qualif.
+ pub qualif: BitSet<Local>,
+ /// Describes whether a local's address escaped and it might become qualified as a result an
+ /// indirect mutation.
+ pub borrow: BitSet<Local>,
+}
+
+impl Clone for State {
+ fn clone(&self) -> Self {
+ State { qualif: self.qualif.clone(), borrow: self.borrow.clone() }
+ }
+
+ // Data flow engine when possible uses `clone_from` for domain values.
+ // Providing an implementation will avoid some intermediate memory allocations.
+ fn clone_from(&mut self, other: &Self) {
+ self.qualif.clone_from(&other.qualif);
+ self.borrow.clone_from(&other.borrow);
+ }
+}
+
+impl State {
+ #[inline]
+ pub(super) fn contains(&self, local: Local) -> bool {
+ self.qualif.contains(local)
+ }
+}
+
+impl<C> DebugWithContext<C> for State {
+ fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("qualif: ")?;
+ self.qualif.fmt_with(ctxt, f)?;
+ f.write_str(" borrow: ")?;
+ self.borrow.fmt_with(ctxt, f)?;
+ Ok(())
+ }
+
+ fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if self == old {
+ return Ok(());
+ }
+
+ if self.qualif != old.qualif {
+ f.write_str("qualif: ")?;
+ self.qualif.fmt_diff_with(&old.qualif, ctxt, f)?;
+ f.write_str("\n")?;
+ }
+
+ if self.borrow != old.borrow {
+ f.write_str("borrow: ")?;
+ self.qualif.fmt_diff_with(&old.borrow, ctxt, f)?;
+ f.write_str("\n")?;
+ }
+
+ Ok(())
+ }
+}
+
+impl JoinSemiLattice for State {
+ fn join(&mut self, other: &Self) -> bool {
+ self.qualif.join(&other.qualif) || self.borrow.join(&other.borrow)
+ }
+}
+
+impl<'tcx, Q> AnalysisDomain<'tcx> for FlowSensitiveAnalysis<'_, '_, 'tcx, Q>
+where
+ Q: Qualif,
+{
+ type Domain = State;
+
+ const NAME: &'static str = Q::ANALYSIS_NAME;
+
+ fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
+ State {
+ qualif: BitSet::new_empty(body.local_decls.len()),
+ borrow: BitSet::new_empty(body.local_decls.len()),
+ }
+ }
+
+ fn initialize_start_block(&self, _body: &mir::Body<'tcx>, state: &mut Self::Domain) {
+ self.transfer_function(state).initialize_state();
+ }
+}
+
+impl<'tcx, Q> Analysis<'tcx> for FlowSensitiveAnalysis<'_, '_, 'tcx, Q>
+where
+ Q: Qualif,
+{
+ fn apply_statement_effect(
+ &self,
+ state: &mut Self::Domain,
+ statement: &mir::Statement<'tcx>,
+ location: Location,
+ ) {
+ self.transfer_function(state).visit_statement(statement, location);
+ }
+
+ fn apply_terminator_effect(
+ &self,
+ state: &mut Self::Domain,
+ terminator: &mir::Terminator<'tcx>,
+ location: Location,
+ ) {
+ self.transfer_function(state).visit_terminator(terminator, location);
+ }
+
+ fn apply_call_return_effect(
+ &self,
+ state: &mut Self::Domain,
+ block: BasicBlock,
+ return_places: CallReturnPlaces<'_, 'tcx>,
+ ) {
+ self.transfer_function(state).apply_call_return_effect(block, return_places)
+ }
+}