Skip to content

const-eval: allow constants to refer to mutable/external memory, but reject such constants as patterns #140942

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions compiler/rustc_const_eval/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,6 @@ const_eval_long_running =
.label = the const evaluator is currently interpreting this expression
.help = the constant being evaluated

const_eval_max_num_nodes_in_const = maximum number of nodes exceeded in constant {$global_const_id}

const_eval_memory_exhausted =
tried to allocate more memory than available to compiler

Expand Down Expand Up @@ -440,9 +438,6 @@ const_eval_unwind_past_top =
## (We'd love to sort this differently to make that more clear but tidy won't let us...)
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`
const_eval_validation_const_ref_to_mutable = {$front_matter}: encountered reference to mutable memory in `const`

const_eval_validation_dangling_box_no_provenance = {$front_matter}: encountered a dangling box ({$pointer} has no provenance)
const_eval_validation_dangling_box_out_of_bounds = {$front_matter}: encountered a dangling box (going beyond the bounds of its allocation)
const_eval_validation_dangling_box_use_after_free = {$front_matter}: encountered a dangling box (use-after-free)
Expand Down Expand Up @@ -482,6 +477,7 @@ const_eval_validation_invalid_ref_meta = {$front_matter}: encountered invalid re
const_eval_validation_invalid_ref_slice_meta = {$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object
const_eval_validation_invalid_vtable_ptr = {$front_matter}: encountered {$value}, but expected a vtable pointer
const_eval_validation_invalid_vtable_trait = {$front_matter}: wrong trait in wide pointer vtable: expected `{$expected_dyn_type}`, but encountered `{$vtable_dyn_type}`
const_eval_validation_mutable_ref_in_const = {$front_matter}: encountered mutable reference in `const` value
const_eval_validation_mutable_ref_to_immutable = {$front_matter}: encountered mutable reference or box pointing to read-only memory
const_eval_validation_never_val = {$front_matter}: encountered a value of the never type `!`
const_eval_validation_null_box = {$front_matter}: encountered a null box
Expand Down
7 changes: 0 additions & 7 deletions compiler/rustc_const_eval/src/const_eval/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,6 @@ pub(crate) use self::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<'tcx> {
NodesOverflow,
/// Values of this type, or this particular value, are not supported as valtrees.
NonSupportedType(Ty<'tcx>),
}
pub(crate) type ValTreeCreationResult<'tcx> = Result<ty::ValTree<'tcx>, ValTreeCreationError<'tcx>>;

#[instrument(skip(tcx), level = "debug")]
pub(crate) fn try_destructure_mir_constant_for_user_output<'tcx>(
tcx: TyCtxt<'tcx>,
Expand Down
38 changes: 10 additions & 28 deletions compiler/rustc_const_eval/src/const_eval/valtrees.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
use rustc_abi::{BackendRepr, VariantIdx};
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId, ReportedErrorInfo};
use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId, ValTreeCreationError};
use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout};
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::{bug, mir};
use rustc_span::DUMMY_SP;
use tracing::{debug, instrument, trace};

use super::VALTREE_MAX_NODES;
use super::eval_queries::{mk_eval_cx_to_read_const_val, op_to_const};
use super::machine::CompileTimeInterpCx;
use super::{VALTREE_MAX_NODES, ValTreeCreationError, ValTreeCreationResult};
use crate::const_eval::CanAccessMutGlobal;
use crate::errors::MaxNumNodesInConstErr;
use crate::interpret::{
ImmTy, Immediate, InternKind, MPlaceTy, MemPlaceMeta, MemoryKind, PlaceTy, Projectable, Scalar,
intern_const_alloc_recursive,
Expand All @@ -24,7 +23,7 @@ fn branches<'tcx>(
field_count: usize,
variant: Option<VariantIdx>,
num_nodes: &mut usize,
) -> ValTreeCreationResult<'tcx> {
) -> EvalToValTreeResult<'tcx> {
let place = match variant {
Some(variant) => ecx.project_downcast(place, variant).unwrap(),
None => place.clone(),
Expand Down Expand Up @@ -58,7 +57,7 @@ fn slice_branches<'tcx>(
ecx: &CompileTimeInterpCx<'tcx>,
place: &MPlaceTy<'tcx>,
num_nodes: &mut usize,
) -> ValTreeCreationResult<'tcx> {
) -> EvalToValTreeResult<'tcx> {
let n = place.len(ecx).unwrap_or_else(|_| panic!("expected to use len of place {place:?}"));

let mut elems = Vec::with_capacity(n as usize);
Expand All @@ -76,7 +75,7 @@ fn const_to_valtree_inner<'tcx>(
ecx: &CompileTimeInterpCx<'tcx>,
place: &MPlaceTy<'tcx>,
num_nodes: &mut usize,
) -> ValTreeCreationResult<'tcx> {
) -> EvalToValTreeResult<'tcx> {
let tcx = *ecx.tcx;
let ty = place.layout.ty;
debug!("ty kind: {:?}", ty.kind());
Expand All @@ -91,7 +90,7 @@ fn const_to_valtree_inner<'tcx>(
Ok(ty::ValTree::zst(tcx))
}
ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Char => {
let val = ecx.read_immediate(place).unwrap();
let val = ecx.read_immediate(place).report_err()?;
let val = val.to_scalar_int().unwrap();
*num_nodes += 1;

Expand All @@ -113,7 +112,7 @@ 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 val = ecx.read_immediate(place).unwrap();
let val = ecx.read_immediate(place).report_err()?;
// We could allow wide raw pointers where both sides are integers in the future,
// but for now we reject them.
if matches!(val.layout.backend_repr, BackendRepr::ScalarPair(..)) {
Expand All @@ -134,7 +133,7 @@ fn const_to_valtree_inner<'tcx>(
ty::FnPtr(..) => Err(ValTreeCreationError::NonSupportedType(ty)),

ty::Ref(_, _, _) => {
let derefd_place = ecx.deref_pointer(place).unwrap();
let derefd_place = ecx.deref_pointer(place).report_err()?;
const_to_valtree_inner(ecx, &derefd_place, num_nodes)
}

Expand All @@ -158,7 +157,7 @@ fn const_to_valtree_inner<'tcx>(
bug!("uninhabited types should have errored and never gotten converted to valtree")
}

let variant = ecx.read_discriminant(place).unwrap();
let variant = ecx.read_discriminant(place).report_err()?;
branches(ecx, place, def.variant(variant).fields.len(), def.is_enum().then_some(variant), num_nodes)
}

Expand Down Expand Up @@ -249,24 +248,7 @@ pub(crate) fn eval_to_valtree<'tcx>(
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(Ok(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(ReportedErrorInfo::allowed_in_infallible(handled).into())
}
ValTreeCreationError::NonSupportedType(ty) => Ok(Err(ty)),
}
}
}
const_to_valtree_inner(&ecx, &place, &mut num_nodes)
}

/// Converts a `ValTree` to a `ConstValue`, which is needed after mir
Expand Down
14 changes: 2 additions & 12 deletions compiler/rustc_const_eval/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,6 @@ pub(crate) struct PanicNonStrErr {
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(const_eval_max_num_nodes_in_const)]
pub(crate) struct MaxNumNodesInConstErr {
#[primary_span]
pub span: Option<Span>,
pub global_const_id: String,
}

#[derive(Diagnostic)]
#[diag(const_eval_unallowed_fn_pointer_call)]
pub(crate) struct UnallowedFnPointerCall {
Expand Down Expand Up @@ -691,9 +683,8 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {

PointerAsInt { .. } => const_eval_validation_pointer_as_int,
PartialPointer => const_eval_validation_partial_pointer,
ConstRefToMutable => const_eval_validation_const_ref_to_mutable,
ConstRefToExtern => const_eval_validation_const_ref_to_extern,
MutableRefToImmutable => const_eval_validation_mutable_ref_to_immutable,
MutableRefInConst => const_eval_validation_mutable_ref_in_const,
NullFnPtr => const_eval_validation_null_fn_ptr,
NeverVal => const_eval_validation_never_val,
NullablePtrOutOfRange { .. } => const_eval_validation_nullable_ptr_out_of_range,
Expand Down Expand Up @@ -851,9 +842,8 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
err.arg("expected_dyn_type", expected_dyn_type.to_string());
}
NullPtr { .. }
| ConstRefToMutable
| ConstRefToExtern
| MutableRefToImmutable
| MutableRefInConst
| NullFnPtr
| NeverVal
| UnsafeCellInImmutable
Expand Down
21 changes: 10 additions & 11 deletions compiler/rustc_const_eval/src/interpret/validity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,8 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
};
let (size, _align) =
global_alloc.size_and_align(*self.ecx.tcx, self.ecx.typing_env);
let alloc_actual_mutbl =
global_alloc.mutability(*self.ecx.tcx, self.ecx.typing_env);

if let GlobalAlloc::Static(did) = global_alloc {
let DefKind::Static { nested, .. } = self.ecx.tcx.def_kind(did) else {
Expand Down Expand Up @@ -597,9 +599,11 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
skip_recursive_check = !nested;
}
CtfeValidationMode::Const { .. } => {
// We can't recursively validate `extern static`, so we better reject them.
if self.ecx.tcx.is_foreign_item(did) {
throw_validation_failure!(self.path, ConstRefToExtern);
// If this is mutable memory on an `extern static`, there's no point in checking it -- we'd
// just get errors trying to read the value.
if alloc_actual_mutbl.is_mut() || self.ecx.tcx.is_foreign_item(did)
{
skip_recursive_check = true;
}
}
}
Expand All @@ -618,22 +622,17 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
mutbl
}
};
// Determine what it actually points to.
let alloc_actual_mutbl =
global_alloc.mutability(*self.ecx.tcx, self.ecx.typing_env);
// Mutable pointer to immutable memory is no good.
if ptr_expected_mutbl == Mutability::Mut
&& alloc_actual_mutbl == Mutability::Not
{
// This can actually occur with transmutes.
throw_validation_failure!(self.path, MutableRefToImmutable);
}
// In a const, everything must be completely immutable.
// In a const, any kind of mutable reference is not good.
if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. })) {
if ptr_expected_mutbl == Mutability::Mut
|| alloc_actual_mutbl == Mutability::Mut
{
throw_validation_failure!(self.path, ConstRefToMutable);
if ptr_expected_mutbl == Mutability::Mut {
throw_validation_failure!(self.path, MutableRefInConst);
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_middle/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ middle_erroneous_constant = erroneous constant encountered
middle_failed_writing_file =
failed to write file {$path}: {$error}"

# Note: We only mention patterns here since the error can only occur with references, and those
# are forbidden in const generics.
middle_invalid_const_in_valtree = constant {$global_const_id} cannot be used as pattern
.note = constants that reference mutable or external memory cannot be used as pattern

middle_layout_cycle =
a cycle occurred during layout computation

Expand All @@ -95,6 +100,8 @@ middle_layout_too_generic = the type `{$ty}` does not have a fixed layout
middle_layout_unknown =
the type `{$ty}` has an unknown layout

middle_max_num_nodes_in_valtree = maximum number of nodes exceeded in constant {$global_const_id}

middle_opaque_hidden_type_mismatch =
concrete type differs from previous defining opaque type use
.label = expected `{$self_ty}`, got `{$other_ty}`
Expand Down
17 changes: 17 additions & 0 deletions compiler/rustc_middle/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,20 @@ pub(crate) struct TypeLengthLimit {
pub path: PathBuf,
pub type_length: usize,
}

#[derive(Diagnostic)]
#[diag(middle_max_num_nodes_in_valtree)]
pub(crate) struct MaxNumNodesInValtree {
#[primary_span]
pub span: Span,
pub global_const_id: String,
}

#[derive(Diagnostic)]
#[diag(middle_invalid_const_in_valtree)]
#[note]
pub(crate) struct InvalidConstInValtree {
#[primary_span]
pub span: Span,
pub global_const_id: String,
}
52 changes: 44 additions & 8 deletions compiler/rustc_middle/src/mir/interpret/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl From<ReportedErrorInfo> for ErrorHandled {
}

impl ErrorHandled {
pub fn with_span(self, span: Span) -> Self {
pub(crate) fn with_span(self, span: Span) -> Self {
match self {
ErrorHandled::Reported(err, _span) => ErrorHandled::Reported(err, span),
ErrorHandled::TooGeneric(_span) => ErrorHandled::TooGeneric(span),
Expand Down Expand Up @@ -94,14 +94,51 @@ impl From<ReportedErrorInfo> for ErrorGuaranteed {
}
}

/// An error type for the `const_to_valtree` query. Some error should be reported with a "use-site span",
/// which means the query cannot emit the error, so those errors are represented as dedicated variants here.
#[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
pub enum ValTreeCreationError<'tcx> {
/// The constant is too big to be valtree'd.
NodesOverflow,
/// The constant references mutable or external memory, so it cannot be valtree'd.
InvalidConst,
/// Values of this type, or this particular value, are not supported as valtrees.
NonSupportedType(Ty<'tcx>),
/// The error has already been handled by const evaluation.
ErrorHandled(ErrorHandled),
}

impl<'tcx> From<ErrorHandled> for ValTreeCreationError<'tcx> {
fn from(err: ErrorHandled) -> Self {
ValTreeCreationError::ErrorHandled(err)
}
}

impl<'tcx> From<InterpErrorInfo<'tcx>> for ValTreeCreationError<'tcx> {
fn from(err: InterpErrorInfo<'tcx>) -> Self {
// An error ocurred outside the const-eval query, as part of constructing the valtree. We
// don't currently preserve the details of this error, since `InterpErrorInfo` cannot be put
// into a query result and it can only be access of some mutable or external memory.
let (_kind, backtrace) = err.into_parts();
backtrace.print_backtrace();
ValTreeCreationError::InvalidConst
}
}

impl<'tcx> ValTreeCreationError<'tcx> {
pub(crate) fn with_span(self, span: Span) -> Self {
use ValTreeCreationError::*;
match self {
ErrorHandled(handled) => ErrorHandled(handled.with_span(span)),
other => other,
}
}
}

pub type EvalToAllocationRawResult<'tcx> = Result<ConstAlloc<'tcx>, ErrorHandled>;
pub type EvalStaticInitializerRawResult<'tcx> = Result<ConstAllocation<'tcx>, ErrorHandled>;
pub type EvalToConstValueResult<'tcx> = Result<ConstValue<'tcx>, ErrorHandled>;
/// `Ok(Err(ty))` indicates the constant was fine, but the valtree couldn't be constructed
/// because the value contains something of type `ty` that is not valtree-compatible.
/// The caller can then show an appropriate error; the query does not have the
/// necessary context to give good user-facing errors for this case.
pub type EvalToValTreeResult<'tcx> = Result<Result<ValTree<'tcx>, Ty<'tcx>>, ErrorHandled>;
pub type EvalToValTreeResult<'tcx> = Result<ValTree<'tcx>, ValTreeCreationError<'tcx>>;

#[cfg(target_pointer_width = "64")]
rustc_data_structures::static_assert_size!(InterpErrorInfo<'_>, 8);
Expand Down Expand Up @@ -450,10 +487,9 @@ pub enum ValidationErrorKind<'tcx> {
ptr_kind: PointerKind,
ty: Ty<'tcx>,
},
ConstRefToMutable,
ConstRefToExtern,
MutableRefToImmutable,
UnsafeCellInImmutable,
MutableRefInConst,
NullFnPtr,
NeverVal,
NullablePtrOutOfRange {
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_middle/src/mir/interpret/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ pub use self::error::{
EvalToAllocationRawResult, EvalToConstValueResult, EvalToValTreeResult, ExpectedKind,
InterpErrorInfo, InterpErrorKind, InterpResult, InvalidMetaKind, InvalidProgramInfo,
MachineStopType, Misalignment, PointerKind, ReportedErrorInfo, ResourceExhaustionInfo,
ScalarSizeMismatch, UndefinedBehaviorInfo, UnsupportedOpInfo, ValidationErrorInfo,
ValidationErrorKind, interp_ok,
ScalarSizeMismatch, UndefinedBehaviorInfo, UnsupportedOpInfo, ValTreeCreationError,
ValidationErrorInfo, ValidationErrorKind, interp_ok,
};
pub use self::pointer::{CtfeProvenance, Pointer, PointerArithmetic, Provenance};
pub use self::value::Scalar;
Expand Down
Loading
Loading