interpret: remove MemoryExtra in favor of giving access to the Machine

This commit is contained in:
Ralf Jung
2022-04-03 13:05:49 -04:00
parent 15a242a432
commit f0ec783bf9
15 changed files with 194 additions and 246 deletions

View File

@@ -17,10 +17,10 @@ use rustc_ast::Mutability;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_middle::mir::display_allocation;
use rustc_middle::ty::{Instance, ParamEnv, TyCtxt};
use rustc_target::abi::{Align, HasDataLayout, Size, TargetDataLayout};
use rustc_target::abi::{Align, HasDataLayout, Size};
use super::{
alloc_range, AllocId, AllocMap, AllocRange, Allocation, CheckInAllocMsg, GlobalAlloc,
alloc_range, AllocId, AllocMap, AllocRange, Allocation, CheckInAllocMsg, GlobalAlloc, InterpCx,
InterpResult, Machine, MayLeak, Pointer, PointerArithmetic, Provenance, Scalar,
ScalarMaybeUninit,
};
@@ -108,19 +108,6 @@ pub struct Memory<'mir, 'tcx, M: Machine<'mir, 'tcx>> {
/// that do not exist any more.
// FIXME: this should not be public, but interning currently needs access to it
pub(super) dead_alloc_map: FxHashMap<AllocId, (Size, Align)>,
/// Extra data added by the machine.
pub extra: M::MemoryExtra,
/// Lets us implement `HasDataLayout`, which is awfully convenient.
pub tcx: TyCtxt<'tcx>,
}
impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout for Memory<'mir, 'tcx, M> {
#[inline]
fn data_layout(&self) -> &TargetDataLayout {
&self.tcx.data_layout
}
}
/// A reference to some allocation that was already bounds-checked for the given region
@@ -142,16 +129,21 @@ pub struct AllocRefMut<'a, 'tcx, Tag, Extra> {
}
impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
pub fn new(tcx: TyCtxt<'tcx>, extra: M::MemoryExtra) -> Self {
pub fn new() -> Self {
Memory {
alloc_map: M::MemoryMap::default(),
extra_fn_ptr_map: FxHashMap::default(),
dead_alloc_map: FxHashMap::default(),
extra,
tcx,
}
}
/// This is used by [priroda](https://github.com/oli-obk/priroda)
pub fn alloc_map(&self) -> &M::MemoryMap {
&self.alloc_map
}
}
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
/// Call this to turn untagged "global" pointers (obtained via `tcx`) into
/// the machine pointer to the allocation. Must never be used
/// for any other pointers, nor for TLS statics.
@@ -182,7 +174,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
Ok(M::tag_alloc_base_pointer(self, Pointer::new(alloc_id, offset)))
}
pub fn create_fn_alloc(
pub fn create_fn_alloc_ptr(
&mut self,
fn_val: FnVal<'tcx, M::ExtraFnVal>,
) -> Pointer<M::PointerTag> {
@@ -191,7 +183,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
FnVal::Other(extra) => {
// FIXME(RalfJung): Should we have a cache here?
let id = self.tcx.reserve_alloc_id();
let old = self.extra_fn_ptr_map.insert(id, extra);
let old = self.memory.extra_fn_ptr_map.insert(id, extra);
assert!(old.is_none());
id
}
@@ -201,17 +193,17 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
self.global_base_pointer(Pointer::from(id)).unwrap()
}
pub fn allocate(
pub fn allocate_ptr(
&mut self,
size: Size,
align: Align,
kind: MemoryKind<M::MemoryKind>,
) -> InterpResult<'static, Pointer<M::PointerTag>> {
let alloc = Allocation::uninit(size, align, M::PANIC_ON_ALLOC_FAIL)?;
Ok(self.allocate_with(alloc, kind))
Ok(self.allocate_with_ptr(alloc, kind))
}
pub fn allocate_bytes(
pub fn allocate_bytes_ptr(
&mut self,
bytes: &[u8],
align: Align,
@@ -219,10 +211,10 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
mutability: Mutability,
) -> Pointer<M::PointerTag> {
let alloc = Allocation::from_bytes(bytes, align, mutability);
self.allocate_with(alloc, kind)
self.allocate_with_ptr(alloc, kind)
}
pub fn allocate_with(
pub fn allocate_with_ptr(
&mut self,
alloc: Allocation,
kind: MemoryKind<M::MemoryKind>,
@@ -234,11 +226,11 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
"dynamically allocating global memory"
);
let alloc = M::init_allocation_extra(self, id, Cow::Owned(alloc), Some(kind));
self.alloc_map.insert(id, (kind, alloc.into_owned()));
self.memory.alloc_map.insert(id, (kind, alloc.into_owned()));
M::tag_alloc_base_pointer(self, Pointer::from(id))
}
pub fn reallocate(
pub fn reallocate_ptr(
&mut self,
ptr: Pointer<Option<M::PointerTag>>,
old_size_and_align: Option<(Size, Align)>,
@@ -256,13 +248,13 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, '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(new_size, new_align, kind)?;
let new_ptr = self.allocate_ptr(new_size, new_align, kind)?;
let old_size = match old_size_and_align {
Some((size, _align)) => size,
None => self.get_raw(alloc_id)?.size(),
None => self.get_alloc_raw(alloc_id)?.size(),
};
// This will also call the access hooks.
self.copy(
self.mem_copy(
ptr.into(),
Align::ONE,
new_ptr.into(),
@@ -270,13 +262,13 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
old_size.min(new_size),
/*nonoverlapping*/ true,
)?;
self.deallocate(ptr.into(), old_size_and_align, kind)?;
self.deallocate_ptr(ptr.into(), old_size_and_align, kind)?;
Ok(new_ptr)
}
#[instrument(skip(self), level = "debug")]
pub fn deallocate(
pub fn deallocate_ptr(
&mut self,
ptr: Pointer<Option<M::PointerTag>>,
old_size_and_align: Option<(Size, Align)>,
@@ -292,7 +284,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
);
}
let Some((alloc_kind, mut alloc)) = self.alloc_map.remove(&alloc_id) else {
let Some((alloc_kind, mut alloc)) = self.memory.alloc_map.remove(&alloc_id) else {
// Deallocating global memory -- always an error
return Err(match self.tcx.get_global_alloc(alloc_id) {
Some(GlobalAlloc::Function(..)) => {
@@ -335,14 +327,14 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
// Let the machine take some extra action
let size = alloc.size();
M::memory_deallocated(
&mut self.extra,
&mut self.machine,
&mut alloc.extra,
ptr.provenance,
alloc_range(Size::ZERO, size),
)?;
// Don't forget to remember size and align of this now-dead allocation
let old = self.dead_alloc_map.insert(alloc_id, (size, alloc.align));
let old = self.memory.dead_alloc_map.insert(alloc_id, (size, alloc.align));
if old.is_some() {
bug!("Nothing can be deallocated twice");
}
@@ -358,7 +350,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
size: Size,
align: Align,
) -> InterpResult<'tcx, Option<(AllocId, Size, Pointer<M::PointerTag>)>> {
let align = M::enforce_alignment(&self.extra).then_some(align);
let align = M::enforce_alignment(&self).then_some(align);
self.check_and_deref_ptr(
ptr,
size,
@@ -366,7 +358,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
CheckInAllocMsg::MemoryAccessTest,
|alloc_id, offset, ptr| {
let (size, align) =
self.get_size_and_align(alloc_id, AllocCheck::Dereferenceable)?;
self.get_alloc_size_and_align(alloc_id, AllocCheck::Dereferenceable)?;
Ok((size, align, (alloc_id, offset, ptr)))
},
)
@@ -392,7 +384,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
| CheckInAllocMsg::OffsetFromTest
| CheckInAllocMsg::InboundsTest => AllocCheck::Live,
};
let (size, align) = self.get_size_and_align(alloc_id, check)?;
let (size, align) = self.get_alloc_size_and_align(alloc_id, check)?;
Ok((size, align, ()))
})?;
Ok(())
@@ -456,7 +448,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
// Test align. Check this last; if both bounds and alignment are violated
// we want the error to be about the bounds.
if let Some(align) = align {
if M::force_int_for_alignment_check(&self.extra) {
if M::force_int_for_alignment_check(self) {
let addr = Scalar::from_pointer(ptr, &self.tcx)
.to_machine_usize(&self.tcx)
.expect("ptr-to-int cast for align check should never fail");
@@ -479,7 +471,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
}
/// Allocation accessors
impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
/// Helper function to obtain a global (tcx) allocation.
/// This attempts to return a reference to an existing allocation if
/// one can be found in `tcx`. That, however, is only possible if `tcx` and
@@ -517,7 +509,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
(self.tcx.eval_static_initializer(def_id)?, Some(def_id))
}
};
M::before_access_global(&self.extra, id, alloc, def_id, is_write)?;
M::before_access_global(&self.machine, id, alloc, def_id, is_write)?;
// We got tcx memory. Let the machine initialize its "extra" stuff.
let alloc = M::init_allocation_extra(
self,
@@ -530,7 +522,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
/// Gives raw access to the `Allocation`, without bounds or alignment checks.
/// The caller is responsible for calling the access hooks!
fn get_raw(
fn get_alloc_raw(
&self,
id: AllocId,
) -> InterpResult<'tcx, &Allocation<M::PointerTag, M::AllocExtra>> {
@@ -538,7 +530,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
// ways of "erroring": An actual error, or because we got a reference from
// `get_global_alloc` that we can actually use directly without inserting anything anywhere.
// So the error type is `InterpResult<'tcx, &Allocation<M::PointerTag>>`.
let a = self.alloc_map.get_or(id, || {
let a = self.memory.alloc_map.get_or(id, || {
let alloc = self.get_global_alloc(id, /*is_write*/ false).map_err(Err)?;
match alloc {
Cow::Borrowed(alloc) => {
@@ -564,27 +556,27 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
}
/// "Safe" (bounds and align-checked) allocation access.
pub fn get<'a>(
pub fn get_ptr_alloc<'a>(
&'a self,
ptr: Pointer<Option<M::PointerTag>>,
size: Size,
align: Align,
) -> InterpResult<'tcx, Option<AllocRef<'a, 'tcx, M::PointerTag, M::AllocExtra>>> {
let align = M::enforce_alignment(&self.extra).then_some(align);
let align = M::enforce_alignment(self).then_some(align);
let ptr_and_alloc = self.check_and_deref_ptr(
ptr,
size,
align,
CheckInAllocMsg::MemoryAccessTest,
|alloc_id, offset, ptr| {
let alloc = self.get_raw(alloc_id)?;
let alloc = self.get_alloc_raw(alloc_id)?;
Ok((alloc.size(), alloc.align, (alloc_id, offset, ptr, alloc)))
},
)?;
if let Some((alloc_id, offset, ptr, alloc)) = ptr_and_alloc {
let range = alloc_range(offset, size);
M::memory_read(&self.extra, &alloc.extra, ptr.provenance, range)?;
Ok(Some(AllocRef { alloc, range, tcx: self.tcx, alloc_id }))
M::memory_read(&self.machine, &alloc.extra, ptr.provenance, range)?;
Ok(Some(AllocRef { alloc, range, tcx: *self.tcx, alloc_id }))
} else {
// Even in this branch we have to be sure that we actually access the allocation, in
// order to ensure that `static FOO: Type = FOO;` causes a cycle error instead of
@@ -596,7 +588,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
/// Return the `extra` field of the given allocation.
pub fn get_alloc_extra<'a>(&'a self, id: AllocId) -> InterpResult<'tcx, &'a M::AllocExtra> {
Ok(&self.get_raw(id)?.extra)
Ok(&self.get_alloc_raw(id)?.extra)
}
/// Gives raw mutable access to the `Allocation`, without bounds or alignment checks.
@@ -604,16 +596,15 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
///
/// Also returns a ptr to `self.extra` so that the caller can use it in parallel with the
/// allocation.
fn get_raw_mut(
fn get_alloc_raw_mut(
&mut self,
id: AllocId,
) -> InterpResult<'tcx, (&mut Allocation<M::PointerTag, M::AllocExtra>, &mut M::MemoryExtra)>
{
) -> InterpResult<'tcx, (&mut Allocation<M::PointerTag, M::AllocExtra>, &mut M)> {
// We have "NLL problem case #3" here, which cannot be worked around without loss of
// efficiency even for the common case where the key is in the map.
// <https://rust-lang.github.io/rfcs/2094-nll.html#problem-case-3-conditional-control-flow-across-functions>
// (Cannot use `get_mut_or` since `get_global_alloc` needs `&self`.)
if self.alloc_map.get_mut(id).is_none() {
if self.memory.alloc_map.get_mut(id).is_none() {
// Slow path.
// Allocation not found locally, go look global.
let alloc = self.get_global_alloc(id, /*is_write*/ true)?;
@@ -621,18 +612,18 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
"I got a global allocation that I have to copy but the machine does \
not expect that to happen",
);
self.alloc_map.insert(id, (MemoryKind::Machine(kind), alloc.into_owned()));
self.memory.alloc_map.insert(id, (MemoryKind::Machine(kind), alloc.into_owned()));
}
let (_kind, alloc) = self.alloc_map.get_mut(id).unwrap();
let (_kind, alloc) = self.memory.alloc_map.get_mut(id).unwrap();
if alloc.mutability == Mutability::Not {
throw_ub!(WriteToReadOnly(id))
}
Ok((alloc, &mut self.extra))
Ok((alloc, &mut self.machine))
}
/// "Safe" (bounds and align-checked) allocation access.
pub fn get_mut<'a>(
pub fn get_ptr_alloc_mut<'a>(
&'a mut self,
ptr: Pointer<Option<M::PointerTag>>,
size: Size,
@@ -640,12 +631,12 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
) -> InterpResult<'tcx, Option<AllocRefMut<'a, 'tcx, M::PointerTag, M::AllocExtra>>> {
let parts = self.get_ptr_access(ptr, size, align)?;
if let Some((alloc_id, offset, ptr)) = parts {
let tcx = self.tcx;
let tcx = *self.tcx;
// FIXME: can we somehow avoid looking up the allocation twice here?
// We cannot call `get_raw_mut` inside `check_and_deref_ptr` as that would duplicate `&mut self`.
let (alloc, extra) = self.get_raw_mut(alloc_id)?;
let (alloc, machine) = self.get_alloc_raw_mut(alloc_id)?;
let range = alloc_range(offset, size);
M::memory_written(extra, &mut alloc.extra, ptr.provenance, range)?;
M::memory_written(machine, &mut alloc.extra, ptr.provenance, range)?;
Ok(Some(AllocRefMut { alloc, range, tcx, alloc_id }))
} else {
Ok(None)
@@ -656,16 +647,16 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
pub fn get_alloc_extra_mut<'a>(
&'a mut self,
id: AllocId,
) -> InterpResult<'tcx, (&'a mut M::AllocExtra, &'a mut M::MemoryExtra)> {
let (alloc, memory_extra) = self.get_raw_mut(id)?;
Ok((&mut alloc.extra, memory_extra))
) -> InterpResult<'tcx, (&'a mut M::AllocExtra, &'a mut M)> {
let (alloc, machine) = self.get_alloc_raw_mut(id)?;
Ok((&mut alloc.extra, machine))
}
/// Obtain the size and alignment of an allocation, even if that allocation has
/// been deallocated.
///
/// If `liveness` is `AllocCheck::MaybeDead`, this function always returns `Ok`.
pub fn get_size_and_align(
pub fn get_alloc_size_and_align(
&self,
id: AllocId,
liveness: AllocCheck,
@@ -674,7 +665,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
// Don't use `self.get_raw` here as that will
// a) cause cycles in case `id` refers to a static
// b) duplicate a global's allocation in miri
if let Some((_, alloc)) = self.alloc_map.get(id) {
if let Some((_, alloc)) = self.memory.alloc_map.get(id) {
return Ok((alloc.size(), alloc.align));
}
@@ -713,6 +704,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
// Deallocated pointers are allowed, we should be able to find
// them in the map.
Ok(*self
.memory
.dead_alloc_map
.get(&id)
.expect("deallocated pointers should all be recorded in `dead_alloc_map`"))
@@ -724,7 +716,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
}
fn get_fn_alloc(&self, id: AllocId) -> Option<FnVal<'tcx, M::ExtraFnVal>> {
if let Some(extra) = self.extra_fn_ptr_map.get(&id) {
if let Some(extra) = self.memory.extra_fn_ptr_map.get(&id) {
Some(FnVal::Other(*extra))
} else {
match self.tcx.get_global_alloc(id) {
@@ -734,7 +726,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
}
}
pub fn get_fn(
pub fn get_ptr_fn(
&self,
ptr: Pointer<Option<M::PointerTag>>,
) -> InterpResult<'tcx, FnVal<'tcx, M::ExtraFnVal>> {
@@ -747,8 +739,8 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
.ok_or_else(|| err_ub!(InvalidFunctionPointer(Pointer::new(alloc_id, offset))).into())
}
pub fn mark_immutable(&mut self, id: AllocId) -> InterpResult<'tcx> {
self.get_raw_mut(id)?.0.mutability = Mutability::Not;
pub fn alloc_mark_immutable(&mut self, id: AllocId) -> InterpResult<'tcx> {
self.get_alloc_raw_mut(id)?.0.mutability = Mutability::Not;
Ok(())
}
@@ -765,7 +757,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
pub fn dump_allocs<'a>(&'a self, mut allocs: Vec<AllocId>) -> DumpAllocs<'a, 'mir, 'tcx, M> {
allocs.sort();
allocs.dedup();
DumpAllocs { mem: self, allocs }
DumpAllocs { ecx: self, allocs }
}
/// Print leaked memory. Allocations reachable from `static_roots` or a `Global` allocation
@@ -775,14 +767,15 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
let reachable = {
let mut reachable = FxHashSet::default();
let global_kind = M::GLOBAL_KIND.map(MemoryKind::Machine);
let mut todo: Vec<_> = self.alloc_map.filter_map_collect(move |&id, &(kind, _)| {
if Some(kind) == global_kind { Some(id) } else { None }
});
let mut todo: Vec<_> =
self.memory.alloc_map.filter_map_collect(move |&id, &(kind, _)| {
if Some(kind) == global_kind { Some(id) } else { None }
});
todo.extend(static_roots);
while let Some(id) = todo.pop() {
if reachable.insert(id) {
// This is a new allocation, add its relocations to `todo`.
if let Some((_, alloc)) = self.alloc_map.get(id) {
if let Some((_, alloc)) = self.memory.alloc_map.get(id) {
todo.extend(alloc.relocations().values().map(|tag| tag.get_alloc_id()));
}
}
@@ -791,7 +784,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
};
// All allocations that are *not* `reachable` and *not* `may_leak` are considered leaking.
let leaks: Vec<_> = self.alloc_map.filter_map_collect(|&id, &(kind, _)| {
let leaks: Vec<_> = self.memory.alloc_map.filter_map_collect(|&id, &(kind, _)| {
if kind.may_leak() || reachable.contains(&id) { None } else { Some(id) }
});
let n = leaks.len();
@@ -800,17 +793,12 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
}
n
}
/// This is used by [priroda](https://github.com/oli-obk/priroda)
pub fn alloc_map(&self) -> &M::MemoryMap {
&self.alloc_map
}
}
#[doc(hidden)]
/// There's no way to use this directly, it's just a helper struct for the `dump_alloc(s)` methods.
pub struct DumpAllocs<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> {
mem: &'a Memory<'mir, 'tcx, M>,
ecx: &'a InterpCx<'mir, 'tcx, M>,
allocs: Vec<AllocId>,
}
@@ -840,25 +828,25 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> std::fmt::Debug for DumpAllocs<'a,
}
write!(fmt, "{}", id)?;
match self.mem.alloc_map.get(id) {
match self.ecx.memory.alloc_map.get(id) {
Some(&(kind, ref alloc)) => {
// normal alloc
write!(fmt, " ({}, ", kind)?;
write_allocation_track_relocs(
&mut *fmt,
self.mem.tcx,
*self.ecx.tcx,
&mut allocs_to_print,
alloc,
)?;
}
None => {
// global alloc
match self.mem.tcx.get_global_alloc(id) {
match self.ecx.tcx.get_global_alloc(id) {
Some(GlobalAlloc::Memory(alloc)) => {
write!(fmt, " (unchanged global, ")?;
write_allocation_track_relocs(
&mut *fmt,
self.mem.tcx,
*self.ecx.tcx,
&mut allocs_to_print,
alloc.inner(),
)?;
@@ -867,7 +855,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> std::fmt::Debug for DumpAllocs<'a,
write!(fmt, " (fn: {})", func)?;
}
Some(GlobalAlloc::Static(did)) => {
write!(fmt, " (static: {})", self.mem.tcx.def_path_str(did))?;
write!(fmt, " (static: {})", self.ecx.tcx.def_path_str(did))?;
}
None => {
write!(fmt, " (deallocated)")?;
@@ -923,16 +911,16 @@ impl<'tcx, 'a, Tag: Provenance, Extra> AllocRef<'a, 'tcx, Tag, Extra> {
}
}
impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
/// Reads the given number of bytes from memory. Returns them as a slice.
///
/// Performs appropriate bounds checks.
pub fn read_bytes(
pub fn read_bytes_ptr(
&self,
ptr: Pointer<Option<M::PointerTag>>,
size: Size,
) -> InterpResult<'tcx, &[u8]> {
let Some(alloc_ref) = self.get(ptr, size, Align::ONE)? else {
let Some(alloc_ref) = self.get_ptr_alloc(ptr, size, Align::ONE)? else {
// zero-sized access
return Ok(&[]);
};
@@ -947,7 +935,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
/// Writes the given stream of bytes into memory.
///
/// Performs appropriate bounds checks.
pub fn write_bytes(
pub fn write_bytes_ptr(
&mut self,
ptr: Pointer<Option<M::PointerTag>>,
src: impl IntoIterator<Item = u8>,
@@ -958,7 +946,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
assert_eq!(lower, len, "can only write iterators with a precise length");
let size = Size::from_bytes(len);
let Some(alloc_ref) = self.get_mut(ptr, size, Align::ONE)? else {
let Some(alloc_ref) = self.get_ptr_alloc_mut(ptr, size, Align::ONE)? else {
// zero-sized access
assert_matches!(
src.next(),
@@ -984,7 +972,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
Ok(())
}
pub fn copy(
pub fn mem_copy(
&mut self,
src: Pointer<Option<M::PointerTag>>,
src_align: Align,
@@ -993,10 +981,10 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
size: Size,
nonoverlapping: bool,
) -> InterpResult<'tcx> {
self.copy_repeatedly(src, src_align, dest, dest_align, size, 1, nonoverlapping)
self.mem_copy_repeatedly(src, src_align, dest, dest_align, size, 1, nonoverlapping)
}
pub fn copy_repeatedly(
pub fn mem_copy_repeatedly(
&mut self,
src: Pointer<Option<M::PointerTag>>,
src_align: Align,
@@ -1019,9 +1007,9 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
// Zero-sized *source*, that means dst is also zero-sized and we have nothing to do.
return Ok(());
};
let src_alloc = self.get_raw(src_alloc_id)?;
let src_alloc = self.get_alloc_raw(src_alloc_id)?;
let src_range = alloc_range(src_offset, size);
M::memory_read(&self.extra, &src_alloc.extra, src.provenance, src_range)?;
M::memory_read(&self.machine, &src_alloc.extra, src.provenance, src_range)?;
// We need the `dest` ptr for the next operation, so we get it now.
// We already did the source checks and called the hooks so we are good to return early.
let Some((dest_alloc_id, dest_offset, dest)) = dest_parts else {
@@ -1044,7 +1032,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
let compressed = src_alloc.compress_uninit_range(src_range);
// Destination alloc preparations and access hooks.
let (dest_alloc, extra) = self.get_raw_mut(dest_alloc_id)?;
let (dest_alloc, extra) = self.get_alloc_raw_mut(dest_alloc_id)?;
let dest_range = alloc_range(dest_offset, size * num_copies);
M::memory_written(extra, &mut dest_alloc.extra, dest.provenance, dest_range)?;
let dest_bytes = dest_alloc
@@ -1112,7 +1100,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
}
/// Machine pointer introspection.
impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
pub fn scalar_to_ptr(&self, scalar: Scalar<M::PointerTag>) -> Pointer<Option<M::PointerTag>> {
// We use `to_bits_or_ptr_internal` since we are just implementing the method people need to
// call to force getting out a pointer.
@@ -1129,6 +1117,29 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
}
}
/// Test if this value might be null.
/// If the machine does not support ptr-to-int casts, this is conservative.
pub fn scalar_may_be_null(&self, scalar: Scalar<M::PointerTag>) -> bool {
match scalar.try_to_int() {
Ok(int) => int.is_null(),
Err(_) => {
// Can only happen during CTFE.
let ptr = self.scalar_to_ptr(scalar);
match self.ptr_try_get_alloc(ptr) {
Ok((alloc_id, offset, _)) => {
let (size, _align) = self
.get_alloc_size_and_align(alloc_id, AllocCheck::MaybeDead)
.expect("alloc info with MaybeDead cannot fail");
// If the pointer is out-of-bounds, it may be null.
// Note that one-past-the-end (offset == size) is still inbounds, and never null.
offset > size
}
Err(_offset) => bug!("a non-int scalar is always a pointer"),
}
}
}
}
/// Turning a "maybe pointer" into a proper pointer (and some information
/// about where it points), or an absolute address.
pub fn ptr_try_get_alloc(