diff options
Diffstat (limited to 'compiler/rustc_const_eval/src')
27 files changed, 448 insertions, 314 deletions
diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index ed34996a7a7..d0ce027ec2b 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -8,6 +8,7 @@ use std::ops::Deref; use rustc_attr_parsing::{ConstStability, StabilityLevel}; use rustc_errors::{Diag, ErrorGuaranteed}; +use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, LangItem}; use rustc_index::bit_set::DenseBitSet; @@ -29,7 +30,7 @@ use super::ops::{self, NonConstOp, Status}; use super::qualifs::{self, HasMutInterior, NeedsDrop, NeedsNonConstDrop}; use super::resolver::FlowSensitiveAnalysis; use super::{ConstCx, Qualif}; -use crate::check_consts::is_safe_to_expose_on_stable_const_fn; +use crate::check_consts::is_fn_or_trait_safe_to_expose_on_stable; use crate::errors; type QualifResults<'mir, 'tcx, Q> = @@ -470,6 +471,88 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { self.tcx.crate_level_attribute_injection_span(self.tcx.local_def_id_to_hir_id(id)) }) } + + /// Check the const stability of the given item (fn or trait). + fn check_callee_stability(&mut self, def_id: DefId) { + match self.tcx.lookup_const_stability(def_id) { + Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }) => { + // All good. + } + None => { + // This doesn't need a separate const-stability check -- const-stability equals + // regular stability, and regular stability is checked separately. + // However, we *do* have to worry about *recursive* const stability. + if self.enforce_recursive_const_stability() + && !is_fn_or_trait_safe_to_expose_on_stable(self.tcx, def_id) + { + self.dcx().emit_err(errors::UnmarkedConstItemExposed { + span: self.span, + def_path: self.tcx.def_path_str(def_id), + }); + } + } + Some(ConstStability { + level: StabilityLevel::Unstable { implied_by: implied_feature, issue, .. }, + feature, + .. + }) => { + // An unstable const fn/trait with a feature gate. + let callee_safe_to_expose_on_stable = + is_fn_or_trait_safe_to_expose_on_stable(self.tcx, def_id); + + // We only honor `span.allows_unstable` aka `#[allow_internal_unstable]` if + // the callee is safe to expose, to avoid bypassing recursive stability. + // This is not ideal since it means the user sees an error, not the macro + // author, but that's also the case if one forgets to set + // `#[allow_internal_unstable]` in the first place. Note that this cannot be + // integrated in the check below since we want to enforce + // `callee_safe_to_expose_on_stable` even if + // `!self.enforce_recursive_const_stability()`. + if (self.span.allows_unstable(feature) + || implied_feature.is_some_and(|f| self.span.allows_unstable(f))) + && callee_safe_to_expose_on_stable + { + return; + } + + // We can't use `check_op` to check whether the feature is enabled because + // the logic is a bit different than elsewhere: local functions don't need + // the feature gate, and there might be an "implied" gate that also suffices + // to allow this. + let feature_enabled = def_id.is_local() + || self.tcx.features().enabled(feature) + || implied_feature.is_some_and(|f| self.tcx.features().enabled(f)) + || { + // When we're compiling the compiler itself we may pull in + // crates from crates.io, but those crates may depend on other + // crates also pulled in from crates.io. We want to ideally be + // able to compile everything without requiring upstream + // modifications, so in the case that this looks like a + // `rustc_private` crate (e.g., a compiler crate) and we also have + // the `-Z force-unstable-if-unmarked` flag present (we're + // compiling a compiler crate), then let this missing feature + // annotation slide. + // This matches what we do in `eval_stability_allow_unstable` for + // regular stability. + feature == sym::rustc_private + && issue == NonZero::new(27812) + && self.tcx.sess.opts.unstable_opts.force_unstable_if_unmarked + }; + // Even if the feature is enabled, we still need check_op to double-check + // this if the callee is not safe to expose on stable. + if !feature_enabled || !callee_safe_to_expose_on_stable { + self.check_op(ops::CallUnstable { + def_id, + feature, + feature_enabled, + safe_to_expose_on_stable: callee_safe_to_expose_on_stable, + suggestion_span: self.crate_inject_span(), + is_function_call: self.tcx.def_kind(def_id) != DefKind::Trait, + }); + } + } + } + } } impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { @@ -518,7 +601,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } Rvalue::Ref(_, BorrowKind::Mut { .. }, place) - | Rvalue::RawPtr(Mutability::Mut, place) => { + | Rvalue::RawPtr(RawPtrKind::Mut, place) => { // Inside mutable statics, we allow arbitrary mutable references. // We've allowed `static mut FOO = &mut [elements];` for a long time (the exact // reasons why are lost to history), and there is no reason to restrict that to @@ -536,7 +619,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } Rvalue::Ref(_, BorrowKind::Shared | BorrowKind::Fake(_), place) - | Rvalue::RawPtr(Mutability::Not, place) => { + | Rvalue::RawPtr(RawPtrKind::Const, place) => { let borrowed_place_has_mut_interior = qualifs::in_place::<HasMutInterior, _>( self.ccx, &mut |local| self.qualifs.has_mut_interior(self.ccx, local, location), @@ -548,6 +631,17 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } } + Rvalue::RawPtr(RawPtrKind::FakeForPtrMetadata, place) => { + // These are only inserted for slice length, so the place must already be indirect. + // This implies we do not have to worry about whether the borrow escapes. + if !place.is_indirect() { + self.tcx.dcx().span_delayed_bug( + self.body.source_info(location).span, + "fake borrows are always indirect", + ); + } + } + Rvalue::Cast( CastKind::PointerCoercion( PointerCoercion::MutToConstPointer @@ -581,17 +675,32 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { Rvalue::Cast(_, _, _) => {} Rvalue::NullaryOp( - NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks, + NullOp::SizeOf + | NullOp::AlignOf + | NullOp::OffsetOf(_) + | NullOp::UbChecks + | NullOp::ContractChecks, _, ) => {} Rvalue::ShallowInitBox(_, _) => {} - Rvalue::UnaryOp(_, operand) => { + Rvalue::UnaryOp(op, operand) => { let ty = operand.ty(self.body, self.tcx); - if is_int_bool_float_or_char(ty) { - // Int, bool, float, and char operations are fine. - } else { - span_bug!(self.span, "non-primitive type in `Rvalue::UnaryOp`: {:?}", ty); + match op { + UnOp::Not | UnOp::Neg => { + if is_int_bool_float_or_char(ty) { + // Int, bool, float, and char operations are fine. + } else { + span_bug!( + self.span, + "non-primitive type in `Rvalue::UnaryOp{op:?}`: {ty:?}", + ); + } + } + UnOp::PtrMetadata => { + // Getting the metadata from a pointer is always const. + // We already validated the type is valid in the validator. + } } } @@ -623,6 +732,10 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { ); } } + + Rvalue::WrapUnsafeBinder(..) => { + // Unsafe binders are always trivial to create. + } } } @@ -716,8 +829,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { span: *fn_span, call_source, }); - // FIXME(const_trait_impl): do a more fine-grained check whether this - // particular trait can be const-stably called. + self.check_callee_stability(trait_did); } else { // Not even a const trait. self.check_op(ops::FnCallNonConst { @@ -793,7 +905,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { // fallback body is safe to expose on stable. let is_const_stable = intrinsic.const_stable || (!intrinsic.must_be_overridden - && is_safe_to_expose_on_stable_const_fn(tcx, callee)); + && is_fn_or_trait_safe_to_expose_on_stable(tcx, callee)); match tcx.lookup_const_stability(callee) { None => { // This doesn't need a separate const-stability check -- const-stability equals @@ -842,83 +954,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } // Finally, stability for regular function calls -- this is the big one. - match tcx.lookup_const_stability(callee) { - Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }) => { - // All good. - } - None => { - // This doesn't need a separate const-stability check -- const-stability equals - // regular stability, and regular stability is checked separately. - // However, we *do* have to worry about *recursive* const stability. - if self.enforce_recursive_const_stability() - && !is_safe_to_expose_on_stable_const_fn(tcx, callee) - { - self.dcx().emit_err(errors::UnmarkedConstFnExposed { - span: self.span, - def_path: self.tcx.def_path_str(callee), - }); - } - } - Some(ConstStability { - level: StabilityLevel::Unstable { implied_by: implied_feature, issue, .. }, - feature, - .. - }) => { - // An unstable const fn with a feature gate. - let callee_safe_to_expose_on_stable = - is_safe_to_expose_on_stable_const_fn(tcx, callee); - - // We only honor `span.allows_unstable` aka `#[allow_internal_unstable]` if - // the callee is safe to expose, to avoid bypassing recursive stability. - // This is not ideal since it means the user sees an error, not the macro - // author, but that's also the case if one forgets to set - // `#[allow_internal_unstable]` in the first place. Note that this cannot be - // integrated in the check below since we want to enforce - // `callee_safe_to_expose_on_stable` even if - // `!self.enforce_recursive_const_stability()`. - if (self.span.allows_unstable(feature) - || implied_feature.is_some_and(|f| self.span.allows_unstable(f))) - && callee_safe_to_expose_on_stable - { - return; - } - - // We can't use `check_op` to check whether the feature is enabled because - // the logic is a bit different than elsewhere: local functions don't need - // the feature gate, and there might be an "implied" gate that also suffices - // to allow this. - let feature_enabled = callee.is_local() - || tcx.features().enabled(feature) - || implied_feature.is_some_and(|f| tcx.features().enabled(f)) - || { - // When we're compiling the compiler itself we may pull in - // crates from crates.io, but those crates may depend on other - // crates also pulled in from crates.io. We want to ideally be - // able to compile everything without requiring upstream - // modifications, so in the case that this looks like a - // `rustc_private` crate (e.g., a compiler crate) and we also have - // the `-Z force-unstable-if-unmarked` flag present (we're - // compiling a compiler crate), then let this missing feature - // annotation slide. - // This matches what we do in `eval_stability_allow_unstable` for - // regular stability. - feature == sym::rustc_private - && issue == NonZero::new(27812) - && tcx.sess.opts.unstable_opts.force_unstable_if_unmarked - }; - // Even if the feature is enabled, we still need check_op to double-check - // this if the callee is not safe to expose on stable. - if !feature_enabled || !callee_safe_to_expose_on_stable { - self.check_op(ops::FnCallUnstable { - def_id: callee, - feature, - feature_enabled, - safe_to_expose_on_stable: callee_safe_to_expose_on_stable, - suggestion_span: self.crate_inject_span(), - }); - } - } - } + self.check_callee_stability(callee); } // Forbid all `Drop` terminators unless the place being dropped is a local with no diff --git a/compiler/rustc_const_eval/src/check_consts/mod.rs b/compiler/rustc_const_eval/src/check_consts/mod.rs index ab68691f1b9..bfa0a0319c3 100644 --- a/compiler/rustc_const_eval/src/check_consts/mod.rs +++ b/compiler/rustc_const_eval/src/check_consts/mod.rs @@ -56,7 +56,7 @@ impl<'mir, 'tcx> ConstCx<'mir, 'tcx> { self.const_kind == Some(hir::ConstContext::ConstFn) && (self.tcx.features().staged_api() || self.tcx.sess.opts.unstable_opts.force_unstable_if_unmarked) - && is_safe_to_expose_on_stable_const_fn(self.tcx, self.def_id().to_def_id()) + && is_fn_or_trait_safe_to_expose_on_stable(self.tcx, self.def_id().to_def_id()) } fn is_async(&self) -> bool { @@ -84,28 +84,14 @@ pub fn rustc_allow_const_fn_unstable( attr::rustc_allow_const_fn_unstable(tcx.sess, attrs).any(|name| name == feature_gate) } -/// Returns `true` if the given `const fn` is "safe to expose on stable". -/// -/// Panics if the given `DefId` does not refer to a `const fn`. +/// Returns `true` if the given `def_id` (trait or function) is "safe to expose on stable". /// /// This is relevant within a `staged_api` crate. Unlike with normal features, the use of unstable /// const features *recursively* taints the functions that use them. This is to avoid accidentally /// exposing e.g. the implementation of an unstable const intrinsic on stable. So we partition the /// world into two functions: those that are safe to expose on stable (and hence may not use /// unstable features, not even recursively), and those that are not. -pub fn is_safe_to_expose_on_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. These functions can't be called from anything stable, so we shouldn't - // restrict them to only call const-stable functions. - if tcx.is_const_default_method(def_id) { - // FIXME(const_trait_impl): we have to eventually allow some of these if these things can ever be stable. - // They should probably behave like regular `const fn` for that... - return false; - } - - // Const-stability is only relevant for `const fn`. - assert!(tcx.is_const_fn(def_id)); - +pub fn is_fn_or_trait_safe_to_expose_on_stable(tcx: TyCtxt<'_>, def_id: DefId) -> bool { match tcx.lookup_const_stability(def_id) { None => { // In a `staged_api` crate, we do enforce recursive const stability for all unmarked diff --git a/compiler/rustc_const_eval/src/check_consts/ops.rs b/compiler/rustc_const_eval/src/check_consts/ops.rs index 3c83a7b92cd..7756e51c4c5 100644 --- a/compiler/rustc_const_eval/src/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/check_consts/ops.rs @@ -377,11 +377,11 @@ fn build_error_for_const_call<'tcx>( err } -/// A call to an `#[unstable]` const fn or `#[rustc_const_unstable]` function. +/// A call to an `#[unstable]` const fn, `#[rustc_const_unstable]` function or trait. /// -/// Contains the name of the feature that would allow the use of this function. +/// Contains the name of the feature that would allow the use of this function/trait. #[derive(Debug)] -pub(crate) struct FnCallUnstable { +pub(crate) struct CallUnstable { pub def_id: DefId, pub feature: Symbol, /// If this is true, then the feature is enabled, but we need to still check if it is safe to @@ -389,24 +389,33 @@ pub(crate) struct FnCallUnstable { pub feature_enabled: bool, pub safe_to_expose_on_stable: bool, pub suggestion_span: Option<Span>, + /// true if `def_id` is the function we are calling, false if `def_id` is an unstable trait. + pub is_function_call: bool, } -impl<'tcx> NonConstOp<'tcx> for FnCallUnstable { +impl<'tcx> NonConstOp<'tcx> for CallUnstable { fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status { Status::Unstable { gate: self.feature, gate_already_checked: self.feature_enabled, safe_to_expose_on_stable: self.safe_to_expose_on_stable, - is_function_call: true, + is_function_call: self.is_function_call, } } fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> { assert!(!self.feature_enabled); - let mut err = ccx.dcx().create_err(errors::UnstableConstFn { - span, - def_path: ccx.tcx.def_path_str(self.def_id), - }); + let mut err = if self.is_function_call { + ccx.dcx().create_err(errors::UnstableConstFn { + span, + def_path: ccx.tcx.def_path_str(self.def_id), + }) + } else { + ccx.dcx().create_err(errors::UnstableConstTrait { + span, + def_path: ccx.tcx.def_path_str(self.def_id), + }) + }; // FIXME: make this translatable let msg = format!("add `#![feature({})]` to the crate attributes to enable", self.feature); #[allow(rustc::untranslatable_diagnostic)] diff --git a/compiler/rustc_const_eval/src/check_consts/qualifs.rs b/compiler/rustc_const_eval/src/check_consts/qualifs.rs index e244b50a4b5..dfcd1969a73 100644 --- a/compiler/rustc_const_eval/src/check_consts/qualifs.rs +++ b/compiler/rustc_const_eval/src/check_consts/qualifs.rs @@ -188,12 +188,14 @@ impl Qualif for NeedsNonConstDrop { ObligationCause::misc(cx.body.span, cx.def_id()), param_env, ty::Binder::dummy(ty::TraitRef::new(cx.tcx, destruct_def_id, [ty])) - .to_host_effect_clause(cx.tcx, match cx.const_kind() { - rustc_hir::ConstContext::ConstFn => ty::BoundConstness::Maybe, - rustc_hir::ConstContext::Static(_) | rustc_hir::ConstContext::Const { .. } => { - ty::BoundConstness::Const - } - }), + .to_host_effect_clause( + cx.tcx, + match cx.const_kind() { + rustc_hir::ConstContext::ConstFn => ty::BoundConstness::Maybe, + rustc_hir::ConstContext::Static(_) + | rustc_hir::ConstContext::Const { .. } => ty::BoundConstness::Const, + }, + ), )); !ocx.select_all_or_error().is_empty() } @@ -258,6 +260,8 @@ where in_place::<Q, _>(cx, in_local, place.as_ref()) } + Rvalue::WrapUnsafeBinder(op, _) => in_operand::<Q, _>(cx, in_local, op), + Rvalue::Aggregate(kind, operands) => { // Return early if we know that the struct or enum being constructed is always // qualified. @@ -297,7 +301,8 @@ where | ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } | ProjectionElem::Downcast(_, _) - | ProjectionElem::Index(_) => {} + | ProjectionElem::Index(_) + | ProjectionElem::UnwrapUnsafeBinder(_) => {} } let base_ty = place_base.ty(cx.body, cx.tcx); @@ -345,7 +350,7 @@ where Const::Ty(_, ct) if matches!( ct.kind(), - ty::ConstKind::Param(_) | ty::ConstKind::Error(_) | ty::ConstKind::Value(_, _) + ty::ConstKind::Param(_) | ty::ConstKind::Error(_) | ty::ConstKind::Value(_) ) => { None diff --git a/compiler/rustc_const_eval/src/check_consts/resolver.rs b/compiler/rustc_const_eval/src/check_consts/resolver.rs index 79df63a9e84..8cee282311f 100644 --- a/compiler/rustc_const_eval/src/check_consts/resolver.rs +++ b/compiler/rustc_const_eval/src/check_consts/resolver.rs @@ -202,7 +202,8 @@ where | mir::Rvalue::NullaryOp(..) | mir::Rvalue::UnaryOp(..) | mir::Rvalue::Discriminant(..) - | mir::Rvalue::Aggregate(..) => {} + | mir::Rvalue::Aggregate(..) + | mir::Rvalue::WrapUnsafeBinder(..) => {} } } diff --git a/compiler/rustc_const_eval/src/const_eval/error.rs b/compiler/rustc_const_eval/src/const_eval/error.rs index fee7287951d..cc21a18af3a 100644 --- a/compiler/rustc_const_eval/src/const_eval/error.rs +++ b/compiler/rustc_const_eval/src/const_eval/error.rs @@ -140,7 +140,7 @@ where // Don't emit a new diagnostic for these errors, they are already reported elsewhere or // should remain silent. err_inval!(AlreadyReported(info)) => ErrorHandled::Reported(info, span), - err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => { + err_inval!(Layout(LayoutError::TooGeneric(_))) | err_inval!(TooGeneric) => { ErrorHandled::TooGeneric(span) } err_inval!(Layout(LayoutError::ReferencesError(guar))) => { diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index cfdfbdb7880..82e0a6e6666 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -21,9 +21,10 @@ use super::error::*; use crate::errors::{LongRunning, LongRunningWarn}; use crate::fluent_generated as fluent; use crate::interpret::{ - self, AllocId, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame, GlobalAlloc, ImmTy, - InterpCx, InterpResult, MPlaceTy, OpTy, RangeSet, Scalar, compile_time_machine, interp_ok, - throw_exhaust, throw_inval, throw_ub, throw_ub_custom, throw_unsup, throw_unsup_format, + self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame, + GlobalAlloc, ImmTy, InterpCx, InterpResult, MPlaceTy, OpTy, RangeSet, Scalar, + compile_time_machine, interp_ok, throw_exhaust, throw_inval, throw_ub, throw_ub_custom, + throw_unsup, throw_unsup_format, }; /// When hitting this many interpreted terminators we emit a deny by default lint @@ -420,6 +421,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { Size::from_bytes(size), align, interpret::MemoryKind::Machine(MemoryKind::Heap), + AllocInit::Uninit, )?; ecx.write_pointer(ptr, dest)?; } @@ -506,6 +508,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { found: eval_to_int(found)?, } } + NullPointerDereference => NullPointerDereference, }; Err(ConstEvalErrKind::AssertFailure(err)).into() } diff --git a/compiler/rustc_const_eval/src/const_eval/mod.rs b/compiler/rustc_const_eval/src/const_eval/mod.rs index 34f795bda75..c0438fb3ff8 100644 --- a/compiler/rustc_const_eval/src/const_eval/mod.rs +++ b/compiler/rustc_const_eval/src/const_eval/mod.rs @@ -1,7 +1,7 @@ // Not in interpret to make sure we do not use private implementation details use rustc_abi::VariantIdx; -use rustc_middle::query::{Key, TyCtxtAt}; +use rustc_middle::query::Key; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::{bug, mir}; @@ -35,16 +35,17 @@ pub(crate) type ValTreeCreationResult<'tcx> = Result<ty::ValTree<'tcx>, ValTreeC #[instrument(skip(tcx), level = "debug")] pub(crate) fn try_destructure_mir_constant_for_user_output<'tcx>( - tcx: TyCtxtAt<'tcx>, + tcx: TyCtxt<'tcx>, val: mir::ConstValue<'tcx>, ty: Ty<'tcx>, ) -> Option<mir::DestructuredConstant<'tcx>> { let typing_env = ty::TypingEnv::fully_monomorphized(); - let (ecx, op) = mk_eval_cx_for_const_val(tcx, typing_env, val, ty)?; + // FIXME: use a proper span here? + let (ecx, op) = mk_eval_cx_for_const_val(tcx.at(rustc_span::DUMMY_SP), typing_env, val, ty)?; // We go to `usize` as we cannot allocate anything bigger anyway. let (field_count, variant, down) = match ty.kind() { - ty::Array(_, len) => (len.try_to_target_usize(tcx.tcx)? as usize, None, op), + ty::Array(_, len) => (len.try_to_target_usize(tcx)? as usize, None, op), ty::Adt(def, _) if def.variants().is_empty() => { return None; } diff --git a/compiler/rustc_const_eval/src/const_eval/valtrees.rs b/compiler/rustc_const_eval/src/const_eval/valtrees.rs index 4ff8aa9a3b4..c35910a706b 100644 --- a/compiler/rustc_const_eval/src/const_eval/valtrees.rs +++ b/compiler/rustc_const_eval/src/const_eval/valtrees.rs @@ -272,59 +272,64 @@ pub(crate) fn eval_to_valtree<'tcx>( /// Converts a `ValTree` to a `ConstValue`, which is needed after mir /// construction has finished. -// FIXME Merge `valtree_to_const_value` and `valtree_into_mplace` into one function +// FIXME(valtrees): Merge `valtree_to_const_value` and `valtree_into_mplace` into one function #[instrument(skip(tcx), level = "debug", ret)] pub fn valtree_to_const_value<'tcx>( tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>, - ty: Ty<'tcx>, - valtree: ty::ValTree<'tcx>, + cv: ty::Value<'tcx>, ) -> mir::ConstValue<'tcx> { // Basic idea: We directly construct `Scalar` values from trivial `ValTree`s // (those for constants with type bool, int, uint, float or char). // For all other types we create an `MPlace` and fill that by walking // the `ValTree` and using `place_projection` and `place_field` to // create inner `MPlace`s which are filled recursively. - // FIXME Does this need an example? - match *ty.kind() { + // FIXME: Does this need an example? + match *cv.ty.kind() { ty::FnDef(..) => { - assert!(valtree.unwrap_branch().is_empty()); + assert!(cv.valtree.unwrap_branch().is_empty()); mir::ConstValue::ZeroSized } ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Char | ty::RawPtr(_, _) => { - match valtree { + match cv.valtree { ty::ValTree::Leaf(scalar_int) => mir::ConstValue::Scalar(Scalar::Int(scalar_int)), ty::ValTree::Branch(_) => bug!( "ValTrees for Bool, Int, Uint, Float, Char or RawPtr should have the form ValTree::Leaf" ), } } - ty::Pat(ty, _) => valtree_to_const_value(tcx, typing_env, ty, valtree), + ty::Pat(ty, _) => { + let cv = ty::Value { valtree: cv.valtree, ty }; + valtree_to_const_value(tcx, typing_env, cv) + } ty::Ref(_, inner_ty, _) => { let mut ecx = mk_eval_cx_to_read_const_val(tcx, DUMMY_SP, typing_env, CanAccessMutGlobal::No); - let imm = valtree_to_ref(&mut ecx, valtree, inner_ty); - let imm = - ImmTy::from_immediate(imm, tcx.layout_of(typing_env.as_query_input(ty)).unwrap()); + let imm = valtree_to_ref(&mut ecx, cv.valtree, inner_ty); + let imm = ImmTy::from_immediate( + imm, + tcx.layout_of(typing_env.as_query_input(cv.ty)).unwrap(), + ); op_to_const(&ecx, &imm.into(), /* for diagnostics */ false) } ty::Tuple(_) | ty::Array(_, _) | ty::Adt(..) => { - let layout = tcx.layout_of(typing_env.as_query_input(ty)).unwrap(); + let layout = tcx.layout_of(typing_env.as_query_input(cv.ty)).unwrap(); if layout.is_zst() { // Fast path to avoid some allocations. return mir::ConstValue::ZeroSized; } if layout.backend_repr.is_scalar() - && (matches!(ty.kind(), ty::Tuple(_)) - || matches!(ty.kind(), ty::Adt(def, _) if def.is_struct())) + && (matches!(cv.ty.kind(), ty::Tuple(_)) + || matches!(cv.ty.kind(), ty::Adt(def, _) if def.is_struct())) { // A Scalar tuple/struct; we can avoid creating an allocation. - let branches = valtree.unwrap_branch(); + let branches = cv.valtree.unwrap_branch(); // Find the non-ZST field. (There can be aligned ZST!) for (i, &inner_valtree) in branches.iter().enumerate() { let field = layout.field(&LayoutCx::new(tcx, typing_env), i); if !field.is_zst() { - return valtree_to_const_value(tcx, typing_env, field.ty, inner_valtree); + let cv = ty::Value { valtree: inner_valtree, ty: field.ty }; + return valtree_to_const_value(tcx, typing_env, cv); } } bug!("could not find non-ZST field during in {layout:#?}"); @@ -334,9 +339,9 @@ pub fn valtree_to_const_value<'tcx>( mk_eval_cx_to_read_const_val(tcx, DUMMY_SP, typing_env, CanAccessMutGlobal::No); // Need to create a place for this valtree. - let place = create_valtree_place(&mut ecx, layout, valtree); + let place = create_valtree_place(&mut ecx, layout, cv.valtree); - valtree_into_mplace(&mut ecx, &place, valtree); + valtree_into_mplace(&mut ecx, &place, cv.valtree); dump_place(&ecx, &place); intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &place).unwrap(); @@ -361,7 +366,7 @@ pub fn valtree_to_const_value<'tcx>( | ty::Slice(_) | ty::Dynamic(..) | ty::UnsafeBinder(_) => { - bug!("no ValTree should have been created for type {:?}", ty.kind()) + bug!("no ValTree should have been created for type {:?}", cv.ty.kind()) } } } diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 1ee9214c4b2..8df9877cabc 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -122,6 +122,14 @@ pub(crate) struct UnstableConstFn { } #[derive(Diagnostic)] +#[diag(const_eval_unstable_const_trait)] +pub(crate) struct UnstableConstTrait { + #[primary_span] + pub span: Span, + pub def_path: String, +} + +#[derive(Diagnostic)] #[diag(const_eval_unstable_intrinsic)] pub(crate) struct UnstableIntrinsic { #[primary_span] @@ -139,9 +147,9 @@ pub(crate) struct UnstableIntrinsic { } #[derive(Diagnostic)] -#[diag(const_eval_unmarked_const_fn_exposed)] +#[diag(const_eval_unmarked_const_item_exposed)] #[help] -pub(crate) struct UnmarkedConstFnExposed { +pub(crate) struct UnmarkedConstItemExposed { #[primary_span] pub span: Span, pub def_path: String, @@ -570,10 +578,13 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { } ShiftOverflow { intrinsic, shift_amount } => { diag.arg("intrinsic", intrinsic); - diag.arg("shift_amount", match shift_amount { - Either::Left(v) => v.to_string(), - Either::Right(v) => v.to_string(), - }); + diag.arg( + "shift_amount", + match shift_amount { + Either::Left(v) => v.to_string(), + Either::Right(v) => v.to_string(), + }, + ); } BoundsCheckFailed { len, index } => { diag.arg("len", len); diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index e6a34193c9d..cdf706f3752 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -5,6 +5,7 @@ use std::borrow::Cow; use either::{Left, Right}; use rustc_abi::{self as abi, ExternAbi, FieldIdx, Integer, VariantIdx}; +use rustc_hir::def_id::DefId; use rustc_middle::ty::layout::{FnAbiOf, IntegerExt, LayoutOf, TyAndLayout}; use rustc_middle::ty::{self, AdtDef, Instance, Ty, VariantDef}; use rustc_middle::{bug, mir, span_bug}; @@ -380,10 +381,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { "caller ABI: {:#?}, args: {:#?}", caller_fn_abi, args.iter() - .map(|arg| (arg.layout().ty, match arg { - FnArg::Copy(op) => format!("copy({op:?})"), - FnArg::InPlace(mplace) => format!("in-place({mplace:?})"), - })) + .map(|arg| ( + arg.layout().ty, + match arg { + FnArg::Copy(op) => format!("copy({op:?})"), + FnArg::InPlace(mplace) => format!("in-place({mplace:?})"), + } + )) .collect::<Vec<_>>() ); trace!( @@ -693,25 +697,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { trace!("Virtual call dispatches to {fn_inst:#?}"); // We can also do the lookup based on `def_id` and `dyn_ty`, and check that that // produces the same result. - if cfg!(debug_assertions) { - let tcx = *self.tcx; - - let trait_def_id = tcx.trait_of_item(def_id).unwrap(); - let virtual_trait_ref = - ty::TraitRef::from_method(tcx, trait_def_id, instance.args); - let existential_trait_ref = - ty::ExistentialTraitRef::erase_self_ty(tcx, virtual_trait_ref); - let concrete_trait_ref = existential_trait_ref.with_self_ty(tcx, dyn_ty); - - let concrete_method = Instance::expect_resolve_for_vtable( - tcx, - self.typing_env, - def_id, - instance.args.rebase_onto(tcx, trait_def_id, concrete_trait_ref.args), - self.cur_span(), - ); - assert_eq!(fn_inst, concrete_method); - } + self.assert_virtual_instance_matches_concrete(dyn_ty, def_id, instance, fn_inst); // Adjust receiver argument. Layout can be any (thin) ptr. let receiver_ty = Ty::new_mut_ptr(self.tcx.tcx, dyn_ty); @@ -744,6 +730,30 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } } + fn assert_virtual_instance_matches_concrete( + &self, + dyn_ty: Ty<'tcx>, + def_id: DefId, + virtual_instance: ty::Instance<'tcx>, + concrete_instance: ty::Instance<'tcx>, + ) { + let tcx = *self.tcx; + + let trait_def_id = tcx.trait_of_item(def_id).unwrap(); + let virtual_trait_ref = ty::TraitRef::from_method(tcx, trait_def_id, virtual_instance.args); + let existential_trait_ref = ty::ExistentialTraitRef::erase_self_ty(tcx, virtual_trait_ref); + let concrete_trait_ref = existential_trait_ref.with_self_ty(tcx, dyn_ty); + + let concrete_method = Instance::expect_resolve_for_vtable( + tcx, + self.typing_env, + def_id, + virtual_instance.args.rebase_onto(tcx, trait_def_id, concrete_trait_ref.args), + self.cur_span(), + ); + assert_eq!(concrete_instance, concrete_method); + } + /// Initiate a tail call to this function -- popping the current stack frame, pushing the new /// stack frame and initializing the arguments. pub(super) fn init_fn_tail_call( @@ -867,10 +877,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ); // Check `unwinding`. - assert_eq!(unwinding, match self.frame().loc { - Left(loc) => self.body().basic_blocks[loc.block].is_cleanup, - Right(_) => true, - }); + assert_eq!( + unwinding, + match self.frame().loc { + Left(loc) => self.body().basic_blocks[loc.block].is_cleanup, + Right(_) => true, + } + ); if unwinding && self.frame_idx() == 0 { throw_ub_custom!(fluent::const_eval_unwind_past_top); } diff --git a/compiler/rustc_const_eval/src/interpret/cast.rs b/compiler/rustc_const_eval/src/interpret/cast.rs index ef3e96784ce..e110c155da0 100644 --- a/compiler/rustc_const_eval/src/interpret/cast.rs +++ b/compiler/rustc_const_eval/src/interpret/cast.rs @@ -414,36 +414,33 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Sanity-check that `supertrait_vtable_slot` in this type's vtable indeed produces // our destination trait. - if cfg!(debug_assertions) { - let vptr_entry_idx = - self.tcx.supertrait_vtable_slot((src_pointee_ty, dest_pointee_ty)); - let vtable_entries = self.vtable_entries(data_a.principal(), ty); - if let Some(entry_idx) = vptr_entry_idx { - let Some(&ty::VtblEntry::TraitVPtr(upcast_trait_ref)) = - vtable_entries.get(entry_idx) - else { - span_bug!( - self.cur_span(), - "invalid vtable entry index in {} -> {} upcast", - src_pointee_ty, - dest_pointee_ty - ); - }; - let erased_trait_ref = upcast_trait_ref - .map_bound(|r| ty::ExistentialTraitRef::erase_self_ty(*self.tcx, r)); - assert!( - data_b - .principal() - .is_some_and(|b| self.eq_in_param_env(erased_trait_ref, b)) + let vptr_entry_idx = + self.tcx.supertrait_vtable_slot((src_pointee_ty, dest_pointee_ty)); + let vtable_entries = self.vtable_entries(data_a.principal(), ty); + if let Some(entry_idx) = vptr_entry_idx { + let Some(&ty::VtblEntry::TraitVPtr(upcast_trait_ref)) = + vtable_entries.get(entry_idx) + else { + span_bug!( + self.cur_span(), + "invalid vtable entry index in {} -> {} upcast", + src_pointee_ty, + dest_pointee_ty ); - } else { - // In this case codegen would keep using the old vtable. We don't want to do - // that as it has the wrong trait. The reason codegen can do this is that - // one vtable is a prefix of the other, so we double-check that. - let vtable_entries_b = self.vtable_entries(data_b.principal(), ty); - assert!(&vtable_entries[..vtable_entries_b.len()] == vtable_entries_b); }; - } + let erased_trait_ref = + ty::ExistentialTraitRef::erase_self_ty(*self.tcx, upcast_trait_ref); + assert!(data_b.principal().is_some_and(|b| self.eq_in_param_env( + erased_trait_ref, + self.tcx.instantiate_bound_regions_with_erased(b) + ))); + } else { + // In this case codegen would keep using the old vtable. We don't want to do + // that as it has the wrong trait. The reason codegen can do this is that + // one vtable is a prefix of the other, so we double-check that. + let vtable_entries_b = self.vtable_entries(data_b.principal(), ty); + assert!(&vtable_entries[..vtable_entries_b.len()] == vtable_entries_b); + }; // Get the destination trait vtable and return that. let new_vptr = self.get_vtable_ptr(ty, data_b)?; diff --git a/compiler/rustc_const_eval/src/interpret/intern.rs b/compiler/rustc_const_eval/src/interpret/intern.rs index 0cbb3b157f3..e14cc1dad36 100644 --- a/compiler/rustc_const_eval/src/interpret/intern.rs +++ b/compiler/rustc_const_eval/src/interpret/intern.rs @@ -102,11 +102,11 @@ fn intern_as_new_static<'tcx>( alloc_id: AllocId, alloc: ConstAllocation<'tcx>, ) { - let feed = tcx.create_def(static_id, sym::nested, DefKind::Static { - safety: hir::Safety::Safe, - mutability: alloc.0.mutability, - nested: true, - }); + let feed = tcx.create_def( + static_id, + sym::nested, + DefKind::Static { safety: hir::Safety::Safe, mutability: alloc.0.mutability, nested: true }, + ); tcx.set_nested_alloc_id_static(alloc_id, feed.def_id()); if tcx.is_thread_local_static(static_id.into()) { diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 0664a882c1d..4ca317e3a1e 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -319,7 +319,25 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Check that the memory between them is dereferenceable at all, starting from the // origin pointer: `dist` is `a - b`, so it is based on `b`. - self.check_ptr_access_signed(b, dist, CheckInAllocMsg::OffsetFromTest)?; + self.check_ptr_access_signed(b, dist, CheckInAllocMsg::OffsetFromTest) + .map_err_kind(|_| { + // This could mean they point to different allocations, or they point to the same allocation + // but not the entire range between the pointers is in-bounds. + if let Ok((a_alloc_id, ..)) = self.ptr_try_get_alloc_id(a, 0) + && let Ok((b_alloc_id, ..)) = self.ptr_try_get_alloc_id(b, 0) + && a_alloc_id == b_alloc_id + { + err_ub_custom!( + fluent::const_eval_offset_from_out_of_bounds, + name = intrinsic_name, + ) + } else { + err_ub_custom!( + fluent::const_eval_offset_from_different_allocations, + name = intrinsic_name, + ) + } + })?; // Then check that this is also dereferenceable from `a`. This ensures that they are // derived from the same allocation. self.check_ptr_access_signed( @@ -747,7 +765,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { { let a: F = self.read_scalar(&args[0])?.to_float()?; let b: F = self.read_scalar(&args[1])?.to_float()?; - let res = self.adjust_nan(a.min(b), &[a, b]); + let res = if a == b { + // They are definitely not NaN (those are never equal), but they could be `+0` and `-0`. + // Let the machine decide which one to return. + M::equal_float_min_max(self, a, b) + } else { + self.adjust_nan(a.min(b), &[a, b]) + }; self.write_scalar(res, dest)?; interp_ok(()) } @@ -762,7 +786,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { { let a: F = self.read_scalar(&args[0])?.to_float()?; let b: F = self.read_scalar(&args[1])?.to_float()?; - let res = self.adjust_nan(a.max(b), &[a, b]); + let res = if a == b { + // They are definitely not NaN (those are never equal), but they could be `+0` and `-0`. + // Let the machine decide which one to return. + M::equal_float_min_max(self, a, b) + } else { + self.adjust_nan(a.max(b), &[a, b]) + }; self.write_scalar(res, dest)?; interp_ok(()) } diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index 36e5a2ff750..1a799f5dea5 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -278,6 +278,12 @@ pub trait Machine<'tcx>: Sized { F2::NAN } + /// Determines the result of `min`/`max` on floats when the arguments are equal. + fn equal_float_min_max<F: Float>(_ecx: &InterpCx<'tcx, Self>, a: F, _b: F) -> F { + // By default, we pick the left argument. + a + } + /// Called before a basic block terminator is executed. #[inline] fn before_terminator(_ecx: &mut InterpCx<'tcx, Self>) -> InterpResult<'tcx> { @@ -287,6 +293,9 @@ pub trait Machine<'tcx>: Sized { /// Determines the result of a `NullaryOp::UbChecks` invocation. fn ub_checks(_ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool>; + /// Determines the result of a `NullaryOp::ContractChecks` invocation. + fn contract_checks(_ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool>; + /// Called when the interpreter encounters a `StatementKind::ConstEvalCounter` instruction. /// You can use this to detect long or endlessly running programs. #[inline] @@ -674,6 +683,13 @@ pub macro compile_time_machine(<$tcx: lifetime>) { } #[inline(always)] + fn contract_checks(_ecx: &InterpCx<$tcx, Self>) -> InterpResult<$tcx, bool> { + // We can't look at `tcx.sess` here as that can differ across crates, which can lead to + // unsound differences in evaluating the same constant at different instantiation sites. + interp_ok(true) + } + + #[inline(always)] fn adjust_global_allocation<'b>( _ecx: &InterpCx<$tcx, Self>, _id: AllocId, diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 2772c94d52b..4f4b6785844 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -20,10 +20,10 @@ use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; use tracing::{debug, instrument, trace}; use super::{ - AllocBytes, AllocId, AllocMap, AllocRange, Allocation, CheckAlignMsg, CheckInAllocMsg, - CtfeProvenance, GlobalAlloc, InterpCx, InterpResult, Machine, MayLeak, Misalignment, Pointer, - PointerArithmetic, Provenance, Scalar, alloc_range, err_ub, err_ub_custom, interp_ok, throw_ub, - throw_ub_custom, throw_unsup, throw_unsup_format, + AllocBytes, AllocId, AllocInit, AllocMap, AllocRange, Allocation, CheckAlignMsg, + CheckInAllocMsg, CtfeProvenance, GlobalAlloc, InterpCx, InterpResult, Machine, MayLeak, + Misalignment, Pointer, PointerArithmetic, Provenance, Scalar, alloc_range, err_ub, + err_ub_custom, interp_ok, throw_ub, throw_ub_custom, throw_unsup, throw_unsup_format, }; use crate::fluent_generated as fluent; @@ -230,11 +230,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { size: Size, align: Align, kind: MemoryKind<M::MemoryKind>, + init: AllocInit, ) -> InterpResult<'tcx, Pointer<M::Provenance>> { let alloc = if M::PANIC_ON_ALLOC_FAIL { - Allocation::uninit(size, align) + Allocation::new(size, align, init) } else { - Allocation::try_uninit(size, align)? + Allocation::try_new(size, align, init)? }; self.insert_allocation(alloc, kind) } @@ -270,6 +271,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { M::adjust_alloc_root_pointer(self, Pointer::from(id), Some(kind)) } + /// If this grows the allocation, `init_growth` determines + /// whether the additional space will be initialized. pub fn reallocate_ptr( &mut self, ptr: Pointer<Option<M::Provenance>>, @@ -277,6 +280,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { new_size: Size, new_align: Align, kind: MemoryKind<M::MemoryKind>, + init_growth: AllocInit, ) -> InterpResult<'tcx, Pointer<M::Provenance>> { let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; if offset.bytes() != 0 { @@ -289,7 +293,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // For simplicities' sake, we implement reallocate as "alloc, copy, dealloc". // This happens so rarely, the perf advantage is outweighed by the maintenance cost. - let new_ptr = self.allocate_ptr(new_size, new_align, kind)?; + // If requested, we zero-init the entire allocation, to ensure that a growing + // allocation has its new bytes properly set. For the part that is copied, + // `mem_copy` below will de-initialize things as necessary. + let new_ptr = self.allocate_ptr(new_size, new_align, kind, init_growth)?; let old_size = match old_size_and_align { Some((size, _align)) => size, None => self.get_alloc_raw(alloc_id)?.size(), @@ -830,9 +837,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { /// [`InterpCx::get_alloc_info`] if all you need to check is whether the kind is /// [`AllocKind::Dead`] because it doesn't have to look up the type and layout of statics. pub fn is_alloc_live(&self, id: AllocId) -> bool { - self.tcx.try_get_global_alloc(id).is_some() - || self.memory.alloc_map.contains_key_ref(&id) + self.memory.alloc_map.contains_key_ref(&id) || self.memory.extra_fn_ptr_map.contains_key(&id) + // We check `tcx` last as that has to acquire a lock in `many-seeds` mode. + // This also matches the order in `get_alloc_info`. + || self.tcx.try_get_global_alloc(id).is_some() } /// Obtain the size and alignment of an allocation, even if that allocation has diff --git a/compiler/rustc_const_eval/src/interpret/operator.rs b/compiler/rustc_const_eval/src/interpret/operator.rs index 5fa632fc57a..899670aeb62 100644 --- a/compiler/rustc_const_eval/src/interpret/operator.rs +++ b/compiler/rustc_const_eval/src/interpret/operator.rs @@ -537,6 +537,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ImmTy::from_uint(val, usize_layout()) } UbChecks => ImmTy::from_bool(M::ub_checks(self)?, *self.tcx), + ContractChecks => ImmTy::from_bool(M::contract_checks(self)?, *self.tcx), }) } } diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs index c97922ac132..f5d3de7b1b2 100644 --- a/compiler/rustc_const_eval/src/interpret/place.rs +++ b/compiler/rustc_const_eval/src/interpret/place.rs @@ -12,9 +12,9 @@ use rustc_middle::{bug, mir, span_bug}; use tracing::{instrument, trace}; use super::{ - AllocRef, AllocRefMut, CheckAlignMsg, CtfeProvenance, ImmTy, Immediate, InterpCx, InterpResult, - Machine, MemoryKind, Misalignment, OffsetMode, OpTy, Operand, Pointer, Projectable, Provenance, - Scalar, alloc_range, interp_ok, mir_assign_valid_types, + AllocInit, AllocRef, AllocRefMut, CheckAlignMsg, CtfeProvenance, ImmTy, Immediate, InterpCx, + InterpResult, Machine, MemoryKind, Misalignment, OffsetMode, OpTy, Operand, Pointer, + Projectable, Provenance, Scalar, alloc_range, interp_ok, mir_assign_valid_types, }; #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] @@ -983,7 +983,7 @@ where let Some((size, align)) = self.size_and_align_of(&meta, &layout)? else { span_bug!(self.cur_span(), "cannot allocate space for `extern` type, size is not known") }; - let ptr = self.allocate_ptr(size, align, kind)?; + let ptr = self.allocate_ptr(size, align, kind, AllocInit::Uninit)?; interp_ok(self.ptr_with_meta_to_mplace(ptr.into(), meta, layout, /*unaligned*/ false)) } diff --git a/compiler/rustc_const_eval/src/interpret/projection.rs b/compiler/rustc_const_eval/src/interpret/projection.rs index 996142d7b03..8ecb3e13d5c 100644 --- a/compiler/rustc_const_eval/src/interpret/projection.rs +++ b/compiler/rustc_const_eval/src/interpret/projection.rs @@ -381,6 +381,7 @@ where OpaqueCast(ty) => { span_bug!(self.cur_span(), "OpaqueCast({ty}) encountered after borrowck") } + UnwrapUnsafeBinder(target) => base.transmute(self.layout_of(target)?, self)?, // We don't want anything happening here, this is here as a dummy. Subtype(_) => base.transmute(base.layout(), self)?, Field(field, _) => self.project_field(base, field.index())?, diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index b61865be667..abe73c43d8a 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -237,7 +237,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.write_immediate(*val, &dest)?; } - RawPtr(_, place) => { + RawPtr(kind, place) => { // Figure out whether this is an addr_of of an already raw place. let place_base_raw = if place.is_indirect_first_projection() { let ty = self.frame().body.local_decls[place.local].ty; @@ -250,8 +250,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let src = self.eval_place(place)?; let place = self.force_allocation(&src)?; let mut val = ImmTy::from_immediate(place.to_ref(self), dest.layout); - if !place_base_raw { - // If this was not already raw, it needs retagging. + if !place_base_raw && !kind.is_fake() { + // If this was not already raw, it needs retagging -- except for "fake" + // raw borrows whose defining property is that they do not get retagged. val = M::retag_ptr_value(self, mir::RetagKind::Raw, &val)?; } self.write_immediate(*val, &dest)?; @@ -276,6 +277,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let discr = self.discriminant_for_variant(op.layout.ty, variant)?; self.write_immediate(*discr, &dest)?; } + + WrapUnsafeBinder(ref op, _ty) => { + // Constructing an unsafe binder acts like a transmute + // since the operand's layout does not change. + let op = self.eval_operand(op, None)?; + self.copy_op_allow_transmute(&op, &dest)?; + } } trace!("{:?}", self.dump_place(&dest)); diff --git a/compiler/rustc_const_eval/src/interpret/traits.rs b/compiler/rustc_const_eval/src/interpret/traits.rs index af8d618b6b5..4cfaacebfcd 100644 --- a/compiler/rustc_const_eval/src/interpret/traits.rs +++ b/compiler/rustc_const_eval/src/interpret/traits.rs @@ -54,7 +54,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ) -> &'tcx [VtblEntry<'tcx>] { if let Some(trait_) = trait_ { let trait_ref = trait_.with_self_ty(*self.tcx, dyn_ty); - let trait_ref = self.tcx.erase_regions(trait_ref); + let trait_ref = + self.tcx.erase_regions(self.tcx.instantiate_bound_regions_with_erased(trait_ref)); self.tcx.vtable_entries(trait_ref) } else { TyCtxt::COMMON_VTABLE_ENTRIES diff --git a/compiler/rustc_const_eval/src/interpret/util.rs b/compiler/rustc_const_eval/src/interpret/util.rs index ecb7c3fc93c..ba579e25f03 100644 --- a/compiler/rustc_const_eval/src/interpret/util.rs +++ b/compiler/rustc_const_eval/src/interpret/util.rs @@ -1,12 +1,8 @@ -use std::ops::ControlFlow; - use rustc_hir::def_id::LocalDefId; use rustc_middle::mir; -use rustc_middle::mir::interpret::{Allocation, InterpResult, Pointer}; +use rustc_middle::mir::interpret::{AllocInit, Allocation, InterpResult, Pointer}; use rustc_middle::ty::layout::TyAndLayout; -use rustc_middle::ty::{ - self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, -}; +use rustc_middle::ty::{TyCtxt, TypeVisitable, TypeVisitableExt}; use tracing::debug; use super::{InterpCx, MPlaceTy, MemoryKind, interp_ok, throw_inval}; @@ -20,44 +16,10 @@ where T: TypeVisitable<TyCtxt<'tcx>>, { debug!("ensure_monomorphic_enough: ty={:?}", ty); - if !ty.has_param() { - return interp_ok(()); - } - - struct FoundParam; - struct UsedParamsNeedInstantiationVisitor {} - - impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for UsedParamsNeedInstantiationVisitor { - type Result = ControlFlow<FoundParam>; - - fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { - if !ty.has_param() { - return ControlFlow::Continue(()); - } - - match *ty.kind() { - ty::Param(_) => ControlFlow::Break(FoundParam), - ty::Closure(..) | ty::CoroutineClosure(..) | ty::Coroutine(..) | ty::FnDef(..) => { - ControlFlow::Continue(()) - } - _ => ty.super_visit_with(self), - } - } - - fn visit_const(&mut self, c: ty::Const<'tcx>) -> Self::Result { - match c.kind() { - ty::ConstKind::Param(..) => ControlFlow::Break(FoundParam), - _ => c.super_visit_with(self), - } - } - } - - let mut vis = UsedParamsNeedInstantiationVisitor {}; - if matches!(ty.visit_with(&mut vis), ControlFlow::Break(FoundParam)) { + if ty.has_param() { throw_inval!(TooGeneric); - } else { - interp_ok(()) } + interp_ok(()) } impl<'tcx> InterpretationResult<'tcx> for mir::interpret::ConstAllocation<'tcx> { @@ -76,7 +38,7 @@ pub(crate) fn create_static_alloc<'tcx>( static_def_id: LocalDefId, layout: TyAndLayout<'tcx>, ) -> InterpResult<'tcx, MPlaceTy<'tcx>> { - let alloc = Allocation::try_uninit(layout.size, layout.align.abi)?; + let alloc = Allocation::try_new(layout.size, layout.align.abi, AllocInit::Uninit)?; let alloc_id = ecx.tcx.reserve_and_set_static_alloc(static_def_id.into()); assert_eq!(ecx.machine.static_root_ids, None); ecx.machine.static_root_ids = Some((alloc_id, static_def_id)); diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index d75df1ad442..0ac34f4633b 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -812,10 +812,10 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> { if start == 1 && end == max_value { // Only null is the niche. So make sure the ptr is NOT null. if self.ecx.scalar_may_be_null(scalar)? { - throw_validation_failure!(self.path, NullablePtrOutOfRange { - range: valid_range, - max_value - }) + throw_validation_failure!( + self.path, + NullablePtrOutOfRange { range: valid_range, max_value } + ) } else { return interp_ok(()); } @@ -825,10 +825,10 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> { } else { // Conservatively, we reject, because the pointer *could* have a bad // value. - throw_validation_failure!(self.path, PtrOutOfRange { - range: valid_range, - max_value - }) + throw_validation_failure!( + self.path, + PtrOutOfRange { range: valid_range, max_value } + ) } } }; @@ -836,11 +836,10 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> { if valid_range.contains(bits) { interp_ok(()) } else { - throw_validation_failure!(self.path, OutOfRange { - value: format!("{bits}"), - range: valid_range, - max_value - }) + throw_validation_failure!( + self.path, + OutOfRange { value: format!("{bits}"), range: valid_range, max_value } + ) } } @@ -1240,6 +1239,17 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt, self.visit_field(val, 0, &self.ecx.project_index(val, 0)?)?; } } + ty::Pat(base, pat) => { + // First check that the base type is valid + self.visit_value(&val.transmute(self.ecx.layout_of(*base)?, self.ecx)?)?; + // When you extend this match, make sure to also add tests to + // tests/ui/type/pattern_types/validity.rs(( + match **pat { + // Range patterns are precisely reflected into `valid_range` and thus + // handled fully by `visit_scalar` (called below). + ty::PatternKind::Range { .. } => {}, + } + } _ => { // default handler try_validation!( diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs index b5adf06b300..ed5489652fb 100644 --- a/compiler/rustc_const_eval/src/lib.rs +++ b/compiler/rustc_const_eval/src/lib.rs @@ -46,12 +46,13 @@ pub fn provide(providers: &mut Providers) { }; providers.hooks.try_destructure_mir_constant_for_user_output = const_eval::try_destructure_mir_constant_for_user_output; - providers.valtree_to_const_val = |tcx, (ty, valtree)| { - const_eval::valtree_to_const_value(tcx, ty::TypingEnv::fully_monomorphized(), ty, valtree) - }; + providers.valtree_to_const_val = + |tcx, cv| const_eval::valtree_to_const_value(tcx, ty::TypingEnv::fully_monomorphized(), cv); providers.check_validity_requirement = |tcx, (init_kind, param_env_and_ty)| { util::check_validity_requirement(tcx, init_kind, param_env_and_ty) }; + providers.hooks.validate_scalar_in_layout = + |tcx, scalar, layout| util::validate_scalar_in_layout(tcx, scalar, layout); } /// `rustc_driver::main` installs a handler that will set this to `true` if diff --git a/compiler/rustc_const_eval/src/util/caller_location.rs b/compiler/rustc_const_eval/src/util/caller_location.rs index 6dd9447cf5a..e926040e9ba 100644 --- a/compiler/rustc_const_eval/src/util/caller_location.rs +++ b/compiler/rustc_const_eval/src/util/caller_location.rs @@ -1,7 +1,6 @@ use rustc_hir::LangItem; -use rustc_middle::query::TyCtxtAt; use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::{self}; +use rustc_middle::ty::{self, TyCtxt}; use rustc_middle::{bug, mir}; use rustc_span::Symbol; use tracing::trace; @@ -48,15 +47,15 @@ fn alloc_caller_location<'tcx>( } pub(crate) fn const_caller_location_provider( - tcx: TyCtxtAt<'_>, + tcx: TyCtxt<'_>, file: Symbol, line: u32, col: u32, ) -> mir::ConstValue<'_> { trace!("const_caller_location: {}:{}:{}", file, line, col); let mut ecx = mk_eval_cx_to_read_const_val( - tcx.tcx, - tcx.span, + tcx, + rustc_span::DUMMY_SP, // FIXME: use a proper span here? ty::TypingEnv::fully_monomorphized(), CanAccessMutGlobal::No, ); diff --git a/compiler/rustc_const_eval/src/util/check_validity_requirement.rs b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs index a729d9325c8..79baf91c3ce 100644 --- a/compiler/rustc_const_eval/src/util/check_validity_requirement.rs +++ b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs @@ -1,9 +1,10 @@ use rustc_abi::{BackendRepr, FieldsShape, Scalar, Variants}; -use rustc_middle::bug; use rustc_middle::ty::layout::{ HasTyCtxt, LayoutCx, LayoutError, LayoutOf, TyAndLayout, ValidityRequirement, }; -use rustc_middle::ty::{PseudoCanonicalInput, Ty, TyCtxt}; +use rustc_middle::ty::{PseudoCanonicalInput, ScalarInt, Ty, TyCtxt}; +use rustc_middle::{bug, ty}; +use rustc_span::DUMMY_SP; use crate::const_eval::{CanAccessMutGlobal, CheckAlignment, CompileTimeMachine}; use crate::interpret::{InterpCx, MemoryKind}; @@ -34,7 +35,7 @@ pub fn check_validity_requirement<'tcx>( let layout_cx = LayoutCx::new(tcx, input.typing_env); if kind == ValidityRequirement::Uninit || tcx.sess.opts.unstable_opts.strict_init_checks { - check_validity_requirement_strict(layout, &layout_cx, kind) + Ok(check_validity_requirement_strict(layout, &layout_cx, kind)) } else { check_validity_requirement_lax(layout, &layout_cx, kind) } @@ -46,10 +47,10 @@ fn check_validity_requirement_strict<'tcx>( ty: TyAndLayout<'tcx>, cx: &LayoutCx<'tcx>, kind: ValidityRequirement, -) -> Result<bool, &'tcx LayoutError<'tcx>> { +) -> bool { let machine = CompileTimeMachine::new(CanAccessMutGlobal::No, CheckAlignment::Error); - let mut cx = InterpCx::new(cx.tcx(), rustc_span::DUMMY_SP, cx.typing_env, machine); + let mut cx = InterpCx::new(cx.tcx(), DUMMY_SP, cx.typing_env, machine); let allocated = cx .allocate(ty, MemoryKind::Machine(crate::const_eval::MemoryKind::Heap)) @@ -69,14 +70,13 @@ fn check_validity_requirement_strict<'tcx>( // due to this. // The value we are validating is temporary and discarded at the end of this function, so // there is no point in reseting provenance and padding. - Ok(cx - .validate_operand( - &allocated.into(), - /*recursive*/ false, - /*reset_provenance_and_padding*/ false, - ) - .discard_err() - .is_some()) + cx.validate_operand( + &allocated.into(), + /*recursive*/ false, + /*reset_provenance_and_padding*/ false, + ) + .discard_err() + .is_some() } /// Implements the 'lax' (default) version of the [`check_validity_requirement`] checks; see that @@ -168,3 +168,31 @@ fn check_validity_requirement_lax<'tcx>( Ok(true) } + +pub(crate) fn validate_scalar_in_layout<'tcx>( + tcx: TyCtxt<'tcx>, + scalar: ScalarInt, + ty: Ty<'tcx>, +) -> bool { + let machine = CompileTimeMachine::new(CanAccessMutGlobal::No, CheckAlignment::Error); + + let typing_env = ty::TypingEnv::fully_monomorphized(); + let mut cx = InterpCx::new(tcx, DUMMY_SP, typing_env, machine); + + let Ok(layout) = cx.layout_of(ty) else { + bug!("could not compute layout of {scalar:?}:{ty:?}") + }; + let allocated = cx + .allocate(layout, MemoryKind::Machine(crate::const_eval::MemoryKind::Heap)) + .expect("OOM: failed to allocate for uninit check"); + + cx.write_scalar(scalar, &allocated).unwrap(); + + cx.validate_operand( + &allocated.into(), + /*recursive*/ false, + /*reset_provenance_and_padding*/ false, + ) + .discard_err() + .is_some() +} diff --git a/compiler/rustc_const_eval/src/util/mod.rs b/compiler/rustc_const_eval/src/util/mod.rs index 25a9dbb2c11..5be5ee8d1ae 100644 --- a/compiler/rustc_const_eval/src/util/mod.rs +++ b/compiler/rustc_const_eval/src/util/mod.rs @@ -8,6 +8,7 @@ mod type_name; pub use self::alignment::{is_disaligned, is_within_packed}; pub use self::check_validity_requirement::check_validity_requirement; +pub(crate) use self::check_validity_requirement::validate_scalar_in_layout; pub use self::compare_types::{relate_types, sub_types}; pub use self::type_name::type_name; |
