Skip to content

Commit 6d80f60

Browse files
committed
const_eval, middle: expose ffi-related apis
1 parent 7a7bcbb commit 6d80f60

File tree

4 files changed

+128
-25
lines changed

4 files changed

+128
-25
lines changed

compiler/rustc_const_eval/src/interpret/memory.rs

Lines changed: 112 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -979,12 +979,18 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
979979
}
980980

981981
/// Handle the effect an FFI call might have on the state of allocations.
982-
/// This overapproximates the modifications which external code might make to memory:
983-
/// We set all reachable allocations as initialized, mark all reachable provenances as exposed
984-
/// and overwrite them with `Provenance::WILDCARD`.
982+
/// If `paranoid` is true, overapproximates the modifications which external code might make
983+
/// to memory: We set all reachable allocations as initialized, mark all reachable provenances
984+
/// as exposed and overwrite them with `Provenance::WILDCARD`. Otherwise, it just makes sure
985+
/// that all allocations are properly set up so that we don't leak whatever was in the uninit
986+
/// bytes on FFI call.
985987
///
986988
/// The allocations in `ids` are assumed to be already exposed.
987-
pub fn prepare_for_native_call(&mut self, ids: Vec<AllocId>) -> InterpResult<'tcx> {
989+
pub fn prepare_for_native_call(
990+
&mut self,
991+
ids: Vec<AllocId>,
992+
paranoid: bool,
993+
) -> InterpResult<'tcx> {
988994
let mut done = FxHashSet::default();
989995
let mut todo = ids;
990996
while let Some(id) = todo.pop() {
@@ -999,25 +1005,117 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
9991005
continue;
10001006
}
10011007

1002-
// Expose all provenances in this allocation, and add them to `todo`.
1008+
// Make sure we iterate over everything recursively, preparing the extra alloc info.
10031009
let alloc = self.get_alloc_raw(id)?;
10041010
for prov in alloc.provenance().provenances() {
1005-
M::expose_provenance(self, prov)?;
1011+
if paranoid {
1012+
// Expose all provenances in this allocation, and add them to `todo`.
1013+
M::expose_provenance(self, prov)?;
1014+
}
10061015
if let Some(id) = prov.get_alloc_id() {
10071016
todo.push(id);
10081017
}
10091018
}
1019+
10101020
// Also expose the provenance of the interpreter-level allocation, so it can
10111021
// be read by FFI. The `black_box` is defensive programming as LLVM likes
10121022
// to (incorrectly) optimize away ptr2int casts whose result is unused.
1013-
std::hint::black_box(alloc.get_bytes_unchecked_raw().expose_provenance());
1014-
1015-
// Prepare for possible write from native code if mutable.
1016-
if info.mutbl.is_mut() {
1017-
self.get_alloc_raw_mut(id)?
1018-
.0
1019-
.prepare_for_native_write()
1020-
.map_err(|e| e.to_interp_error(id))?;
1023+
if paranoid {
1024+
std::hint::black_box(alloc.get_bytes_unchecked_raw().expose_provenance());
1025+
// Prepare for possible write from native code if mutable.
1026+
if info.mutbl.is_mut() {
1027+
self.get_alloc_raw_mut(id)?.0.prepare_for_native_write();
1028+
}
1029+
}
1030+
}
1031+
interp_ok(())
1032+
}
1033+
1034+
/// Updates the machine state "as if" the accesses given had been performed.
1035+
/// Used only by Miri for FFI, for taking note of events that were intercepted from foreign
1036+
/// code and properly (but still conservatively) marking their effects. Remember to call
1037+
/// `prepare_for_native_call` with `paranoid` set to false first on the same `AllocId`s, or
1038+
/// some writes may be discarded!
1039+
///
1040+
/// The allocations in `ids` are assumed to be already exposed.
1041+
pub fn apply_accesses(
1042+
&mut self,
1043+
mut ids: Vec<AllocId>,
1044+
reads: Vec<std::ops::Range<u64>>,
1045+
writes: Vec<std::ops::Range<u64>>,
1046+
) -> InterpResult<'tcx> {
1047+
/// Helper function to avoid some code duplication over range overlaps.
1048+
fn get_start_size(
1049+
rg: std::ops::Range<u64>,
1050+
alloc_base: u64,
1051+
alloc_size: u64,
1052+
) -> Option<(u64, u64)> {
1053+
// A bunch of range bounds nonsense that effectively simplifies to
1054+
// "get the starting point of the overlap and the length from there".
1055+
// Needs to also account for the allocation being in the middle of the
1056+
// range or completely containing it
1057+
let signed_start = rg.start.cast_signed() - alloc_base.cast_signed();
1058+
let size_uncapped = if signed_start < 0 {
1059+
// If this returns, they don't overlap
1060+
(signed_start + (rg.end - rg.start).cast_signed()).try_into().ok()?
1061+
} else {
1062+
rg.end - rg.start
1063+
};
1064+
let start: u64 = signed_start.try_into().unwrap_or(0);
1065+
let size = std::cmp::min(size_uncapped, alloc_size - start);
1066+
Some((start, size))
1067+
}
1068+
1069+
let mut done = FxHashSet::default();
1070+
while let Some(id) = ids.pop() {
1071+
if !done.insert(id) {
1072+
continue;
1073+
}
1074+
let info = self.get_alloc_info(id);
1075+
1076+
// If there is no data behind this pointer, skip this.
1077+
if !matches!(info.kind, AllocKind::LiveData) {
1078+
continue;
1079+
}
1080+
1081+
let alloc_base: u64 = {
1082+
// Keep the alloc here so the borrow checker is happy
1083+
let alloc = self.get_alloc_raw(id)?;
1084+
// No need for black_box trickery since we actually use the address
1085+
alloc.get_bytes_unchecked_raw().expose_provenance().try_into().unwrap()
1086+
};
1087+
let alloc_size = info.size.bytes();
1088+
1089+
// Find reads which overlap with the current allocation
1090+
for rg in &reads {
1091+
if let Some((start, size)) = get_start_size(rg.clone(), alloc_base, alloc_size) {
1092+
let alloc = self.get_alloc_raw(id)?;
1093+
let prov_map = alloc.provenance();
1094+
// Only iterate on the bytes that overlap with the access
1095+
for i in start..start + size {
1096+
// We can be conservative and only expose provenances actually read
1097+
if let Some(prov) = prov_map.get(Size::from_bytes(1), self)
1098+
&& rg.contains(&(alloc_base + i))
1099+
{
1100+
M::expose_provenance(self, prov)?;
1101+
if let Some(id) = prov.get_alloc_id() {
1102+
ids.push(id);
1103+
}
1104+
}
1105+
}
1106+
}
1107+
}
1108+
1109+
// Then do the same thing for writes, marking down that a write happened
1110+
for rg in &writes {
1111+
if let Some((start, size)) = get_start_size(rg.clone(), alloc_base, alloc_size)
1112+
&& self.get_alloc_mutability(id)?.is_mut()
1113+
{
1114+
let alloc_mut = self.get_alloc_raw_mut(id)?.0;
1115+
let range =
1116+
AllocRange { start: Size::from_bytes(start), size: Size::from_bytes(size) };
1117+
alloc_mut.mark_foreign_write(range);
1118+
}
10211119
}
10221120
}
10231121
interp_ok(())

