use rustc_ast::ast::Mutability;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir::def_id::DefId;
use rustc_middle::ty::{self, Instance, ParamEnv, TyCtxt};
use rustc_target::abi::{Align, HasDataLayout, Size, TargetDataLayout};
pub tcx: TyCtxt<'tcx>,
}
+/// Return the `tcx` allocation containing the initial value of the given static
+pub fn get_static(tcx: TyCtxt<'tcx>, def_id: DefId) -> InterpResult<'tcx, &'tcx Allocation> {
+ trace!("get_static: Need to compute {:?}", def_id);
+ let instance = Instance::mono(tcx, def_id);
+ let gid = GlobalId { instance, promoted: None };
+ // Use the raw query here to break validation cycles. Later uses of the static
+ // will call the full query anyway.
+ let raw_const = tcx.const_eval_raw(ty::ParamEnv::reveal_all().and(gid))?;
+ Ok(tcx.global_alloc(raw_const.alloc_id).unwrap_memory())
+}
+
impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout for Memory<'mir, 'tcx, M> {
#[inline]
fn data_layout(&self) -> &TargetDataLayout {
}
/// Call this to turn untagged "global" pointers (obtained via `tcx`) into
- /// the *canonical* machine pointer to the allocation. Must never be used
- /// for any other pointers!
+ /// the machine pointer to the allocation. Must never be used
+ /// for any other pointers, nor for TLS statics.
+ ///
+ /// Using the resulting pointer represents a *direct* access to that memory
+ /// (e.g. by directly using a `static`),
+ /// as opposed to access through a pointer that was created by the program.
///
- /// This represents a *direct* access to that memory, as opposed to access
- /// through a pointer that was created by the program.
+ /// This function can fail only if `ptr` points to an `extern static`.
#[inline]
- pub fn tag_global_base_pointer(&self, ptr: Pointer) -> Pointer<M::PointerTag> {
- let id = M::canonical_alloc_id(self, ptr.alloc_id);
- ptr.with_tag(M::tag_global_base_pointer(&self.extra, id))
+ pub fn global_base_pointer(
+ &self,
+ mut ptr: Pointer,
+ ) -> InterpResult<'tcx, Pointer<M::PointerTag>> {
+ // We need to handle `extern static`.
+ let ptr = match self.tcx.get_global_alloc(ptr.alloc_id) {
+ Some(GlobalAlloc::Static(def_id)) if self.tcx.is_thread_local_static(def_id) => {
+ bug!("global memory cannot point to thread-local static")
+ }
+ Some(GlobalAlloc::Static(def_id)) if self.tcx.is_foreign_item(def_id) => {
+ ptr.alloc_id = M::extern_static_alloc_id(self, def_id)?;
+ ptr
+ }
+ _ => {
+ // No need to change the `AllocId`.
+ ptr
+ }
+ };
+ // And we need to get the tag.
+ let tag = M::tag_global_base_pointer(&self.extra, ptr.alloc_id);
+ Ok(ptr.with_tag(tag))
}
pub fn create_fn_alloc(
id
}
};
- self.tag_global_base_pointer(Pointer::from(id))
+ // Functions are global allocations, so make sure we get the right base pointer.
+ // We know this is not an `extern static` so this cannot fail.
+ self.global_base_pointer(Pointer::from(id)).unwrap()
}
pub fn allocate(
align: Align,
kind: MemoryKind<M::MemoryKind>,
) -> Pointer<M::PointerTag> {
- let alloc = Allocation::undef(size, align);
+ let alloc = Allocation::uninit(size, align);
self.allocate_with(alloc, kind)
}
M::GLOBAL_KIND.map(MemoryKind::Machine),
"dynamically allocating global memory"
);
+ // This is a new allocation, not a new global one, so no `global_base_ptr`.
let (alloc, tag) = M::init_allocation_extra(&self.extra, id, Cow::Owned(alloc), Some(kind));
self.alloc_map.insert(id, (kind, alloc.into_owned()));
Pointer::from(id).with_tag(tag)
Some(GlobalAlloc::Function(..)) => throw_ub!(DerefFunctionPointer(id)),
None => throw_ub!(PointerUseAfterFree(id)),
Some(GlobalAlloc::Static(def_id)) => {
+ assert!(tcx.is_static(def_id));
assert!(!tcx.is_thread_local_static(def_id));
// Notice that every static has two `AllocId` that will resolve to the same
// thing here: one maps to `GlobalAlloc::Static`, this is the "lazy" ID,
// The `GlobalAlloc::Memory` branch here is still reachable though; when a static
// contains a reference to memory that was created during its evaluation (i.e., not
// to another static), those inner references only exist in "resolved" form.
- //
- // Assumes `id` is already canonical.
if tcx.is_foreign_item(def_id) {
- trace!("get_global_alloc: foreign item {:?}", def_id);
- throw_unsup!(ReadForeignStatic(def_id))
+ throw_unsup!(ReadExternStatic(def_id));
}
- trace!("get_global_alloc: Need to compute {:?}", def_id);
- let instance = Instance::mono(tcx, def_id);
- let gid = GlobalId { instance, promoted: None };
- // Use the raw query here to break validation cycles. Later uses of the static
- // will call the full query anyway.
- let raw_const =
- tcx.const_eval_raw(ty::ParamEnv::reveal_all().and(gid)).map_err(|err| {
- // no need to report anything, the const_eval call takes care of that
- // for statics
- assert!(tcx.is_static(def_id));
- err
- })?;
- // Make sure we use the ID of the resolved memory, not the lazy one!
- let id = raw_const.alloc_id;
- let allocation = tcx.global_alloc(id).unwrap_memory();
-
- (allocation, Some(def_id))
+
+ (get_static(tcx, def_id)?, Some(def_id))
}
};
M::before_access_global(memory_extra, id, alloc, def_id, is_write)?;
alloc,
M::GLOBAL_KIND.map(MemoryKind::Machine),
);
+ // Sanity check that this is the same pointer we would have gotten via `global_base_pointer`.
debug_assert_eq!(tag, M::tag_global_base_pointer(memory_extra, id));
Ok(alloc)
}
&self,
id: AllocId,
) -> InterpResult<'tcx, &Allocation<M::PointerTag, M::AllocExtra>> {
- let id = M::canonical_alloc_id(self, id);
// The error type of the inner closure here is somewhat funny. We have two
// 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.
&mut self,
id: AllocId,
) -> InterpResult<'tcx, &mut Allocation<M::PointerTag, M::AllocExtra>> {
- let id = M::canonical_alloc_id(self, id);
let tcx = self.tcx;
let memory_extra = &self.extra;
let a = self.alloc_map.get_mut_or(id, || {
id: AllocId,
liveness: AllocCheck,
) -> InterpResult<'static, (Size, Align)> {
- let id = M::canonical_alloc_id(self, id);
// # Regular allocations
// Don't use `self.get_raw` here as that will
// a) cause cycles in case `id` refers to a static
}
}
- /// Assumes `id` is already canonical.
fn get_fn_alloc(&self, id: AllocId) -> Option<FnVal<'tcx, M::ExtraFnVal>> {
trace!("reading fn ptr: {}", id);
if let Some(extra) = self.extra_fn_ptr_map.get(&id) {
if ptr.offset.bytes() != 0 {
throw_ub!(InvalidFunctionPointer(ptr.erase_tag()))
}
- let id = M::canonical_alloc_id(self, ptr.alloc_id);
- self.get_fn_alloc(id).ok_or_else(|| err_ub!(InvalidFunctionPointer(ptr.erase_tag())).into())
+ self.get_fn_alloc(ptr.alloc_id)
+ .ok_or_else(|| err_ub!(InvalidFunctionPointer(ptr.erase_tag())).into())
}
pub fn mark_immutable(&mut self, id: AllocId) -> InterpResult<'tcx> {
Ok(())
}
- /// Print an allocation and all allocations it points to, recursively.
- /// This prints directly to stderr, ignoring RUSTC_LOG! It is up to the caller to
- /// control for this.
- pub fn dump_alloc(&self, id: AllocId) {
- self.dump_allocs(vec![id]);
+ /// Create a lazy debug printer that prints the given allocation and all allocations it points
+ /// to, recursively.
+ #[must_use]
+ pub fn dump_alloc<'a>(&'a self, id: AllocId) -> DumpAllocs<'a, 'mir, 'tcx, M> {
+ self.dump_allocs(vec![id])
+ }
+
+ /// Create a lazy debug printer for a list of allocations and all allocations they point to,
+ /// recursively.
+ #[must_use]
+ pub fn dump_allocs<'a>(&'a self, mut allocs: Vec<AllocId>) -> DumpAllocs<'a, 'mir, 'tcx, M> {
+ allocs.sort();
+ allocs.dedup();
+ DumpAllocs { mem: self, allocs }
+ }
+
+ /// Print leaked memory. Allocations reachable from `static_roots` or a `Global` allocation
+ /// are not considered leaked. Leaks whose kind `may_leak()` returns true are not reported.
+ pub fn leak_report(&self, static_roots: &[AllocId]) -> usize {
+ // Collect the set of allocations that are *reachable* from `Global` allocations.
+ 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 }
+ });
+ 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) {
+ todo.extend(alloc.relocations().values().map(|&(_, target_id)| target_id));
+ }
+ }
+ }
+ reachable
+ };
+
+ // All allocations that are *not* `reachable` and *not* `may_leak` are considered leaking.
+ let leaks: Vec<_> = self.alloc_map.filter_map_collect(|&id, &(kind, _)| {
+ if kind.may_leak() || reachable.contains(&id) { None } else { Some(id) }
+ });
+ let n = leaks.len();
+ if n > 0 {
+ eprintln!("The following memory was leaked: {:?}", self.dump_allocs(leaks));
+ }
+ n
+ }
+
+ /// This is used by [priroda](https://github.com/oli-obk/priroda)
+ pub fn alloc_map(&self) -> &M::MemoryMap {
+ &self.alloc_map
}
+}
- /// Print a list of allocations and all allocations they point to, recursively.
- /// This prints directly to stderr, ignoring RUSTC_LOG! It is up to the caller to
- /// control for this.
- pub fn dump_allocs(&self, mut allocs: Vec<AllocId>) {
+#[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>,
+ allocs: Vec<AllocId>,
+}
+
+impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> std::fmt::Debug for DumpAllocs<'a, 'mir, 'tcx, M> {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Cannot be a closure because it is generic in `Tag`, `Extra`.
fn write_allocation_track_relocs<'tcx, Tag: Copy + fmt::Debug, Extra>(
+ fmt: &mut std::fmt::Formatter<'_>,
tcx: TyCtxt<'tcx>,
allocs_to_print: &mut VecDeque<AllocId>,
alloc: &Allocation<Tag, Extra>,
- ) {
+ ) -> std::fmt::Result {
for &(_, target_id) in alloc.relocations().values() {
allocs_to_print.push_back(target_id);
}
- pretty::write_allocation(tcx, alloc, &mut std::io::stderr()).unwrap();
+ write!(fmt, "{}", pretty::display_allocation(tcx, alloc))
}
- allocs.sort();
- allocs.dedup();
- let mut allocs_to_print = VecDeque::from(allocs);
+ let mut allocs_to_print: VecDeque<_> = self.allocs.iter().copied().collect();
// `allocs_printed` contains all allocations that we have already printed.
let mut allocs_printed = FxHashSet::default();
continue;
}
- eprint!("{}", id);
- match self.alloc_map.get(id) {
+ write!(fmt, "{}", id)?;
+ match self.mem.alloc_map.get(id) {
Some(&(kind, ref alloc)) => {
// normal alloc
- eprint!(" ({}, ", kind);
- write_allocation_track_relocs(self.tcx, &mut allocs_to_print, alloc);
+ write!(fmt, " ({}, ", kind)?;
+ write_allocation_track_relocs(
+ &mut *fmt,
+ self.mem.tcx,
+ &mut allocs_to_print,
+ alloc,
+ )?;
}
None => {
// global alloc
- match self.tcx.get_global_alloc(id) {
+ match self.mem.tcx.get_global_alloc(id) {
Some(GlobalAlloc::Memory(alloc)) => {
- eprint!(" (unchanged global, ");
- write_allocation_track_relocs(self.tcx, &mut allocs_to_print, alloc);
+ write!(fmt, " (unchanged global, ")?;
+ write_allocation_track_relocs(
+ &mut *fmt,
+ self.mem.tcx,
+ &mut allocs_to_print,
+ alloc,
+ )?;
}
Some(GlobalAlloc::Function(func)) => {
- eprint!(" (fn: {})", func);
+ write!(fmt, " (fn: {})", func)?;
}
Some(GlobalAlloc::Static(did)) => {
- eprint!(" (static: {})", self.tcx.def_path_str(did));
+ write!(fmt, " (static: {})", self.mem.tcx.def_path_str(did))?;
}
None => {
- eprint!(" (deallocated)");
+ write!(fmt, " (deallocated)")?;
}
}
}
}
- eprintln!();
- }
- }
-
- pub fn leak_report(&self) -> usize {
- // Collect the set of allocations that are *reachable* from `Global` allocations.
- 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 }
- });
- 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) {
- todo.extend(alloc.relocations().values().map(|&(_, target_id)| target_id));
- }
- }
- }
- reachable
- };
-
- // All allocations that are *not* `reachable` and *not* `may_leak` are considered leaking.
- let leaks: Vec<_> = self.alloc_map.filter_map_collect(|&id, &(kind, _)| {
- if kind.may_leak() || reachable.contains(&id) { None } else { Some(id) }
- });
- let n = leaks.len();
- if n > 0 {
- eprintln!("The following memory was leaked:");
- self.dump_allocs(leaks);
+ writeln!(fmt)?;
}
- n
- }
-
- /// This is used by [priroda](https://github.com/oli-obk/priroda)
- pub fn alloc_map(&self) -> &M::MemoryMap {
- &self.alloc_map
+ Ok(())
}
}
let dest_bytes = dest_bytes.as_mut_ptr();
- // Prepare a copy of the undef mask.
+ // Prepare a copy of the initialization mask.
let compressed = self.get_raw(src.alloc_id)?.compress_undef_range(src, size);
- if compressed.all_bytes_undef() {
- // Fast path: If all bytes are `undef` then there is nothing to copy. The target range
- // is marked as undef but we otherwise omit changing the byte representation which may
- // be arbitrary for undef bytes.
+ if compressed.no_bytes_init() {
+ // Fast path: If all bytes are `uninit` then there is nothing to copy. The target range
+ // is marked as uninitialized but we otherwise omit changing the byte representation which may
+ // be arbitrary for uninitialized bytes.
// This also avoids writing to the target bytes so that the backing allocation is never
- // touched if the bytes stay undef for the whole interpreter execution. On contemporary
+ // touched if the bytes stay uninitialized for the whole interpreter execution. On contemporary
// operating system this can avoid physically allocating the page.
let dest_alloc = self.get_raw_mut(dest.alloc_id)?;
- dest_alloc.mark_definedness(dest, size * length, false); // `Size` multiplication
+ dest_alloc.mark_init(dest, size * length, false); // `Size` multiplication
dest_alloc.mark_relocation_range(relocations);
return Ok(());
}
}
// now fill in all the data
- self.get_raw_mut(dest.alloc_id)?.mark_compressed_undef_range(
+ self.get_raw_mut(dest.alloc_id)?.mark_compressed_init_range(
&compressed,
dest,
size,