diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 2dbeb7d5e0ca4..46eaff1d4acc7 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -310,6 +310,8 @@ const_eval_realloc_or_alloc_with_offset = const_eval_recursive_static = encountered static that tried to initialize itself with itself +const_eval_ref_to_static = encountered a reference pointing to a static variable in a constant + const_eval_remainder_by_zero = calculating the remainder with a divisor of zero const_eval_remainder_overflow = @@ -399,7 +401,6 @@ const_eval_unwind_past_top = ## The `front_matter`s here refer to either `const_eval_front_matter_invalid_value` or `const_eval_front_matter_invalid_value_with_path`. ## (We'd love to sort this differently to make that more clear but tidy won't let us...) -const_eval_validation_box_to_static = {$front_matter}: encountered a box pointing to a static variable in a constant const_eval_validation_box_to_uninhabited = {$front_matter}: encountered a box pointing to uninhabited type {$ty} const_eval_validation_const_ref_to_extern = {$front_matter}: encountered reference to `extern` static in `const` @@ -454,7 +455,6 @@ const_eval_validation_out_of_range = {$front_matter}: encountered {$value}, but const_eval_validation_partial_pointer = {$front_matter}: encountered a partial pointer or a mix of pointers const_eval_validation_pointer_as_int = {$front_matter}: encountered a pointer, but {$expected} const_eval_validation_ptr_out_of_range = {$front_matter}: encountered a pointer, but expected something that cannot possibly fail to be {$in_range} -const_eval_validation_ref_to_static = {$front_matter}: encountered a reference pointing to a static variable in a constant const_eval_validation_ref_to_uninhabited = {$front_matter}: encountered a reference pointing to uninhabited type {$ty} const_eval_validation_unaligned_box = {$front_matter}: encountered an unaligned box (required {$required_bytes} byte alignment but found {$found_bytes}) const_eval_validation_unaligned_ref = {$front_matter}: encountered an unaligned reference (required {$required_bytes} byte alignment but found {$found_bytes}) 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 4b8145eb4855b..b2b9470a5ee84 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -17,7 +17,7 @@ use rustc_span::def_id::LocalDefId; use rustc_span::{Span, DUMMY_SP}; use rustc_target::abi::{self, Abi}; -use super::{CanAccessMutGlobal, CompileTimeInterpCx, CompileTimeMachine}; +use super::{CompileTimeInterpCx, CompileTimeMachine, GlobalAccessPermissions}; use crate::const_eval::CheckAlignment; use crate::errors::ConstEvalError; use crate::errors::{self, DanglingPtrInFinal}; @@ -138,7 +138,7 @@ pub(crate) fn mk_eval_cx_to_read_const_val<'tcx>( tcx: TyCtxt<'tcx>, root_span: Span, param_env: ty::ParamEnv<'tcx>, - can_access_mut_global: CanAccessMutGlobal, + can_access_mut_global: GlobalAccessPermissions, ) -> CompileTimeInterpCx<'tcx> { debug!("mk_eval_cx: {:?}", param_env); InterpCx::new( @@ -157,7 +157,8 @@ pub fn mk_eval_cx_for_const_val<'tcx>( val: mir::ConstValue<'tcx>, ty: Ty<'tcx>, ) -> Option<(CompileTimeInterpCx<'tcx>, OpTy<'tcx>)> { - let ecx = mk_eval_cx_to_read_const_val(tcx.tcx, tcx.span, param_env, CanAccessMutGlobal::No); + let ecx = + mk_eval_cx_to_read_const_val(tcx.tcx, tcx.span, param_env, GlobalAccessPermissions::Static); let op = ecx.const_val_to_op(val, ty, None).ok()?; Some((ecx, op)) } @@ -261,7 +262,7 @@ pub(crate) fn turn_into_const_value<'tcx>( tcx, tcx.def_span(key.value.instance.def_id()), key.param_env, - CanAccessMutGlobal::from(is_static), + GlobalAccessPermissions::from(is_static), ); let mplace = ecx.raw_const_to_mplace(constant).expect( @@ -383,7 +384,7 @@ fn eval_in_interpreter<'tcx, R: InterpretationResult<'tcx>>( // they do not have to behave "as if" they were evaluated at runtime. // For consts however we want to ensure they behave "as if" they were evaluated at runtime, // so we have to reject reading mutable global memory. - CompileTimeMachine::new(CanAccessMutGlobal::from(is_static), CheckAlignment::Error), + CompileTimeMachine::new(GlobalAccessPermissions::from(is_static), CheckAlignment::Error), ); let res = ecx.load_mir(cid.instance.def, cid.promoted); res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, body)).map_err(|error| { diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 4f6df857142cf..1f72a6e260491 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -58,7 +58,7 @@ pub struct CompileTimeMachine<'tcx> { /// Pattern matching on consts with references would be unsound if those references /// could point to anything mutable. Therefore, when evaluating consts and when constructing valtrees, /// we ensure that only immutable global memory can be accessed. - pub(super) can_access_mut_global: CanAccessMutGlobal, + pub(super) global_access_permissions: GlobalAccessPermissions, /// Whether to check alignment during evaluation. pub(super) check_alignment: CheckAlignment, @@ -79,26 +79,30 @@ pub enum CheckAlignment { } #[derive(Copy, Clone, PartialEq)] -pub(crate) enum CanAccessMutGlobal { - No, - Yes, +pub(crate) enum GlobalAccessPermissions { + /// Not allowed to read from static items at all + Nothing, + /// Allowed to read from immutable statics + Static, + /// Allowed to read from mutable statics + StaticMut, } -impl From for CanAccessMutGlobal { +impl From for GlobalAccessPermissions { fn from(value: bool) -> Self { - if value { Self::Yes } else { Self::No } + if value { Self::StaticMut } else { Self::Static } } } impl<'tcx> CompileTimeMachine<'tcx> { pub(crate) fn new( - can_access_mut_global: CanAccessMutGlobal, + global_access_permissions: GlobalAccessPermissions, check_alignment: CheckAlignment, ) -> Self { CompileTimeMachine { num_evaluated_steps: 0, stack: Vec::new(), - can_access_mut_global, + global_access_permissions, check_alignment, static_root_ids: None, } @@ -684,7 +688,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { machine: &Self, alloc_id: AllocId, alloc: ConstAllocation<'tcx>, - _static_def_id: Option, + static_def_id: Option, is_write: bool, ) -> InterpResult<'tcx> { let alloc = alloc.inner(); @@ -695,18 +699,26 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { Mutability::Mut => Err(ConstEvalErrKind::ModifiedGlobal.into()), } } else { - // Read access. These are usually allowed, with some exceptions. - if machine.can_access_mut_global == CanAccessMutGlobal::Yes { - // Machine configuration allows us read from anything (e.g., `static` initializer). - Ok(()) - } else if alloc.mutability == Mutability::Mut { - // Machine configuration does not allow us to read statics (e.g., `const` - // initializer). - Err(ConstEvalErrKind::ConstAccessesMutGlobal.into()) - } else { + let check_mutability = || match alloc.mutability { + Mutability::Mut => { + // Machine configuration does not allow us to read statics (e.g., `const` + // initializer). + Err(ConstEvalErrKind::ConstAccessesMutGlobal.into()) + } // Immutable global, this read is fine. - assert_eq!(alloc.mutability, Mutability::Not); - Ok(()) + Mutability::Not => Ok(()), + }; + match machine.global_access_permissions { + // Machine configuration allows us read from anything (e.g., `static` initializer). + GlobalAccessPermissions::StaticMut => Ok(()), + GlobalAccessPermissions::Static => check_mutability(), + GlobalAccessPermissions::Nothing => { + if static_def_id.is_none() { + check_mutability() + } else { + Err(ConstEvalErrKind::ConstAccessesMutGlobal.into()) + } + } } } } diff --git a/compiler/rustc_const_eval/src/const_eval/mod.rs b/compiler/rustc_const_eval/src/const_eval/mod.rs index 4ae4816e33ab9..e7fd0d1f9a55e 100644 --- a/compiler/rustc_const_eval/src/const_eval/mod.rs +++ b/compiler/rustc_const_eval/src/const_eval/mod.rs @@ -31,6 +31,8 @@ pub(crate) enum ValTreeCreationError { NodesOverflow, /// Values of this type, or this particular value, are not supported as valtrees. NonSupportedType, + /// Tried to create a valtree with a reference to a static. + StaticRef, } pub(crate) type ValTreeCreationResult<'tcx> = Result, ValTreeCreationError>; diff --git a/compiler/rustc_const_eval/src/const_eval/valtrees.rs b/compiler/rustc_const_eval/src/const_eval/valtrees.rs index 2e8ad445cf5e8..ffb2666ed08e1 100644 --- a/compiler/rustc_const_eval/src/const_eval/valtrees.rs +++ b/compiler/rustc_const_eval/src/const_eval/valtrees.rs @@ -11,8 +11,9 @@ use tracing::{debug, instrument, trace}; use super::eval_queries::{mk_eval_cx_to_read_const_val, op_to_const}; use super::machine::CompileTimeInterpCx; use super::{ValTreeCreationError, ValTreeCreationResult, VALTREE_MAX_NODES}; -use crate::const_eval::CanAccessMutGlobal; +use crate::const_eval::GlobalAccessPermissions; use crate::errors::MaxNumNodesInConstErr; +use crate::errors::StaticRefErr; use crate::interpret::MPlaceTy; use crate::interpret::{ intern_const_alloc_recursive, ImmTy, Immediate, InternKind, MemPlaceMeta, MemoryKind, PlaceTy, @@ -138,6 +139,16 @@ fn const_to_valtree_inner<'tcx>( ty::Ref(_, _, _) => { let derefd_place = ecx.deref_pointer(place)?; + if let Some(prov) = derefd_place.ptr().provenance { + match ecx.tcx.global_alloc(prov.alloc_id()) { + mir::interpret::GlobalAlloc::Function(_) => unreachable!(), + mir::interpret::GlobalAlloc::VTable(_, _) => unreachable!(), + mir::interpret::GlobalAlloc::Static(_) => { + return Err(ValTreeCreationError::StaticRef); + }, + mir::interpret::GlobalAlloc::Memory(_) => {}, + } + } const_to_valtree_inner(ecx, &derefd_place, num_nodes) } @@ -236,18 +247,26 @@ pub(crate) fn eval_to_valtree<'tcx>( let const_alloc = tcx.eval_to_allocation_raw(param_env.and(cid))?; // FIXME Need to provide a span to `eval_to_valtree` - let ecx = mk_eval_cx_to_read_const_val( + let mut ecx = mk_eval_cx_to_read_const_val( tcx, DUMMY_SP, param_env, // It is absolutely crucial for soundness that // we do not read from mutable memory. - CanAccessMutGlobal::No, + GlobalAccessPermissions::Static, ); let place = ecx.raw_const_to_mplace(const_alloc).unwrap(); debug!(?place); let mut num_nodes = 0; + + // To preserve provenance of static items, it is crucial that we do not + // treat them as constants and just read their values. + // This flag will cause valtree creation to ICE if a reference to a static + // is read, so valtree creation needs to eagerly catch those cases and handle + // them in custom ways. Currently by reporting a hard error, but we can opt to + // create a special valtree node for statics in the future. + ecx.machine.global_access_permissions = GlobalAccessPermissions::Nothing; let valtree_result = const_to_valtree_inner(&ecx, &place, &mut num_nodes); match valtree_result { @@ -262,6 +281,10 @@ pub(crate) fn eval_to_valtree<'tcx>( tcx.dcx().emit_err(MaxNumNodesInConstErr { span, global_const_id }); Err(handled.into()) } + ValTreeCreationError::StaticRef => { + let handled = tcx.dcx().emit_err(StaticRefErr { span }); + Err(handled.into()) + } ValTreeCreationError::NonSupportedType => Ok(None), } } @@ -301,8 +324,12 @@ pub fn valtree_to_const_value<'tcx>( } ty::Pat(ty, _) => valtree_to_const_value(tcx, param_env.and(ty), valtree), ty::Ref(_, inner_ty, _) => { - let mut ecx = - mk_eval_cx_to_read_const_val(tcx, DUMMY_SP, param_env, CanAccessMutGlobal::No); + let mut ecx = mk_eval_cx_to_read_const_val( + tcx, + DUMMY_SP, + param_env, + GlobalAccessPermissions::Static, + ); let imm = valtree_to_ref(&mut ecx, valtree, inner_ty); let imm = ImmTy::from_immediate(imm, tcx.layout_of(param_env_ty).unwrap()); op_to_const(&ecx, &imm.into(), /* for diagnostics */ false) @@ -329,8 +356,12 @@ pub fn valtree_to_const_value<'tcx>( bug!("could not find non-ZST field during in {layout:#?}"); } - let mut ecx = - mk_eval_cx_to_read_const_val(tcx, DUMMY_SP, param_env, CanAccessMutGlobal::No); + let mut ecx = mk_eval_cx_to_read_const_val( + tcx, + DUMMY_SP, + param_env, + GlobalAccessPermissions::Static, + ); // Need to create a place for this valtree. let place = create_valtree_place(&mut ecx, layout, valtree); diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index e5ea4c3442eac..3b3576732b97f 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -126,6 +126,13 @@ pub(crate) struct MaxNumNodesInConstErr { pub global_const_id: String, } +#[derive(Diagnostic)] +#[diag(const_eval_ref_to_static)] +pub(crate) struct StaticRefErr { + #[primary_span] + pub span: Option, +} + #[derive(Diagnostic)] #[diag(const_eval_unallowed_fn_pointer_call)] pub(crate) struct UnallowedFnPointerCall { @@ -640,9 +647,6 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> { const_eval_validation_ref_to_uninhabited } - PtrToStatic { ptr_kind: PointerKind::Box } => const_eval_validation_box_to_static, - PtrToStatic { ptr_kind: PointerKind::Ref(_) } => const_eval_validation_ref_to_static, - PointerAsInt { .. } => const_eval_validation_pointer_as_int, PartialPointer => const_eval_validation_partial_pointer, ConstRefToMutable => const_eval_validation_const_ref_to_mutable, @@ -807,7 +811,6 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> { ); } NullPtr { .. } - | PtrToStatic { .. } | ConstRefToMutable | ConstRefToExtern | MutableRefToImmutable diff --git a/compiler/rustc_const_eval/src/util/caller_location.rs b/compiler/rustc_const_eval/src/util/caller_location.rs index 3b07bee2d9c27..63409e2fb1eee 100644 --- a/compiler/rustc_const_eval/src/util/caller_location.rs +++ b/compiler/rustc_const_eval/src/util/caller_location.rs @@ -7,7 +7,9 @@ use rustc_middle::ty::{self, Mutability}; use rustc_span::symbol::Symbol; use tracing::trace; -use crate::const_eval::{mk_eval_cx_to_read_const_val, CanAccessMutGlobal, CompileTimeInterpCx}; +use crate::const_eval::{ + mk_eval_cx_to_read_const_val, CompileTimeInterpCx, GlobalAccessPermissions, +}; use crate::interpret::*; /// Allocate a `const core::panic::Location` with the provided filename and line/column numbers. @@ -62,7 +64,7 @@ pub(crate) fn const_caller_location_provider( tcx.tcx, tcx.span, ty::ParamEnv::reveal_all(), - CanAccessMutGlobal::No, + GlobalAccessPermissions::Static, ); let loc_place = alloc_caller_location(&mut ecx, file, line, col); 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 daf57285ebe6d..a3917265e443f 100644 --- a/compiler/rustc_const_eval/src/util/check_validity_requirement.rs +++ b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs @@ -3,7 +3,7 @@ use rustc_middle::ty::layout::{LayoutCx, LayoutError, LayoutOf, TyAndLayout, Val use rustc_middle::ty::{ParamEnv, ParamEnvAnd, Ty, TyCtxt}; use rustc_target::abi::{Abi, FieldsShape, Scalar, Variants}; -use crate::const_eval::{CanAccessMutGlobal, CheckAlignment, CompileTimeMachine}; +use crate::const_eval::{CheckAlignment, CompileTimeMachine, GlobalAccessPermissions}; use crate::interpret::{InterpCx, MemoryKind, OpTy}; /// Determines if this type permits "raw" initialization by just transmuting some memory into an @@ -45,7 +45,7 @@ fn might_permit_raw_init_strict<'tcx>( tcx: TyCtxt<'tcx>, kind: ValidityRequirement, ) -> Result> { - let machine = CompileTimeMachine::new(CanAccessMutGlobal::No, CheckAlignment::Error); + let machine = CompileTimeMachine::new(GlobalAccessPermissions::Static, CheckAlignment::Error); let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine); diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index eabbcc2033f1f..23680f143970d 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -438,9 +438,6 @@ pub enum ValidationErrorKind<'tcx> { ptr_kind: PointerKind, ty: Ty<'tcx>, }, - PtrToStatic { - ptr_kind: PointerKind, - }, ConstRefToMutable, ConstRefToExtern, MutableRefToImmutable, diff --git a/tests/ui/consts/const_refs_to_static.rs b/tests/ui/consts/const_refs_to_static.rs index 1baa8535b2c0a..9a5ec1a46fc43 100644 --- a/tests/ui/consts/const_refs_to_static.rs +++ b/tests/ui/consts/const_refs_to_static.rs @@ -14,6 +14,4 @@ const C2: *const i32 = unsafe { std::ptr::addr_of!(S_MUT) }; fn main() { assert_eq!(*C1, 0); assert_eq!(unsafe { *C2 }, 0); - // Computing this pattern will read from an immutable static. That's fine. - assert!(matches!(&0, C1)); } diff --git a/tests/ui/consts/const_refs_to_static_fail_invalid.rs b/tests/ui/consts/const_refs_to_static_fail_invalid.rs index be1574af588a3..f1630830aeafd 100644 --- a/tests/ui/consts/const_refs_to_static_fail_invalid.rs +++ b/tests/ui/consts/const_refs_to_static_fail_invalid.rs @@ -48,4 +48,20 @@ fn mutable() { } } +fn immutable() { + static S: i32 = 0; + + const C: &i32 = unsafe { &S }; + //~^ ERROR: encountered a reference pointing to a static variable in a constant + + // This could be ok, but we'd need to teach valtrees to support + // preserving statics, because valtree creation is shared with + // const generics, which can't just erase the information about + // the static's address. + match &42 { + C => {} //~ERROR: could not evaluate constant pattern + _ => {} + } +} + fn main() {} diff --git a/tests/ui/consts/const_refs_to_static_fail_invalid.stderr b/tests/ui/consts/const_refs_to_static_fail_invalid.stderr index d5bb4847746eb..e6b8b38943ee5 100644 --- a/tests/ui/consts/const_refs_to_static_fail_invalid.stderr +++ b/tests/ui/consts/const_refs_to_static_fail_invalid.stderr @@ -49,6 +49,18 @@ error: could not evaluate constant pattern LL | C => {} | ^ -error: aborting due to 6 previous errors +error: encountered a reference pointing to a static variable in a constant + --> $DIR/const_refs_to_static_fail_invalid.rs:54:5 + | +LL | const C: &i32 = unsafe { &S }; + | ^^^^^^^^^^^^^ + +error: could not evaluate constant pattern + --> $DIR/const_refs_to_static_fail_invalid.rs:62:9 + | +LL | C => {} + | ^ + +error: aborting due to 8 previous errors For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/statics/const_generics.rs b/tests/ui/statics/const_generics.rs new file mode 100644 index 0000000000000..a92ce431f4d74 --- /dev/null +++ b/tests/ui/statics/const_generics.rs @@ -0,0 +1,22 @@ +//! check that we lose the information that `BAR` points to `FOO` +//! when going through a const generic. + +// With optimizations, LLVM will deduplicate the constant `X` whose +// value is `&42` to just be a reference to the static. This is correct, +// but obscures the issue we're trying to show. +//@ compile-flags: -Copt-level=0 + +#![feature(const_refs_to_static)] +#![feature(adt_const_params)] +#![allow(incomplete_features)] + +static FOO: usize = 42; +const BAR: &usize = &FOO; +fn foo() { + assert!(!std::ptr::eq(X, &FOO)); +} + +fn main() { + foo::(); + //~^ ERROR: encountered a reference pointing to a static variable in a constant +} diff --git a/tests/ui/statics/const_generics.stderr b/tests/ui/statics/const_generics.stderr new file mode 100644 index 0000000000000..29d3bc2ef4aa4 --- /dev/null +++ b/tests/ui/statics/const_generics.stderr @@ -0,0 +1,8 @@ +error: encountered a reference pointing to a static variable in a constant + --> $DIR/const_generics.rs:20:11 + | +LL | foo::(); + | ^^^ + +error: aborting due to 1 previous error +