compiler/rustc_middle/src/mir/interpret/allocation.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@ impl<Prov: Provenance, Extra, Bytes: AllocBytes> Allocation<Prov, Extra, Bytes>
799799
/// Initialize all previously uninitialized bytes in the entire allocation, and set
800800
/// provenance of everything to `Wildcard`. Before calling this, make sure all
801801
/// provenance in this allocation is exposed!
802-
pub fn prepare_for_native_write(&mut self) -> AllocResult {
802+
pub fn prepare_for_native_write(&mut self) {
803803
let full_range = AllocRange { start: Size::ZERO, size: Size::from_bytes(self.len()) };
804804
// Overwrite uninitialized bytes with 0, to ensure we don't leak whatever their value happens to be.
805805
for chunk in self.init_mask.range_as_init_chunks(full_range) {
@@ -809,18 +809,23 @@ impl<Prov: Provenance, Extra, Bytes: AllocBytes> Allocation<Prov, Extra, Bytes>
809809
uninit_bytes.fill(0);
810810
}
811811
}
812+
self.mark_foreign_write(full_range);
813+
}
814+
815+
/// Initialise previously uninitialised bytes in the given range, and set provenance of
816+
/// everything in it to `Wildcard`. Before calling this, make sure all provenance in this
817+
/// range is exposed!
818+
pub fn mark_foreign_write(&mut self, range: AllocRange) {
812819
// Mark everything as initialized now.
813-
self.mark_init(full_range, true);
820+
self.mark_init(range, true);
814821

815-
// Set provenance of all bytes to wildcard.
816-
self.provenance.write_wildcards(self.len());
822+
// Set provenance of affected bytes to wildcard.
823+
self.provenance.write_wildcards(range);
817824

818825
// Also expose the provenance of the interpreter-level allocation, so it can
819826
// be written by FFI. The `black_box` is defensive programming as LLVM likes
820827
// to (incorrectly) optimize away ptr2int casts whose result is unused.
821828
std::hint::black_box(self.get_bytes_unchecked_raw_mut().expose_provenance());
822-
823-
Ok(())
824829
}
825830

826831
/// Remove all provenance in the given memory range.

compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,11 @@ impl<Prov: Provenance> ProvenanceMap<Prov> {
213213
Ok(())
214214
}
215215

216-
/// Overwrites all provenance in the allocation with wildcard provenance.
216+
/// Overwrites all provenance in the specified range within the allocation
217+
/// with wildcard provenance.
217218
///
218219
/// Provided for usage in Miri and panics otherwise.
219-
pub fn write_wildcards(&mut self, alloc_size: usize) {
220+
pub fn write_wildcards(&mut self, range: AllocRange) {
220221
assert!(
221222
Prov::OFFSET_IS_ADDR,
222223
"writing wildcard provenance is not supported when `OFFSET_IS_ADDR` is false"
@@ -225,9 +226,8 @@ impl<Prov: Provenance> ProvenanceMap<Prov> {
225226

226227
// Remove all pointer provenances, then write wildcards into the whole byte range.
227228
self.ptrs.clear();
228-
let last = Size::from_bytes(alloc_size);
229229
let bytes = self.bytes.get_or_insert_with(Box::default);
230-
for offset in Size::ZERO..last {
230+
for offset in range.start..range.start + range.size {
231231
bytes.insert(offset, wildcard);
232232
}
233233
}

src/tools/miri/src/alloc_addresses/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
471471
// for the search within `prepare_for_native_call`.
472472
let exposed: Vec<AllocId> =
473473
this.machine.alloc_addresses.get_mut().exposed.iter().copied().collect();
474-
this.prepare_for_native_call(exposed)
474+
this.prepare_for_native_call(exposed, true)
475475
}
476476
}
477477

0 commit comments

Comments
 (0)