Skip to content

Commit

Permalink
remove allocations from intptrcast when they get freed
Browse files Browse the repository at this point in the history
  • Loading branch information
RalfJung committed Oct 14, 2023
1 parent 4c2024b commit fde8582
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 16 deletions.
47 changes: 31 additions & 16 deletions src/intptrcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ pub type GlobalState = RefCell<GlobalStateInner>;

#[derive(Clone, Debug)]
pub struct GlobalStateInner {
/// This is used as a map between the address of each allocation and its `AllocId`.
/// It is always sorted
/// This is used as a map between the address of each allocation and its `AllocId`. It is always
/// sorted. We cannot use a `HashMap` since we can be given an address that is offset from the
/// base address, and we need to find the `AllocId` it belongs to.
int_to_ptr_map: Vec<(u64, AllocId)>,
/// The base address for each allocation. We cannot put that into
/// `AllocExtra` because function pointers also have a base address, and
Expand Down Expand Up @@ -102,18 +103,14 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
}?;

// We only use this provenance if it has been exposed, *and* is still live.
// We only use this provenance if it has been exposede.
if global_state.exposed.contains(&alloc_id) {
let (_size, _align, kind) = ecx.get_alloc_info(alloc_id);
match kind {
AllocKind::LiveData | AllocKind::Function | AllocKind::VTable => {
return Some(alloc_id);
}
AllocKind::Dead => {}
}
// This must still be live, since we remove allocations from `int_to_ptr_map` when they get freed.
debug_assert!(!matches!(ecx.get_alloc_info(alloc_id).2, AllocKind::Dead));
Some(alloc_id)
} else {
None
}

None
}

fn addr_from_alloc_id(&self, alloc_id: AllocId) -> InterpResult<'tcx, u64> {
Expand All @@ -124,9 +121,12 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
Ok(match global_state.base_addr.entry(alloc_id) {
Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => {
// There is nothing wrong with a raw pointer being cast to an integer only after
// it became dangling. Hence we allow dead allocations.
let (size, align, _kind) = ecx.get_alloc_info(alloc_id);
let (size, align, kind) = ecx.get_alloc_info(alloc_id);
// This is only called just after allocation, and when adjusting `tcx` pointers. So
// the allocation can never be dead here. This also ensures that we never re-assign
// an address to an allocation that previously had an address, but then was freed
// and the address information was removed.
assert!(!matches!(kind, AllocKind::Dead));

// This allocation does not have a base address yet, pick one.
// Leave some space to the previous allocation, to give it some chance to be less aligned.
Expand Down Expand Up @@ -162,6 +162,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if global_state.next_base_addr > ecx.target_usize_max() {
throw_exhaust!(AddressSpaceFull);
}
// Also maintain the opposite mapping in `int_to_ptr_map`.
// Given that `next_base_addr` increases in each allocation, pushing the
// corresponding tuple keeps `int_to_ptr_map` sorted
global_state.int_to_ptr_map.push((base_addr, alloc_id));
Expand Down Expand Up @@ -246,7 +247,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
};

// This cannot fail: since we already have a pointer with that provenance, rel_ptr_to_addr
// must have been called in the past.
// must have been called in the past, so we can just look up the address in the map.
let base_addr = ecx.addr_from_alloc_id(alloc_id).unwrap();

// Wrapping "addr - base_addr"
Expand All @@ -260,6 +261,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
}

impl GlobalStateInner {
pub fn free_alloc_id(&mut self, dead_id: AllocId) {
// We can remove this from `base_addr`, since `addr_from_alloc_id` is only called right
// after the allocation was created (to construct the first pointer with the absolute
// address), and when converting `tcx` pointers (which point to things that can never be
// freed anyway).
self.base_addr.remove(&dead_id);
// We can also remove it from `int_to_ptr_map`, since any wildcard pointers that exist can
// no longer actually be accessing that address. This ensures `alloc_id_from_addr` never
// returns a dead allocation.
self.int_to_ptr_map.retain(|&(_, id)| id != dead_id);
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
1 change: 1 addition & 0 deletions src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1252,6 +1252,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
{
*deallocated_at = Some(machine.current_span());
}
machine.intptrcast.get_mut().free_alloc_id(alloc_id);
Ok(())
}

Expand Down

0 comments on commit fde8582

Please sign in to comment.