about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <476013+matthiaskrgr@users.noreply.github.com>2025-07-13 15:15:58 +0200
committerGitHub <noreply@github.com>2025-07-13 15:15:58 +0200
commit762b3143fca8bc76d70eef15581f73315af77ef8 (patch)
tree522aa3f0a4dbd4f20710a2bc130fbcb85d241bb3
parent061bd28ceeebf03cb779b10ad1966ec868b1f153 (diff)
parent8d0e0c6d6fae36283c11535dcb88b52baf286fc5 (diff)
downloadrust-762b3143fca8bc76d70eef15581f73315af77ef8.tar.gz
rust-762b3143fca8bc76d70eef15581f73315af77ef8.zip
Rollup merge of #143634 - nia-e:init-and-wildcards, r=RalfJung
interpret/allocation: expose init + write_wildcards on a range

Part of https://github.com/rust-lang/miri/pull/4456, so that we can mark down when a foreign access to our memory happened. Should this also move `prepare_for_native_access()` itself into Miri, given that everything there can be implemented on Miri's side?

r? `````@RalfJung`````
-rw-r--r--compiler/rustc_const_eval/src/interpret/memory.rs2
-rw-r--r--compiler/rustc_middle/src/mir/interpret/allocation.rs33
-rw-r--r--compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs28
-rw-r--r--src/tools/miri/src/shims/native_lib/mod.rs7
4 files changed, 42 insertions, 28 deletions
diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs
index bb301600135..6414821e21d 100644
--- a/compiler/rustc_const_eval/src/interpret/memory.rs
+++ b/compiler/rustc_const_eval/src/interpret/memory.rs
@@ -1498,7 +1498,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             dest_alloc
                 .write_uninit(&tcx, dest_range)
                 .map_err(|e| e.to_interp_error(dest_alloc_id))?;
-            // We can forget about the provenance, this is all not initialized anyway.
+            // `write_uninit` also resets the provenance, so we are done.
             return interp_ok(());
         }
 
diff --git a/compiler/rustc_middle/src/mir/interpret/allocation.rs b/compiler/rustc_middle/src/mir/interpret/allocation.rs
index f039849d1bb..133111ff15d 100644
--- a/compiler/rustc_middle/src/mir/interpret/allocation.rs
+++ b/compiler/rustc_middle/src/mir/interpret/allocation.rs
@@ -101,6 +101,8 @@ pub struct Allocation<Prov: Provenance = CtfeProvenance, Extra = (), Bytes = Box
     /// at the given offset.
     provenance: ProvenanceMap<Prov>,
     /// Denotes which part of this allocation is initialized.
+    ///
+    /// Invariant: the uninitialized parts have no provenance.
     init_mask: InitMask,
     /// The alignment of the allocation to detect unaligned reads.
     /// (`Align` guarantees that this is a power of two.)
@@ -796,24 +798,19 @@ impl<Prov: Provenance, Extra, Bytes: AllocBytes> Allocation<Prov, Extra, Bytes>
         Ok(())
     }
 
