diff options
Diffstat (limited to 'compiler/rustc_const_eval')
17 files changed, 225 insertions, 523 deletions
diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 8a6dfb89249..2dc9d57e3b5 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -134,9 +134,6 @@ const_eval_incompatible_return_types = const_eval_incompatible_types = calling a function with argument of type {$callee_ty} passing data of type {$caller_ty} -const_eval_interior_mutability_borrow = - cannot borrow here, since the borrowed element may contain interior mutability - const_eval_interior_mutable_data_refer = {const_eval_const_context}s cannot refer to interior mutable data .label = this borrow of an interior mutable value may end up in the final value @@ -230,9 +227,6 @@ const_eval_memory_exhausted = const_eval_modified_global = modifying a static's initial value from another static's initializer -const_eval_mut_deref = - mutation through a reference is not allowed in {const_eval_const_context}s - const_eval_mutable_ptr_in_final = encountered mutable pointer in final value of {const_eval_intern_kind} const_eval_nested_static_in_thread_local = #[thread_local] does not support implicit nested statics, please create explicit static items and refer to them instead @@ -363,10 +357,6 @@ const_eval_too_generic = const_eval_too_many_caller_args = calling a function with more arguments than it expected -const_eval_transient_mut_borrow = mutable references are not allowed in {const_eval_const_context}s - -const_eval_transient_mut_raw = raw mutable pointers are not allowed in {const_eval_const_context}s - const_eval_try_block_from_output_non_const = `try` block cannot convert `{$ty}` to the result in {const_eval_const_context}s const_eval_unallowed_fn_pointer_call = function pointer calls are not allowed in {const_eval_const_context}s diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 8be327a8b56..6d09ed5b4bf 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -11,18 +11,17 @@ use rustc_hir::{self as hir, LangItem}; use rustc_index::bit_set::BitSet; use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::ObligationCause; -use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor}; +use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::*; use rustc_middle::span_bug; use rustc_middle::ty::adjustment::PointerCoercion; -use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TypeVisitableExt}; use rustc_mir_dataflow::impls::MaybeStorageLive; use rustc_mir_dataflow::storage::always_storage_live_locals; use rustc_mir_dataflow::Analysis; use rustc_span::{sym, Span, Symbol, DUMMY_SP}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::traits::{self, ObligationCauseCode, ObligationCtxt}; -use rustc_type_ir::visit::{TypeSuperVisitable, TypeVisitor}; use tracing::{debug, instrument, trace}; use super::ops::{self, NonConstOp, Status}; @@ -166,24 +165,6 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> { } } -struct LocalReturnTyVisitor<'ck, 'mir, 'tcx> { - kind: LocalKind, - checker: &'ck mut Checker<'mir, 'tcx>, -} - -impl<'ck, 'mir, 'tcx> TypeVisitor<TyCtxt<'tcx>> for LocalReturnTyVisitor<'ck, 'mir, 'tcx> { - fn visit_ty(&mut self, t: Ty<'tcx>) { - match t.kind() { - ty::FnPtr(..) => {} - ty::Ref(_, _, hir::Mutability::Mut) => { - self.checker.check_op(ops::mut_ref::MutRef(self.kind)); - t.super_visit_with(self) - } - _ => t.super_visit_with(self), - } - } -} - pub struct Checker<'mir, 'tcx> { ccx: &'mir ConstCx<'mir, 'tcx>, qualifs: Qualifs<'mir, 'tcx>, @@ -230,25 +211,6 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { 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 { - 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 = self.ccx.fn_sig().output(); - self.check_local_or_return_ty(return_ty.skip_binder(), RETURN_PLACE); - } - if !tcx.has_attr(def_id, sym::rustc_do_not_const_check) { self.visit_body(body); } @@ -358,16 +320,11 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { 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); - - let mut visitor = LocalReturnTyVisitor { kind, checker: self }; - - visitor.visit_ty(ty); - } - - fn check_mut_borrow(&mut self, place: &Place<'_>, kind: hir::BorrowKind) { - match self.const_kind() { + /// Returns whether this place can possibly escape the evaluation of the current const/static + /// initializer. The check assumes that all already existing pointers and references point to + /// non-escaping places. + fn place_may_escape(&mut self, place: &Place<'_>) -> bool { + let is_transient = 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). @@ -375,7 +332,7 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { // 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)), + hir::ConstContext::ConstFn => true, _ => { // For indirect places, we are not creating a new permanent borrow, it's just as // transient as the already existing one. For reborrowing references this is handled @@ -387,15 +344,16 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { // value of the constant. // 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. - // The good news is that interning will detect if any unexpected mutable - // pointer slips through. - if place.is_indirect() || self.local_is_transient(place.local) { - self.check_op(ops::TransientMutBorrow(kind)); - } else { - self.check_op(ops::MutBorrow(kind)); - } + // If anything slips through, there's no safety net -- safe code can create + // references to variants of `!Freeze` enums as long as that variant is `Freeze`, so + // interning can't protect us here. (There *is* a safety net for mutable references + // though, interning will ICE if we miss something here.) + place.is_indirect() || self.local_is_transient(place.local) } - } + }; + // Transient places cannot possibly escape because the place doesn't exist any more at the + // end of evaluation. + !is_transient } } @@ -420,47 +378,6 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { 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. - // FIXME: this does not actually handle all reborrows. It only detects cases where `*` is the outermost - // projection of the borrowed place, it skips deref'ing raw pointers and it skips `static`. - // All those cases are handled below with shared/mutable borrows. - // Once `const_mut_refs` is stable, we should be able to entirely remove this special case. - // (`const_refs_to_cell` is not needed, we already allow all borrows of indirect places anyway.) - 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::Fake(_) => { - PlaceContext::NonMutatingUse(NonMutatingUseContext::FakeBorrow) - } - 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::RawPtr(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::RawBorrow) - } - Mutability::Mut => PlaceContext::MutatingUse(MutatingUseContext::RawBorrow), - }; - 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 { @@ -494,15 +411,12 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { let is_allowed = self.const_kind() == hir::ConstContext::Static(hir::Mutability::Mut); - if !is_allowed { - self.check_mut_borrow( - place, - if matches!(rvalue, Rvalue::Ref(..)) { - hir::BorrowKind::Ref - } else { - hir::BorrowKind::Raw - }, - ); + if !is_allowed && self.place_may_escape(place) { + self.check_op(ops::EscapingMutBorrow(if matches!(rvalue, Rvalue::Ref(..)) { + hir::BorrowKind::Ref + } else { + hir::BorrowKind::Raw + })); } } @@ -514,39 +428,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { place.as_ref(), ); - // If the place is indirect, this is basically a reborrow. We have a reborrow - // special case above, but for raw pointers and pointers/references to `static` and - // when the `*` is not the first projection, `place_as_reborrow` does not recognize - // them as such, so we end up here. This should probably be considered a - // `TransientCellBorrow` (we consider the equivalent mutable case a - // `TransientMutBorrow`), but such reborrows got accidentally stabilized already and - // it is too much of a breaking change to take back. - if borrowed_place_has_mut_interior && !place.is_indirect() { - 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. - // The good news is that interning will detect if any unexpected mutable - // pointer slips through. - if self.local_is_transient(place.local) { - self.check_op(ops::TransientCellBorrow); - } else { - self.check_op(ops::CellBorrow); - } - } - } + if borrowed_place_has_mut_interior && self.place_may_escape(place) { + self.check_op(ops::EscapingCellBorrow); } } @@ -635,58 +518,6 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } } } - fn visit_projection_elem( - &mut self, - place_ref: PlaceRef<'tcx>, - elem: PlaceElem<'tcx>, - context: PlaceContext, - location: Location, - ) { - trace!( - "visit_projection_elem: place_ref={:?} elem={:?} \ - context={:?} location={:?}", - place_ref, elem, context, location, - ); - - self.super_projection_elem(place_ref, elem, context, location); - - match elem { - ProjectionElem::Deref => { - let base_ty = place_ref.ty(self.body, self.tcx).ty; - if base_ty.is_unsafe_ptr() { - if place_ref.projection.is_empty() { - let decl = &self.body.local_decls[place_ref.local]; - // If this is a static, then this is not really dereferencing a pointer, - // just directly accessing a static. That is not subject to any feature - // gates (except for the one about whether statics can even be used, but - // that is checked already by `visit_operand`). - if let LocalInfo::StaticRef { .. } = *decl.local_info() { - 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::OpaqueCast(..) - | ProjectionElem::Subslice { .. } - | ProjectionElem::Subtype(..) - | ProjectionElem::Field(..) - | ProjectionElem::Index(_) => {} - } - } fn visit_source_info(&mut self, source_info: &SourceInfo) { trace!("visit_source_info: source_info={:?}", source_info); @@ -983,40 +814,6 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } } -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)) => { - // FIXME: why do statics and raw pointers get excluded here? This makes - // some code involving mutable pointers unstable, but it is unclear - // why that code is treated differently from mutable references. - // Once TransientMutBorrow and TransientCellBorrow are stable, - // this can probably be cleaned up without any behavioral changes. - - // 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_float_or_char(ty: Ty<'_>) -> bool { ty.is_bool() || ty.is_integral() || ty.is_char() || ty.is_floating_point() } diff --git a/compiler/rustc_const_eval/src/check_consts/ops.rs b/compiler/rustc_const_eval/src/check_consts/ops.rs index e8f10c88408..a52fc6a077b 100644 --- a/compiler/rustc_const_eval/src/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/check_consts/ops.rs @@ -8,7 +8,7 @@ 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::{self, CallSource}; +use rustc_middle::mir::CallSource; use rustc_middle::span_bug; use rustc_middle::ty::print::{with_no_trimmed_paths, PrintTraitRefExt as _}; use rustc_middle::ty::{ @@ -392,26 +392,11 @@ impl<'tcx> NonConstOp<'tcx> for LiveDrop<'tcx> { } #[derive(Debug)] -/// A borrow of a type that contains an `UnsafeCell` somewhere. The borrow never escapes to -/// the final value of the constant. -pub(crate) struct TransientCellBorrow; -impl<'tcx> NonConstOp<'tcx> for TransientCellBorrow { - fn status_in_item(&self, _: &ConstCx<'_, 'tcx>) -> Status { - Status::Unstable(sym::const_refs_to_cell) - } - fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> { - ccx.tcx - .sess - .create_feature_err(errors::InteriorMutabilityBorrow { span }, sym::const_refs_to_cell) - } -} - -#[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(crate) struct CellBorrow; -impl<'tcx> NonConstOp<'tcx> for CellBorrow { +pub(crate) struct EscapingCellBorrow; +impl<'tcx> NonConstOp<'tcx> for EscapingCellBorrow { fn importance(&self) -> DiagImportance { // Most likely the code will try to do mutation with these borrows, which // triggers its own errors. Only show this one if that does not happen. @@ -431,9 +416,9 @@ impl<'tcx> NonConstOp<'tcx> for CellBorrow { /// 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(crate) struct MutBorrow(pub hir::BorrowKind); +pub(crate) struct EscapingMutBorrow(pub hir::BorrowKind); -impl<'tcx> NonConstOp<'tcx> for MutBorrow { +impl<'tcx> NonConstOp<'tcx> for EscapingMutBorrow { fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status { Status::Forbidden } @@ -460,49 +445,6 @@ impl<'tcx> NonConstOp<'tcx> for MutBorrow { } } -#[derive(Debug)] -pub(crate) 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) -> Diag<'tcx> { - let kind = ccx.const_kind(); - match self.0 { - hir::BorrowKind::Raw => ccx - .tcx - .sess - .create_feature_err(errors::TransientMutRawErr { span, kind }, sym::const_mut_refs), - hir::BorrowKind::Ref => ccx.tcx.sess.create_feature_err( - errors::TransientMutBorrowErr { span, kind }, - sym::const_mut_refs, - ), - } - } -} - -#[derive(Debug)] -pub(crate) 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) -> DiagImportance { - // Usually a side-effect of a `TransientMutBorrow` somewhere. - DiagImportance::Secondary - } - - fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> { - ccx.tcx.sess.create_feature_err( - errors::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(crate) struct PanicNonStr; @@ -524,24 +466,6 @@ impl<'tcx> NonConstOp<'tcx> for RawPtrComparison { } } -#[derive(Debug)] -pub(crate) struct RawMutPtrDeref; -impl<'tcx> NonConstOp<'tcx> for RawMutPtrDeref { - fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status { - Status::Unstable(sym::const_mut_refs) - } - - #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable - fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> { - feature_err( - &ccx.tcx.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. @@ -588,33 +512,3 @@ impl<'tcx> NonConstOp<'tcx> for ThreadLocalAccess { ccx.dcx().create_err(errors::ThreadLocalAccessErr { span }) } } - -/// Types that cannot appear in the signature or locals of a `const fn`. -pub(crate) mod mut_ref { - use super::*; - - #[derive(Debug)] - pub(crate) 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) -> DiagImportance { - match self.0 { - mir::LocalKind::Temp => DiagImportance::Secondary, - mir::LocalKind::ReturnPointer | mir::LocalKind::Arg => DiagImportance::Primary, - } - } - - #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable - fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> { - feature_err( - &ccx.tcx.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/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs index 7ccebd83f24..337eab7a1c2 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -10,7 +10,6 @@ use rustc_middle::traits::Reveal; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_session::lint; use rustc_span::def_id::LocalDefId; use rustc_span::{Span, DUMMY_SP}; use rustc_target::abi::{self, Abi}; @@ -18,13 +17,12 @@ use tracing::{debug, instrument, trace}; use super::{CanAccessMutGlobal, CompileTimeInterpCx, CompileTimeMachine}; use crate::const_eval::CheckAlignment; -use crate::errors::{self, ConstEvalError, DanglingPtrInFinal}; use crate::interpret::{ create_static_alloc, eval_nullary_intrinsic, intern_const_alloc_recursive, throw_exhaust, CtfeValidationMode, GlobalId, Immediate, InternKind, InternResult, InterpCx, InterpError, InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking, StackPopCleanup, }; -use crate::CTRL_C_RECEIVED; +use crate::{errors, CTRL_C_RECEIVED}; // Returns a pointer to where the result lives #[instrument(level = "trace", skip(ecx, body))] @@ -75,12 +73,7 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>( // This can't use `init_stack_frame` since `body` is not a function, // so computing its ABI would fail. It's also not worth it since there are no arguments to pass. - ecx.push_stack_frame_raw( - cid.instance, - body, - &ret.clone().into(), - StackPopCleanup::Root { cleanup: false }, - )?; + ecx.push_stack_frame_raw(cid.instance, body, &ret, StackPopCleanup::Root { cleanup: false })?; ecx.storage_live_for_always_live_locals()?; // The main interpreter loop. @@ -105,18 +98,15 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>( return Err(ecx .tcx .dcx() - .emit_err(DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind }) + .emit_err(errors::DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind }) .into()); } Err(InternResult::FoundBadMutablePointer) => { - // only report mutable pointers if there were no dangling pointers - let err_diag = errors::MutablePtrInFinal { span: ecx.tcx.span, kind: intern_kind }; - ecx.tcx.emit_node_span_lint( - lint::builtin::CONST_EVAL_MUTABLE_PTR_IN_FINAL_VALUE, - ecx.machine.best_lint_scope(*ecx.tcx), - err_diag.span, - err_diag, - ) + return Err(ecx + .tcx + .dcx() + .emit_err(errors::MutablePtrInFinal { span: ecx.tcx.span, kind: intern_kind }) + .into()); } } @@ -448,7 +438,12 @@ fn report_eval_error<'tcx>( error, DUMMY_SP, || super::get_span_and_frames(ecx.tcx, ecx.stack()), - |span, frames| ConstEvalError { span, error_kind: kind, instance, frame_notes: frames }, + |span, frames| errors::ConstEvalError { + span, + error_kind: kind, + instance, + frame_notes: frames, + }, ) } diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 7405ca09342..6a691abae02 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -718,16 +718,29 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { _kind: mir::RetagKind, val: &ImmTy<'tcx, CtfeProvenance>, ) -> InterpResult<'tcx, ImmTy<'tcx, CtfeProvenance>> { - // If it's a frozen shared reference that's not already immutable, make it immutable. + // If it's a frozen shared reference that's not already immutable, potentially make it immutable. // (Do nothing on `None` provenance, that cannot store immutability anyway.) if let ty::Ref(_, ty, mutbl) = val.layout.ty.kind() && *mutbl == Mutability::Not - && val.to_scalar_and_meta().0.to_pointer(ecx)?.provenance.is_some_and(|p| !p.immutable()) - // That next check is expensive, that's why we have all the guards above. - && ty.is_freeze(*ecx.tcx, ecx.param_env) + && val + .to_scalar_and_meta() + .0 + .to_pointer(ecx)? + .provenance + .is_some_and(|p| !p.immutable()) { + // That next check is expensive, that's why we have all the guards above. + let is_immutable = ty.is_freeze(*ecx.tcx, ecx.param_env); let place = ecx.ref_to_mplace(val)?; - let new_place = place.map_provenance(CtfeProvenance::as_immutable); + let new_place = if is_immutable { + place.map_provenance(CtfeProvenance::as_immutable) + } else { + // Even if it is not immutable, remember that it is a shared reference. + // This allows it to become part of the final value of the constant. + // (See <https://github.com/rust-lang/rust/pull/128543> for why we allow this + // even when there is interior mutability.) + place.map_provenance(CtfeProvenance::as_shared_ref) + }; Ok(ImmTy::from_immediate(new_place.to_ref(ecx), val.layout)) } else { Ok(val.clone()) diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 0b366b43f95..b66b5c0a00a 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -35,13 +35,10 @@ pub(crate) struct NestedStaticInThreadLocal { pub span: Span, } -#[derive(LintDiagnostic)] +#[derive(Diagnostic)] #[diag(const_eval_mutable_ptr_in_final)] pub(crate) struct MutablePtrInFinal { - // rust-lang/rust#122153: This was marked as `#[primary_span]` under - // `derive(Diagnostic)`. Since we expect we may hard-error in future, we are - // keeping the field (and skipping it under `derive(LintDiagnostic)`). - #[skip_arg] + #[primary_span] pub span: Span, pub kind: InternKind, } @@ -97,30 +94,6 @@ pub(crate) struct PanicNonStrErr { } #[derive(Diagnostic)] -#[diag(const_eval_mut_deref, code = E0658)] -pub(crate) struct MutDerefErr { - #[primary_span] - pub span: Span, - pub kind: ConstContext, -} - -#[derive(Diagnostic)] -#[diag(const_eval_transient_mut_borrow, code = E0658)] -pub(crate) struct TransientMutBorrowErr { - #[primary_span] - pub span: Span, - pub kind: ConstContext, -} - -#[derive(Diagnostic)] -#[diag(const_eval_transient_mut_raw, code = E0658)] -pub(crate) struct TransientMutRawErr { - #[primary_span] - pub span: Span, - pub kind: ConstContext, -} - -#[derive(Diagnostic)] #[diag(const_eval_max_num_nodes_in_const)] pub(crate) struct MaxNumNodesInConstErr { #[primary_span] @@ -220,13 +193,6 @@ pub(crate) struct InteriorMutableDataRefer { pub teach: bool, } -#[derive(Diagnostic)] -#[diag(const_eval_interior_mutability_borrow)] -pub(crate) struct InteriorMutabilityBorrow { - #[primary_span] - pub span: Span, -} - #[derive(LintDiagnostic)] #[diag(const_eval_long_running)] #[note] diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 568a9a3a637..0f2b22f035b 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -235,13 +235,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { if self.layout_compat(caller_abi.layout, callee_abi.layout)? { // Ensure that our checks imply actual ABI compatibility for this concrete call. assert!(caller_abi.eq_abi(callee_abi)); - return Ok(true); + Ok(true) } else { trace!( "check_argument_compat: incompatible ABIs:\ncaller: {:?}\ncallee: {:?}", caller_abi, callee_abi ); - return Ok(false); + Ok(false) } } @@ -823,7 +823,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { (Abi::Rust, fn_abi), &[FnArg::Copy(arg.into())], false, - &ret.into(), + &ret, Some(target), unwind, ) diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index dd744c51f23..16d40fcceb6 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -16,7 +16,7 @@ use rustc_span::Span; use rustc_target::abi::call::FnAbi; use rustc_target::abi::{Align, HasDataLayout, Size, TargetDataLayout}; use rustc_trait_selection::traits::ObligationCtxt; -use tracing::{debug, trace}; +use tracing::{debug, instrument, trace}; use super::{ err_inval, throw_inval, throw_ub, throw_ub_custom, Frame, FrameInfo, GlobalId, InterpErrorInfo, @@ -315,6 +315,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { /// Check if the two things are equal in the current param_env, using an infctx to get proper /// equality checks. + #[instrument(level = "trace", skip(self), ret)] pub(super) fn eq_in_param_env<T>(&self, a: T, b: T) -> bool where T: PartialEq + TypeFoldable<TyCtxt<'tcx>> + ToTrace<'tcx>, @@ -330,13 +331,20 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // equate the two trait refs after normalization let a = ocx.normalize(&cause, self.param_env, a); let b = ocx.normalize(&cause, self.param_env, b); - if ocx.eq(&cause, self.param_env, a, b).is_ok() { - if ocx.select_all_or_error().is_empty() { - // All good. - return true; - } + + if let Err(terr) = ocx.eq(&cause, self.param_env, a, b) { + trace!(?terr); + return false; + } + + let errors = ocx.select_all_or_error(); + if !errors.is_empty() { + trace!(?errors); + return false; } - return false; + + // All good. + true } /// Walks up the callstack from the intrinsic's callsite, searching for the first callsite in a diff --git a/compiler/rustc_const_eval/src/interpret/intern.rs b/compiler/rustc_const_eval/src/interpret/intern.rs index 8b0a2afa4d6..5df989b4c1d 100644 --- a/compiler/rustc_const_eval/src/interpret/intern.rs +++ b/compiler/rustc_const_eval/src/interpret/intern.rs @@ -20,6 +20,7 @@ use rustc_hir as hir; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs; use rustc_middle::mir::interpret::{ConstAllocation, CtfeProvenance, InterpResult}; use rustc_middle::query::TyCtxtAt; +use rustc_middle::span_bug; use rustc_middle::ty::layout::TyAndLayout; use rustc_span::def_id::LocalDefId; use rustc_span::sym; @@ -223,37 +224,52 @@ pub fn intern_const_alloc_recursive<'tcx, M: CompileTimeMachine<'tcx, const_eval continue; } - // Crucially, we check this *before* checking whether the `alloc_id` - // has already been interned. The point of this check is to ensure that when - // there are multiple pointers to the same allocation, they are *all* immutable. - // Therefore it would be bad if we only checked the first pointer to any given - // allocation. + // Ensure that this is derived from a shared reference. Crucially, we check this *before* + // checking whether the `alloc_id` has already been interned. The point of this check is to + // ensure that when there are multiple pointers to the same allocation, they are *all* + // derived from a shared reference. Therefore it would be bad if we only checked the first + // pointer to any given allocation. // (It is likely not possible to actually have multiple pointers to the same allocation, // so alternatively we could also check that and ICE if there are multiple such pointers.) + // See <https://github.com/rust-lang/rust/pull/128543> for why we are checking for "shared + // reference" and not "immutable", i.e., for why we are allowing interior-mutable shared + // references: they can actually be created in safe code while pointing to apparently + // "immutable" values, via promotion or tail expression lifetime extension of + // `&None::<Cell<T>>`. + // We also exclude promoteds from this as `&mut []` can be promoted, which is a mutable + // reference pointing to an immutable (zero-sized) allocation. We rely on the promotion + // analysis not screwing up to ensure that it is sound to intern promoteds as immutable. if intern_kind != InternKind::Promoted && inner_mutability == Mutability::Not - && !prov.immutable() + && !prov.shared_ref() { - if ecx.tcx.try_get_global_alloc(alloc_id).is_some() - && !just_interned.contains(&alloc_id) - { + let is_already_global = ecx.tcx.try_get_global_alloc(alloc_id).is_some(); + if is_already_global && !just_interned.contains(&alloc_id) { // This is a pointer to some memory from another constant. We encounter mutable // pointers to such memory since we do not always track immutability through // these "global" pointers. Allowing them is harmless; the point of these checks // during interning is to justify why we intern the *new* allocations immutably, - // so we can completely ignore existing allocations. We also don't need to add - // this to the todo list, since after all it is already interned. + // so we can completely ignore existing allocations. + // We can also skip the rest of this loop iteration, since after all it is already + // interned. continue; } - // Found a mutable pointer inside a const where inner allocations should be - // immutable. We exclude promoteds from this, since things like `&mut []` and - // `&None::<Cell<i32>>` lead to promotion that can produce mutable pointers. We rely - // on the promotion analysis not screwing up to ensure that it is sound to intern - // promoteds as immutable. - trace!("found bad mutable pointer"); - // Prefer dangling pointer errors over mutable pointer errors - if result.is_ok() { - result = Err(InternResult::FoundBadMutablePointer); + // If this is a dangling pointer, that's actually fine -- the problematic case is + // when there is memory there that someone might expect to be mutable, but we make it immutable. + let dangling = !is_already_global && !ecx.memory.alloc_map.contains_key(&alloc_id); + if !dangling { + // Found a mutable reference inside a const where inner allocations should be + // immutable. + if !ecx.tcx.sess.opts.unstable_opts.unleash_the_miri_inside_of_you { + span_bug!( + ecx.tcx.span, + "the static const safety checks accepted mutable references they should not have accepted" + ); + } + // Prefer dangling pointer errors over mutable pointer errors + if result.is_ok() { + result = Err(InternResult::FoundBadMutablePointer); + } } } if ecx.tcx.try_get_global_alloc(alloc_id).is_some() { @@ -261,7 +277,6 @@ pub fn intern_const_alloc_recursive<'tcx, M: CompileTimeMachine<'tcx, const_eval debug_assert!(!ecx.memory.alloc_map.contains_key(&alloc_id)); continue; } - just_interned.insert(alloc_id); // We always intern with `inner_mutability`, and furthermore we ensured above that if // that is "immutable", then there are *no* mutable pointers anywhere in the newly // interned memory -- justifying that we can indeed intern immutably. However this also @@ -272,6 +287,7 @@ pub fn intern_const_alloc_recursive<'tcx, M: CompileTimeMachine<'tcx, const_eval // pointers before deciding which allocations can be made immutable; but for now we are // okay with losing some potential for immutability here. This can anyway only affect // `static mut`. + just_interned.insert(alloc_id); match intern_shallow(ecx, alloc_id, inner_mutability) { Ok(nested) => todo.extend(nested), Err(()) => { diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index bedc56de0da..8a07f90c951 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -384,8 +384,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { sym::simd_insert => { let index = u64::from(self.read_scalar(&args[1])?.to_u32()?); let elem = &args[2]; - let (input, input_len) = self.operand_to_simd(&args[0])?; - let (dest, dest_len) = self.mplace_to_simd(dest)?; + let (input, input_len) = self.project_to_simd(&args[0])?; + let (dest, dest_len) = self.project_to_simd(dest)?; assert_eq!(input_len, dest_len, "Return vector length must match input length"); // Bounds are not checked by typeck so we have to do it ourselves. if index >= input_len { @@ -406,7 +406,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } sym::simd_extract => { let index = u64::from(self.read_scalar(&args[1])?.to_u32()?); - let (input, input_len) = self.operand_to_simd(&args[0])?; + let (input, input_len) = self.project_to_simd(&args[0])?; // Bounds are not checked by typeck so we have to do it ourselves. if index >= input_len { throw_ub_format!( diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index a65637f4978..2ad675025a2 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -7,7 +7,7 @@ //! short-circuiting the empty case! use std::assert_matches::assert_matches; -use std::borrow::Cow; +use std::borrow::{Borrow, Cow}; use std::collections::VecDeque; use std::{fmt, mem, ptr}; @@ -386,12 +386,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { size: Size, ) -> InterpResult<'tcx, Option<(AllocId, Size, M::ProvenanceExtra)>> { let size = i64::try_from(size.bytes()).unwrap(); // it would be an error to even ask for more than isize::MAX bytes - self.check_and_deref_ptr( + Self::check_and_deref_ptr( + self, ptr, size, CheckInAllocMsg::MemoryAccessTest, - |alloc_id, offset, prov| { - let (size, align) = self + |this, alloc_id, offset, prov| { + let (size, align) = this .get_live_alloc_size_and_align(alloc_id, CheckInAllocMsg::MemoryAccessTest)?; Ok((size, align, (alloc_id, offset, prov))) }, @@ -408,8 +409,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { msg: CheckInAllocMsg, ) -> InterpResult<'tcx> { let size = i64::try_from(size.bytes()).unwrap(); // it would be an error to even ask for more than isize::MAX bytes - self.check_and_deref_ptr(ptr, size, msg, |alloc_id, _, _| { - let (size, align) = self.get_live_alloc_size_and_align(alloc_id, msg)?; + Self::check_and_deref_ptr(self, ptr, size, msg, |this, alloc_id, _, _| { + let (size, align) = this.get_live_alloc_size_and_align(alloc_id, msg)?; Ok((size, align, ())) })?; Ok(()) @@ -424,8 +425,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { size: i64, msg: CheckInAllocMsg, ) -> InterpResult<'tcx> { - self.check_and_deref_ptr(ptr, size, msg, |alloc_id, _, _| { - let (size, align) = self.get_live_alloc_size_and_align(alloc_id, msg)?; + Self::check_and_deref_ptr(self, ptr, size, msg, |this, alloc_id, _, _| { + let (size, align) = this.get_live_alloc_size_and_align(alloc_id, msg)?; Ok((size, align, ())) })?; Ok(()) @@ -439,12 +440,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { /// `alloc_size` will only get called for non-zero-sized accesses. /// /// Returns `None` if and only if the size is 0. - fn check_and_deref_ptr<T>( - &self, + fn check_and_deref_ptr<T, R: Borrow<Self>>( + this: R, ptr: Pointer<Option<M::Provenance>>, size: i64, msg: CheckInAllocMsg, alloc_size: impl FnOnce( + R, AllocId, Size, M::ProvenanceExtra, @@ -455,13 +457,14 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { return Ok(None); } - Ok(match self.ptr_try_get_alloc_id(ptr, size) { + Ok(match this.borrow().ptr_try_get_alloc_id(ptr, size) { Err(addr) => { // We couldn't get a proper allocation. throw_ub!(DanglingIntPointer { addr, inbounds_size: size, msg }); } Ok((alloc_id, offset, prov)) => { - let (alloc_size, _alloc_align, ret_val) = alloc_size(alloc_id, offset, prov)?; + let tcx = this.borrow().tcx; + let (alloc_size, _alloc_align, ret_val) = alloc_size(this, alloc_id, offset, prov)?; let offset = offset.bytes(); // Compute absolute begin and end of the range. let (begin, end) = if size >= 0 { @@ -475,7 +478,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { throw_ub!(PointerOutOfBounds { alloc_id, alloc_size, - ptr_offset: self.sign_extend_to_target_isize(offset), + ptr_offset: tcx.sign_extend_to_target_isize(offset), inbounds_size: size, msg, }) @@ -669,12 +672,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ) -> InterpResult<'tcx, Option<AllocRef<'a, 'tcx, M::Provenance, M::AllocExtra, M::Bytes>>> { let size_i64 = i64::try_from(size.bytes()).unwrap(); // it would be an error to even ask for more than isize::MAX bytes - let ptr_and_alloc = self.check_and_deref_ptr( + let ptr_and_alloc = Self::check_and_deref_ptr( + self, ptr, size_i64, CheckInAllocMsg::MemoryAccessTest, - |alloc_id, offset, prov| { - let alloc = self.get_alloc_raw(alloc_id)?; + |this, alloc_id, offset, prov| { + let alloc = this.get_alloc_raw(alloc_id)?; Ok((alloc.size(), alloc.align, (alloc_id, offset, prov, alloc))) }, )?; @@ -726,7 +730,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // We have "NLL problem case #3" here, which cannot be worked around without loss of // efficiency even for the common case where the key is in the map. // <https://rust-lang.github.io/rfcs/2094-nll.html#problem-case-3-conditional-control-flow-across-functions> - // (Cannot use `get_mut_or` since `get_global_alloc` needs `&self`.) + // (Cannot use `get_mut_or` since `get_global_alloc` needs `&self`, and that boils down to + // Miri's `adjust_alloc_root_pointer` needing to look up the size of the allocation. + // It could be avoided with a totally separate codepath in Miri for handling the absolute address + // of global allocations, but that's not worth it.) if self.memory.alloc_map.get_mut(id).is_none() { // Slow path. // Allocation not found locally, go look global. @@ -762,13 +769,22 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { size: Size, ) -> InterpResult<'tcx, Option<AllocRefMut<'a, 'tcx, M::Provenance, M::AllocExtra, M::Bytes>>> { - let parts = self.get_ptr_access(ptr, size)?; - if let Some((alloc_id, offset, prov)) = parts { - let tcx = self.tcx; - let validation_in_progress = self.memory.validation_in_progress; - // FIXME: can we somehow avoid looking up the allocation twice here? - // We cannot call `get_raw_mut` inside `check_and_deref_ptr` as that would duplicate `&mut self`. - let (alloc, machine) = self.get_alloc_raw_mut(alloc_id)?; + let tcx = self.tcx; + let validation_in_progress = self.memory.validation_in_progress; + + let size_i64 = i64::try_from(size.bytes()).unwrap(); // it would be an error to even ask for more than isize::MAX bytes + let ptr_and_alloc = Self::check_and_deref_ptr( + self, + ptr, + size_i64, + CheckInAllocMsg::MemoryAccessTest, + |this, alloc_id, offset, prov| { + let (alloc, machine) = this.get_alloc_raw_mut(alloc_id)?; + Ok((alloc.size(), alloc.align, (alloc_id, offset, prov, alloc, machine))) + }, + )?; + + if let Some((alloc_id, offset, prov, alloc, machine)) = ptr_and_alloc { let range = alloc_range(offset, size); if !validation_in_progress { M::before_memory_write(tcx, machine, &mut alloc.extra, (alloc_id, prov), range)?; @@ -1118,7 +1134,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> std::fmt::Debug for DumpAllocs<'a, 'tcx, M> { } /// Reading and writing. -impl<'tcx, 'a, Prov: Provenance, Extra, Bytes: AllocBytes> +impl<'a, 'tcx, Prov: Provenance, Extra, Bytes: AllocBytes> AllocRefMut<'a, 'tcx, Prov, Extra, Bytes> { pub fn as_ref<'b>(&'b self) -> AllocRef<'b, 'tcx, Prov, Extra, Bytes> { @@ -1166,7 +1182,7 @@ impl<'tcx, 'a, Prov: Provenance, Extra, Bytes: AllocBytes> } } -impl<'tcx, 'a, Prov: Provenance, Extra, Bytes: AllocBytes> AllocRef<'a, 'tcx, Prov, Extra, Bytes> { +impl<'a, 'tcx, Prov: Provenance, Extra, Bytes: AllocBytes> AllocRef<'a, 'tcx, Prov, Extra, Bytes> { /// `range` is relative to this allocation reference, not the base of the allocation. pub fn read_scalar( &self, diff --git a/compiler/rustc_const_eval/src/interpret/operand.rs b/compiler/rustc_const_eval/src/interpret/operand.rs index bdbacfd20c2..ead3ef6861a 100644 --- a/compiler/rustc_const_eval/src/interpret/operand.rs +++ b/compiler/rustc_const_eval/src/interpret/operand.rs @@ -433,6 +433,7 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for ImmTy<'tcx, Prov> { Ok(self.offset_(offset, layout, ecx)) } + #[inline(always)] fn to_op<M: Machine<'tcx, Provenance = Prov>>( &self, _ecx: &InterpCx<'tcx, M>, @@ -522,6 +523,7 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for OpTy<'tcx, Prov> { } } + #[inline(always)] fn to_op<M: Machine<'tcx, Provenance = Prov>>( &self, _ecx: &InterpCx<'tcx, M>, @@ -679,30 +681,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { Ok(str) } - /// Converts a repr(simd) operand into an operand where `place_index` accesses the SIMD elements. - /// Also returns the number of elements. - /// - /// Can (but does not always) trigger UB if `op` is uninitialized. - pub fn operand_to_simd( - &self, - op: &OpTy<'tcx, M::Provenance>, - ) -> InterpResult<'tcx, (MPlaceTy<'tcx, M::Provenance>, u64)> { - // Basically we just transmute this place into an array following simd_size_and_type. - // This only works in memory, but repr(simd) types should never be immediates anyway. - assert!(op.layout.ty.is_simd()); - match op.as_mplace_or_imm() { - Left(mplace) => self.mplace_to_simd(&mplace), - Right(imm) => match *imm { - Immediate::Uninit => { - throw_ub!(InvalidUninitBytes(None)) - } - Immediate::Scalar(..) | Immediate::ScalarPair(..) => { - bug!("arrays/slices can never have Scalar/ScalarPair layout") - } - }, - } - } - /// Read from a local of the current frame. /// Will not access memory, instead an indirect `Operand` is returned. /// diff --git a/compiler/rustc_const_eval/src/interpret/operator.rs b/compiler/rustc_const_eval/src/interpret/operator.rs index e9ba12dbcc4..b390bb87789 100644 --- a/compiler/rustc_const_eval/src/interpret/operator.rs +++ b/compiler/rustc_const_eval/src/interpret/operator.rs @@ -303,8 +303,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let pointee_layout = self.layout_of(pointee_ty)?; assert!(pointee_layout.abi.is_sized()); - // We cannot overflow i64 as a type's size must be <= isize::MAX. + // The size always fits in `i64` as it can be at most `isize::MAX`. let pointee_size = i64::try_from(pointee_layout.size.bytes()).unwrap(); + // This uses the same type as `right`, which can be `isize` or `usize`. + // `pointee_size` is guaranteed to fit into both types. let pointee_size = ImmTy::from_int(pointee_size, right.layout); // Multiply element size and element count. let (val, overflowed) = self @@ -316,6 +318,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } let offset_bytes = val.to_target_isize(self)?; + if !right.layout.abi.is_signed() && offset_bytes < 0 { + // We were supposed to do an unsigned offset but the result is negative -- this + // can only mean that the cast wrapped around. + throw_ub!(PointerArithOverflow) + } let offset_ptr = self.ptr_offset_inbounds(ptr, offset_bytes)?; Ok(ImmTy::from_scalar(Scalar::from_maybe_pointer(offset_ptr, self), left.layout)) } diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs index ee8e5419495..e398c4c0742 100644 --- a/compiler/rustc_const_eval/src/interpret/place.rs +++ b/compiler/rustc_const_eval/src/interpret/place.rs @@ -166,6 +166,7 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for MPlaceTy<'tcx, Prov> { Ok(MPlaceTy { mplace: self.mplace.offset_with_meta_(offset, mode, meta, ecx)?, layout }) } + #[inline(always)] fn to_op<M: Machine<'tcx, Provenance = Prov>>( &self, _ecx: &InterpCx<'tcx, M>, @@ -299,6 +300,7 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for PlaceTy<'tcx, Prov> { }) } + #[inline(always)] fn to_op<M: Machine<'tcx, Provenance = Prov>>( &self, ecx: &InterpCx<'tcx, M>, @@ -375,13 +377,15 @@ where Prov: Provenance, M: Machine<'tcx, Provenance = Prov>, { - pub fn ptr_with_meta_to_mplace( + fn ptr_with_meta_to_mplace( &self, ptr: Pointer<Option<M::Provenance>>, meta: MemPlaceMeta<M::Provenance>, layout: TyAndLayout<'tcx>, + unaligned: bool, ) -> MPlaceTy<'tcx, M::Provenance> { - let misaligned = self.is_ptr_misaligned(ptr, layout.align.abi); + let misaligned = + if unaligned { None } else { self.is_ptr_misaligned(ptr, layout.align.abi) }; MPlaceTy { mplace: MemPlace { ptr, meta, misaligned }, layout } } @@ -391,7 +395,16 @@ where layout: TyAndLayout<'tcx>, ) -> MPlaceTy<'tcx, M::Provenance> { assert!(layout.is_sized()); - self.ptr_with_meta_to_mplace(ptr, MemPlaceMeta::None, layout) + self.ptr_with_meta_to_mplace(ptr, MemPlaceMeta::None, layout, /*unaligned*/ false) + } + + pub fn ptr_to_mplace_unaligned( + &self, + ptr: Pointer<Option<M::Provenance>>, + layout: TyAndLayout<'tcx>, + ) -> MPlaceTy<'tcx, M::Provenance> { + assert!(layout.is_sized()); + self.ptr_with_meta_to_mplace(ptr, MemPlaceMeta::None, layout, /*unaligned*/ true) } /// Take a value, which represents a (thin or wide) reference, and make it a place. @@ -412,7 +425,7 @@ where // `ref_to_mplace` is called on raw pointers even if they don't actually get dereferenced; // we hence can't call `size_and_align_of` since that asserts more validity than we want. let ptr = ptr.to_pointer(self)?; - Ok(self.ptr_with_meta_to_mplace(ptr, meta, layout)) + Ok(self.ptr_with_meta_to_mplace(ptr, meta, layout, /*unaligned*/ false)) } /// Turn a mplace into a (thin or wide) mutable raw pointer, pointing to the same space. @@ -482,23 +495,6 @@ where Ok(a) } - /// Converts a repr(simd) place into a place where `place_index` accesses the SIMD elements. - /// Also returns the number of elements. - pub fn mplace_to_simd( - &self, - mplace: &MPlaceTy<'tcx, M::Provenance>, - ) -> InterpResult<'tcx, (MPlaceTy<'tcx, M::Provenance>, u64)> { - // Basically we want to transmute this place into an array following simd_size_and_type. - let (len, e_ty) = mplace.layout.ty.simd_size_and_type(*self.tcx); - // Some SIMD types have padding, so `len` many `e_ty` does not cover the entire place. - // Therefore we cannot transmute, and instead we project at offset 0, which side-steps - // the size check. - let array_layout = self.layout_of(Ty::new_array(self.tcx.tcx, e_ty, len))?; - assert!(array_layout.size <= mplace.layout.size); - let mplace = mplace.offset(Size::ZERO, array_layout, self)?; - Ok((mplace, len)) - } - /// Turn a local in the current frame into a place. pub fn local_to_place( &self, @@ -558,6 +554,7 @@ where /// Given a place, returns either the underlying mplace or a reference to where the value of /// this place is stored. + #[inline(always)] fn as_mplace_or_mutable_local( &mut self, place: &PlaceTy<'tcx, M::Provenance>, @@ -996,7 +993,7 @@ where span_bug!(self.cur_span(), "cannot allocate space for `extern` type, size is not known") }; let ptr = self.allocate_ptr(size, align, kind)?; - Ok(self.ptr_with_meta_to_mplace(ptr.into(), meta, layout)) + Ok(self.ptr_with_meta_to_mplace(ptr.into(), meta, layout, /*unaligned*/ false)) } pub fn allocate( @@ -1031,7 +1028,12 @@ where }; let meta = Scalar::from_target_usize(u64::try_from(str.len()).unwrap(), self); let layout = self.layout_of(self.tcx.types.str_).unwrap(); - Ok(self.ptr_with_meta_to_mplace(ptr.into(), MemPlaceMeta::Meta(meta), layout)) + Ok(self.ptr_with_meta_to_mplace( + ptr.into(), + MemPlaceMeta::Meta(meta), + layout, + /*unaligned*/ false, + )) } pub fn raw_const_to_mplace( diff --git a/compiler/rustc_const_eval/src/interpret/projection.rs b/compiler/rustc_const_eval/src/interpret/projection.rs index dd8dd21e0e8..395e78e623b 100644 --- a/compiler/rustc_const_eval/src/interpret/projection.rs +++ b/compiler/rustc_const_eval/src/interpret/projection.rs @@ -101,7 +101,7 @@ pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug { } /// A type representing iteration over the elements of an array. -pub struct ArrayIterator<'tcx, 'a, Prov: Provenance, P: Projectable<'tcx, Prov>> { +pub struct ArrayIterator<'a, 'tcx, Prov: Provenance, P: Projectable<'tcx, Prov>> { base: &'a P, range: Range<u64>, stride: Size, @@ -109,7 +109,7 @@ pub struct ArrayIterator<'tcx, 'a, Prov: Provenance, P: Projectable<'tcx, Prov>> _phantom: PhantomData<Prov>, // otherwise it says `Prov` is never used... } -impl<'tcx, 'a, Prov: Provenance, P: Projectable<'tcx, Prov>> ArrayIterator<'tcx, 'a, Prov, P> { +impl<'a, 'tcx, Prov: Provenance, P: Projectable<'tcx, Prov>> ArrayIterator<'a, 'tcx, Prov, P> { /// Should be the same `ecx` on each call, and match the one used to create the iterator. pub fn next<M: Machine<'tcx, Provenance = Prov>>( &mut self, @@ -244,6 +244,19 @@ where base.offset(offset, field_layout, self) } + /// Converts a repr(simd) value into an array of the right size, such that `project_index` + /// accesses the SIMD elements. Also returns the number of elements. + pub fn project_to_simd<P: Projectable<'tcx, M::Provenance>>( + &self, + base: &P, + ) -> InterpResult<'tcx, (P, u64)> { + assert!(base.layout().ty.ty_adt_def().unwrap().repr().simd()); + // SIMD types must be newtypes around arrays, so all we have to do is project to their only field. + let array = self.project_field(base, 0)?; + let len = array.len(self)?; + Ok((array, len)) + } + fn project_constant_index<P: Projectable<'tcx, M::Provenance>>( &self, base: &P, @@ -273,7 +286,7 @@ where pub fn project_array_fields<'a, P: Projectable<'tcx, M::Provenance>>( &self, base: &'a P, - ) -> InterpResult<'tcx, ArrayIterator<'tcx, 'a, M::Provenance, P>> { + ) -> InterpResult<'tcx, ArrayIterator<'a, 'tcx, M::Provenance, P>> { let abi::FieldsShape::Array { stride, .. } = base.layout().fields else { span_bug!(self.cur_span(), "project_array_fields: expected an array layout"); }; diff --git a/compiler/rustc_const_eval/src/interpret/util.rs b/compiler/rustc_const_eval/src/interpret/util.rs index 6f5bcebbbb6..490355f416c 100644 --- a/compiler/rustc_const_eval/src/interpret/util.rs +++ b/compiler/rustc_const_eval/src/interpret/util.rs @@ -9,7 +9,7 @@ use rustc_middle::ty::{ }; use tracing::debug; -use super::{throw_inval, InterpCx, MPlaceTy, MemPlaceMeta, MemoryKind}; +use super::{throw_inval, InterpCx, MPlaceTy, MemoryKind}; use crate::const_eval::{CompileTimeInterpCx, CompileTimeMachine, InterpretationResult}; /// Checks whether a type contains generic parameters which must be instantiated. @@ -103,5 +103,5 @@ pub(crate) fn create_static_alloc<'tcx>( assert_eq!(ecx.machine.static_root_ids, None); ecx.machine.static_root_ids = Some((alloc_id, static_def_id)); assert!(ecx.memory.alloc_map.insert(alloc_id, (MemoryKind::Stack, alloc)).is_none()); - Ok(ecx.ptr_with_meta_to_mplace(Pointer::from(alloc_id).into(), MemPlaceMeta::None, layout)) + Ok(ecx.ptr_to_mplace(Pointer::from(alloc_id).into(), layout)) } diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index fb24f983ca9..469af35ec1b 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -14,7 +14,6 @@ use hir::def::DefKind; use rustc_ast::Mutability; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; -use rustc_middle::bug; use rustc_middle::mir::interpret::ValidationErrorKind::{self, *}; use rustc_middle::mir::interpret::{ alloc_range, ExpectedKind, InterpError, InvalidMetaKind, Misalignment, PointerKind, Provenance, @@ -22,6 +21,7 @@ use rustc_middle::mir::interpret::{ }; use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout}; use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::{bug, span_bug}; use rustc_span::symbol::{sym, Symbol}; use rustc_target::abi::{ Abi, FieldIdx, FieldsShape, Scalar as ScalarAbi, Size, VariantIdx, Variants, WrappingRange, @@ -617,6 +617,13 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> { if ptr_expected_mutbl == Mutability::Mut && alloc_actual_mutbl == Mutability::Not { + if !self.ecx.tcx.sess.opts.unstable_opts.unleash_the_miri_inside_of_you + { + span_bug!( + self.ecx.tcx.span, + "the static const safety checks accepted mutable references they should not have accepted" + ); + } throw_validation_failure!(self.path, MutableRefToImmutable); } // In a const, everything must be completely immutable. |
