unstably allow constants to refer to statics and read from immutable statics

This commit is contained in:
Ralf Jung
2024-01-05 12:18:11 +01:00
parent 5f40394baa
commit 4e77e368eb
50 changed files with 548 additions and 530 deletions

View File

@@ -17,7 +17,7 @@ use crate::interpret::{ErrorHandled, InterpError, InterpErrorInfo, MachineStopTy
/// The CTFE machine has some custom error kinds.
#[derive(Clone, Debug)]
pub enum ConstEvalErrKind {
ConstAccessesStatic,
ConstAccessesMutGlobal,
ModifiedGlobal,
AssertFailure(AssertKind<ConstInt>),
Panic { msg: Symbol, line: u32, col: u32, file: Symbol },
@@ -28,7 +28,7 @@ impl MachineStopType for ConstEvalErrKind {
use crate::fluent_generated::*;
use ConstEvalErrKind::*;
match self {
ConstAccessesStatic => const_eval_const_accesses_static,
ConstAccessesMutGlobal => const_eval_const_accesses_mut_global,
ModifiedGlobal => const_eval_modified_global,
Panic { .. } => const_eval_panic,
AssertFailure(x) => x.diagnostic_message(),
@@ -37,7 +37,7 @@ impl MachineStopType for ConstEvalErrKind {
fn add_args(self: Box<Self>, adder: &mut dyn FnMut(DiagnosticArgName, DiagnosticArgValue)) {
use ConstEvalErrKind::*;
match *self {
ConstAccessesStatic | ModifiedGlobal => {}
ConstAccessesMutGlobal | ModifiedGlobal => {}
AssertFailure(kind) => kind.add_args(adder),
Panic { msg, line, col, file } => {
adder("msg".into(), msg.into_diagnostic_arg());

View File

@@ -11,7 +11,7 @@ use rustc_middle::ty::{self, TyCtxt};
use rustc_span::Span;
use rustc_target::abi::{self, Abi};
use super::{CanAccessStatics, CompileTimeEvalContext, CompileTimeInterpreter};
use super::{CanAccessMutGlobal, CompileTimeEvalContext, CompileTimeInterpreter};
use crate::const_eval::CheckAlignment;
use crate::errors;
use crate::errors::ConstEvalError;
@@ -90,14 +90,14 @@ pub(crate) fn mk_eval_cx<'mir, 'tcx>(
tcx: TyCtxt<'tcx>,
root_span: Span,
param_env: ty::ParamEnv<'tcx>,
can_access_statics: CanAccessStatics,
can_access_mut_global: CanAccessMutGlobal,
) -> CompileTimeEvalContext<'mir, 'tcx> {
debug!("mk_eval_cx: {:?}", param_env);
InterpCx::new(
tcx,
root_span,
param_env,
CompileTimeInterpreter::new(can_access_statics, CheckAlignment::No),
CompileTimeInterpreter::new(can_access_mut_global, CheckAlignment::No),
)
}
@@ -200,7 +200,7 @@ pub(crate) fn turn_into_const_value<'tcx>(
tcx,
tcx.def_span(key.value.instance.def_id()),
key.param_env,
CanAccessStatics::from(is_static),
CanAccessMutGlobal::from(is_static),
);
let mplace = ecx.raw_const_to_mplace(constant).expect(
@@ -277,9 +277,11 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
tcx,
tcx.def_span(def),
key.param_env,
// Statics (and promoteds inside statics) may access other statics, because unlike consts
// Statics (and promoteds inside statics) may access mutable global memory, because unlike consts
// they do not have to behave "as if" they were evaluated at runtime.
CompileTimeInterpreter::new(CanAccessStatics::from(is_static), CheckAlignment::Error),
// 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.
CompileTimeInterpreter::new(CanAccessMutGlobal::from(is_static), CheckAlignment::Error),
);
eval_in_interpreter(ecx, cid, is_static)
}

View File

@@ -51,13 +51,10 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> {
/// The virtual call stack.
pub(super) stack: Vec<Frame<'mir, 'tcx>>,
/// We need to make sure consts never point to anything mutable, even recursively. That is
/// relied on for pattern matching on consts with references.
/// To achieve this, two pieces have to work together:
/// * Interning makes everything outside of statics immutable.
/// * Pointers to allocations inside of statics can never leak outside, to a non-static global.
/// This boolean here controls the second part.
pub(super) can_access_statics: CanAccessStatics,
/// 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,
/// Whether to check alignment during evaluation.
pub(super) check_alignment: CheckAlignment,
@@ -73,12 +70,12 @@ pub enum CheckAlignment {
}
#[derive(Copy, Clone, PartialEq)]
pub(crate) enum CanAccessStatics {
pub(crate) enum CanAccessMutGlobal {
No,
Yes,
}
impl From<bool> for CanAccessStatics {
impl From<bool> for CanAccessMutGlobal {
fn from(value: bool) -> Self {
if value { Self::Yes } else { Self::No }
}
@@ -86,13 +83,13 @@ impl From<bool> for CanAccessStatics {
impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> {
pub(crate) fn new(
can_access_statics: CanAccessStatics,
can_access_mut_global: CanAccessMutGlobal,
check_alignment: CheckAlignment,
) -> Self {
CompileTimeInterpreter {
num_evaluated_steps: 0,
stack: Vec::new(),
can_access_statics,
can_access_mut_global,
check_alignment,
}
}
@@ -680,7 +677,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
machine: &Self,
alloc_id: AllocId,
alloc: ConstAllocation<'tcx>,
static_def_id: Option<DefId>,
_static_def_id: Option<DefId>,
is_write: bool,
) -> InterpResult<'tcx> {
let alloc = alloc.inner();
@@ -692,22 +689,15 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
}
} else {
// Read access. These are usually allowed, with some exceptions.
if machine.can_access_statics == CanAccessStatics::Yes {
if machine.can_access_mut_global == CanAccessMutGlobal::Yes {
// Machine configuration allows us read from anything (e.g., `static` initializer).
Ok(())
} else if static_def_id.is_some() {
// Machine configuration does not allow us to read statics
// (e.g., `const` initializer).
// See const_eval::machine::MemoryExtra::can_access_statics for why
// this check is so important: if we could read statics, we could read pointers
// to mutable allocations *inside* statics. These allocations are not themselves
// statics, so pointers to them can get around the check in `validity.rs`.
Err(ConstEvalErrKind::ConstAccessesStatic.into())
} else if alloc.mutability == Mutability::Mut {
// Machine configuration does not allow us to read statics (e.g., `const`
// initializer).
Err(ConstEvalErrKind::ConstAccessesMutGlobal.into())
} else {
// Immutable global, this read is fine.
// But make sure we never accept a read from something mutable, that would be
// unsound. The reason is that as the content of this allocation may be different
// now and at run-time, so if we permit reading now we might return the wrong value.
assert_eq!(alloc.mutability, Mutability::Not);
Ok(())
}

View File

@@ -1,12 +1,10 @@
// Not in interpret to make sure we do not use private implementation details
use crate::errors::MaxNumNodesInConstErr;
use crate::interpret::InterpCx;
use rustc_middle::mir;
use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId};
use rustc_middle::mir::interpret::{InterpError, InterpErrorInfo};
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_span::DUMMY_SP;
use rustc_middle::ty::{self, Ty};
mod error;
mod eval_queries;
@@ -18,55 +16,32 @@ pub use error::*;
pub use eval_queries::*;
pub use fn_queries::*;
pub use machine::*;
pub(crate) use valtrees::{const_to_valtree_inner, valtree_to_const_value};
pub(crate) use valtrees::{eval_to_valtree, valtree_to_const_value};
// We forbid type-level constants that contain more than `VALTREE_MAX_NODES` nodes.
const VALTREE_MAX_NODES: usize = 100000;
pub(crate) enum ValTreeCreationError {
NodesOverflow,
/// Values of this type, or this particular value, are not supported as valtrees.
NonSupportedType,
/// The value pointed to non-read-only memory, so we cannot make it a valtree.
NotReadOnly,
Other,
}
pub(crate) type ValTreeCreationResult<'tcx> = Result<ty::ValTree<'tcx>, ValTreeCreationError>;
/// Evaluates a constant and turns it into a type-level constant value.
pub(crate) fn eval_to_valtree<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
cid: GlobalId<'tcx>,
) -> EvalToValTreeResult<'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(
tcx,
DUMMY_SP,
param_env,
// It is absolutely crucial for soundness that
// we do not read from static items or other mutable memory.
CanAccessStatics::No,
);
let place = ecx.raw_const_to_mplace(const_alloc).unwrap();
debug!(?place);
let mut num_nodes = 0;
let valtree_result = const_to_valtree_inner(&ecx, &place, &mut num_nodes);
match valtree_result {
Ok(valtree) => Ok(Some(valtree)),
Err(err) => {
let did = cid.instance.def_id();
let global_const_id = cid.display(tcx);
match err {
ValTreeCreationError::NodesOverflow => {
let span = tcx.hir().span_if_local(did);
tcx.dcx().emit_err(MaxNumNodesInConstErr { span, global_const_id });
Ok(None)
impl From<InterpErrorInfo<'_>> for ValTreeCreationError {
fn from(err: InterpErrorInfo<'_>) -> Self {
match err.kind() {
InterpError::MachineStop(err) => {
let err = err.downcast_ref::<ConstEvalErrKind>().unwrap();
match err {
ConstEvalErrKind::ConstAccessesMutGlobal => ValTreeCreationError::NotReadOnly,
_ => ValTreeCreationError::Other,
}
ValTreeCreationError::NonSupportedType | ValTreeCreationError::Other => Ok(None),
}
_ => ValTreeCreationError::Other,
}
}
}
@@ -78,7 +53,7 @@ pub(crate) fn try_destructure_mir_constant_for_user_output<'tcx>(
ty: Ty<'tcx>,
) -> Option<mir::DestructuredConstant<'tcx>> {
let param_env = ty::ParamEnv::reveal_all();
let ecx = mk_eval_cx(tcx.tcx, tcx.span, param_env, CanAccessStatics::No);
let ecx = mk_eval_cx(tcx.tcx, tcx.span, param_env, CanAccessMutGlobal::No);
let op = ecx.const_val_to_op(val, ty, None).ok()?;
// We go to `usize` as we cannot allocate anything bigger anyway.

View File

@@ -1,17 +1,20 @@
use rustc_middle::mir;
use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId};
use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout};
use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt};
use rustc_span::DUMMY_SP;
use rustc_target::abi::{Abi, VariantIdx};
use super::eval_queries::{mk_eval_cx, op_to_const};
use super::machine::CompileTimeEvalContext;
use super::{ValTreeCreationError, ValTreeCreationResult, VALTREE_MAX_NODES};
use crate::const_eval::CanAccessStatics;
use crate::const_eval::CanAccessMutGlobal;
use crate::errors::{MaxNumNodesInConstErr, MutableDataInConstErr};
use crate::interpret::MPlaceTy;
use crate::interpret::{
intern_const_alloc_recursive, ImmTy, Immediate, InternKind, MemPlaceMeta, MemoryKind, PlaceTy,
Projectable, Scalar,
};
use rustc_middle::mir;
use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout};
use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt};
use rustc_span::DUMMY_SP;
use rustc_target::abi::{Abi, VariantIdx};
#[instrument(skip(ecx), level = "debug")]
fn branches<'tcx>(
@@ -70,7 +73,7 @@ fn slice_branches<'tcx>(
}
#[instrument(skip(ecx), level = "debug")]
pub(crate) fn const_to_valtree_inner<'tcx>(
fn const_to_valtree_inner<'tcx>(
ecx: &CompileTimeEvalContext<'tcx, 'tcx>,
place: &MPlaceTy<'tcx>,
num_nodes: &mut usize,
@@ -88,9 +91,7 @@ pub(crate) fn const_to_valtree_inner<'tcx>(
Ok(ty::ValTree::zst())
}
ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Char => {
let Ok(val) = ecx.read_immediate(place) else {
return Err(ValTreeCreationError::Other);
};
let val = ecx.read_immediate(place)?;
let val = val.to_scalar();
*num_nodes += 1;
@@ -102,19 +103,17 @@ pub(crate) fn const_to_valtree_inner<'tcx>(
// equality at compile-time (see `ptr_guaranteed_cmp`).
// However we allow those that are just integers in disguise.
// First, get the pointer. Remember it might be wide!
let Ok(val) = ecx.read_immediate(place) else {
return Err(ValTreeCreationError::Other);
};
let val = ecx.read_immediate(place)?;
// We could allow wide raw pointers where both sides are integers in the future,
// but for now we reject them.
if matches!(val.layout.abi, Abi::ScalarPair(..)) {
return Err(ValTreeCreationError::Other);
return Err(ValTreeCreationError::NonSupportedType);
}
let val = val.to_scalar();
// We are in the CTFE machine, so ptr-to-int casts will fail.
// This can only be `Ok` if `val` already is an integer.
let Ok(val) = val.try_to_int() else {
return Err(ValTreeCreationError::Other);
return Err(ValTreeCreationError::NonSupportedType);
};
// It's just a ScalarInt!
Ok(ty::ValTree::Leaf(val))
@@ -125,11 +124,7 @@ pub(crate) fn const_to_valtree_inner<'tcx>(
ty::FnPtr(_) => Err(ValTreeCreationError::NonSupportedType),
ty::Ref(_, _, _) => {
let Ok(derefd_place)= ecx.deref_pointer(place) else {
return Err(ValTreeCreationError::Other);
};
debug!(?derefd_place);
let derefd_place = ecx.deref_pointer(place)?;
const_to_valtree_inner(ecx, &derefd_place, num_nodes)
}
@@ -153,9 +148,7 @@ pub(crate) fn const_to_valtree_inner<'tcx>(
bug!("uninhabited types should have errored and never gotten converted to valtree")
}
let Ok(variant) = ecx.read_discriminant(place) else {
return Err(ValTreeCreationError::Other);
};
let variant = ecx.read_discriminant(place)?;
branches(ecx, place, def.variant(variant).fields.len(), def.is_enum().then_some(variant), num_nodes)
}
@@ -221,6 +214,59 @@ fn create_valtree_place<'tcx>(
ecx.allocate_dyn(layout, MemoryKind::Stack, meta).unwrap()
}
/// Evaluates a constant and turns it into a type-level constant value.
pub(crate) fn eval_to_valtree<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
cid: GlobalId<'tcx>,
) -> EvalToValTreeResult<'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(
tcx,
DUMMY_SP,
param_env,
// It is absolutely crucial for soundness that
// we do not read from mutable memory.
CanAccessMutGlobal::No,
);
let place = ecx.raw_const_to_mplace(const_alloc).unwrap();
debug!(?place);
let mut num_nodes = 0;
let valtree_result = const_to_valtree_inner(&ecx, &place, &mut num_nodes);
match valtree_result {
Ok(valtree) => Ok(Some(valtree)),
Err(err) => {
let did = cid.instance.def_id();
let global_const_id = cid.display(tcx);
let span = tcx.hir().span_if_local(did);
match err {
ValTreeCreationError::NodesOverflow => {
let handled =
tcx.dcx().emit_err(MaxNumNodesInConstErr { span, global_const_id });
Err(handled.into())
}
ValTreeCreationError::NotReadOnly => {
let handled =
tcx.dcx().emit_err(MutableDataInConstErr { span, global_const_id });
Err(handled.into())
}
ValTreeCreationError::Other => {
let handled = tcx.dcx().span_delayed_bug(
span.unwrap_or(DUMMY_SP),
"unexpected error during valtree construction",
);
Err(handled.into())
}
ValTreeCreationError::NonSupportedType => Ok(None),
}
}
}
}
/// 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
@@ -253,7 +299,7 @@ pub fn valtree_to_const_value<'tcx>(
}
}
ty::Ref(_, inner_ty, _) => {
let mut ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, CanAccessStatics::No);
let mut ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, CanAccessMutGlobal::No);
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)
@@ -280,7 +326,7 @@ pub fn valtree_to_const_value<'tcx>(
bug!("could not find non-ZST field during in {layout:#?}");
}
let mut ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, CanAccessStatics::No);
let mut ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, CanAccessMutGlobal::No);
// Need to create a place for this valtree.
let place = create_valtree_place(&mut ecx, layout, valtree);