-    /// Initialize all previously uninitialized bytes in the entire allocation, and set
-    /// provenance of everything to `Wildcard`. Before calling this, make sure all
-    /// provenance in this allocation is exposed!
-    pub fn prepare_for_native_access(&mut self) {
-        let full_range = AllocRange { start: Size::ZERO, size: Size::from_bytes(self.len()) };
-        // Overwrite uninitialized bytes with 0, to ensure we don't leak whatever their value happens to be.
-        for chunk in self.init_mask.range_as_init_chunks(full_range) {
-            if !chunk.is_init() {
-                let uninit_bytes = &mut self.bytes
-                    [chunk.range().start.bytes_usize()..chunk.range().end.bytes_usize()];
-                uninit_bytes.fill(0);
-            }
-        }
-        // Mark everything as initialized now.
-        self.mark_init(full_range, true);
-
-        // Set provenance of all bytes to wildcard.
-        self.provenance.write_wildcards(self.len());
+    /// Mark all bytes in the given range as initialised and reset the provenance
+    /// to wildcards. This entirely breaks the normal mechanisms for tracking
+    /// initialisation and is only provided for Miri operating in native-lib
+    /// mode. UB will be missed if the underlying bytes were not actually written to.
+    ///
+    /// If `range` is `None`, defaults to performing this on the whole allocation.
+    pub fn process_native_write(&mut self, cx: &impl HasDataLayout, range: Option<AllocRange>) {
+        let range = range.unwrap_or_else(|| AllocRange {
+            start: Size::ZERO,
+            size: Size::from_bytes(self.len()),
+        });
+        self.mark_init(range, true);
+        self.provenance.write_wildcards(cx, range);
     }
 
     /// Remove all provenance in the given memory range.
diff --git a/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs b/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs
index 9c6e1664386..119d4be64e6 100644
--- a/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs
+++ b/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs
@@ -212,21 +212,37 @@ impl<Prov: Provenance> ProvenanceMap<Prov> {
         Ok(())
     }
 
-    /// Overwrites all provenance in the allocation with wildcard provenance.
+    /// Overwrites all provenance in the given range with wildcard provenance.
+    /// Pointers partially overwritten will have their provenances preserved
+    /// bytewise on their remaining bytes.
     ///
     /// Provided for usage in Miri and panics otherwise.
-    pub fn write_wildcards(&mut self, alloc_size: usize) {
+    pub fn write_wildcards(&mut self, cx: &impl HasDataLayout, range: AllocRange) {
         assert!(
             Prov::OFFSET_IS_ADDR,
             "writing wildcard provenance is not supported when `OFFSET_IS_ADDR` is false"
         );
         let wildcard = Prov::WILDCARD.unwrap();
 
-        // Remove all pointer provenances, then write wildcards into the whole byte range.
-        self.ptrs.clear();
-        let last = Size::from_bytes(alloc_size);
         let bytes = self.bytes.get_or_insert_with(Box::default);
-        for offset in Size::ZERO..last {
+
+        // Remove pointer provenances that overlap with the range, then readd the edge ones bytewise.
+        let ptr_range = Self::adjusted_range_ptrs(range, cx);
+        let ptrs = self.ptrs.range(ptr_range.clone());
+        if let Some((offset, prov)) = ptrs.first() {
+            for byte_ofs in *offset..range.start {
+                bytes.insert(byte_ofs, *prov);
+            }
+        }
+        if let Some((offset, prov)) = ptrs.last() {
+            for byte_ofs in range.end()..*offset + cx.data_layout().pointer_size() {
+                bytes.insert(byte_ofs, *prov);
+            }
+        }
+        self.ptrs.remove_range(ptr_range);
+
+        // Overwrite bytewise provenance.
+        for offset in range.start..range.end() {
             bytes.insert(offset, wildcard);
         }
     }
diff --git a/src/tools/miri/src/shims/native_lib/mod.rs b/src/tools/miri/src/shims/native_lib/mod.rs
index 9b30d8ce78b..02c3bde036d 100644
--- a/src/tools/miri/src/shims/native_lib/mod.rs
+++ b/src/tools/miri/src/shims/native_lib/mod.rs
@@ -231,7 +231,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             .collect::<Vec<libffi::high::Arg<'_>>>();
 
         // Prepare all exposed memory (both previously exposed, and just newly exposed since a
-        // pointer was passed as argument).
+        // pointer was passed as argument). Uninitialised memory is left as-is, but any data
+        // exposed this way is garbage anyway.
         this.visit_reachable_allocs(this.exposed_allocs(), |this, alloc_id, info| {
             // If there is no data behind this pointer, skip this.
             if !matches!(info.kind, AllocKind::LiveData) {
@@ -251,8 +252,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Prepare for possible write from native code if mutable.
             if info.mutbl.is_mut() {
-                let alloc = &mut this.get_alloc_raw_mut(alloc_id)?.0;
-                alloc.prepare_for_native_access();
+                let (alloc, cx) = this.get_alloc_raw_mut(alloc_id)?;
+                alloc.process_native_write(&cx.tcx, None);
                 // Also expose *mutable* provenance for the interpreter-level allocation.
                 std::hint::black_box(alloc.get_bytes_unchecked_raw_mut().expose_provenance());
             }