Skip to content
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

Forbid references to statics in valtrees #126477

Closed
wants to merge 4 commits into from
Closed
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
4 changes: 2 additions & 2 deletions compiler/rustc_const_eval/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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})
Expand Down
11 changes: 6 additions & 5 deletions compiler/rustc_const_eval/src/const_eval/eval_queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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(
Expand All @@ -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))
}
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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| {
Expand Down
52 changes: 32 additions & 20 deletions compiler/rustc_const_eval/src/const_eval/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<bool> for CanAccessMutGlobal {
impl From<bool> 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,
}
Expand Down Expand Up @@ -684,7 +688,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
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();
Expand All @@ -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())
}
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_const_eval/src/const_eval/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ty::ValTree<'tcx>, ValTreeCreationError>;

Expand Down
45 changes: 38 additions & 7 deletions compiler/rustc_const_eval/src/const_eval/valtrees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say this is less about provenance and more about preserving their unique absolute address.

// 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 {
Expand All @@ -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),
}
}
Expand Down Expand Up @@ -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)
Expand All @@ -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);
Expand Down
11 changes: 7 additions & 4 deletions compiler/rustc_const_eval/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Span>,
}

#[derive(Diagnostic)]
#[diag(const_eval_unallowed_fn_pointer_call)]
pub(crate) struct UnallowedFnPointerCall {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -807,7 +811,6 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
);
}
NullPtr { .. }
| PtrToStatic { .. }
| ConstRefToMutable
| ConstRefToExtern
| MutableRefToImmutable
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_const_eval/src/util/caller_location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -45,7 +45,7 @@ fn might_permit_raw_init_strict<'tcx>(
tcx: TyCtxt<'tcx>,
kind: ValidityRequirement,
) -> Result<bool, &'tcx LayoutError<'tcx>> {
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);

Expand Down
3 changes: 0 additions & 3 deletions compiler/rustc_middle/src/mir/interpret/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,9 +438,6 @@ pub enum ValidationErrorKind<'tcx> {
ptr_kind: PointerKind,
ty: Ty<'tcx>,
},
PtrToStatic {
ptr_kind: PointerKind,
},
ConstRefToMutable,
ConstRefToExtern,
MutableRefToImmutable,
Expand Down
2 changes: 0 additions & 2 deletions tests/ui/consts/const_refs_to_static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
16 changes: 16 additions & 0 deletions tests/ui/consts/const_refs_to_static_fail_invalid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
14 changes: 13 additions & 1 deletion tests/ui/consts/const_refs_to_static_fail_invalid.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Loading
Loading