Skip to content

Commit de1278b

Browse files
committed
interpret: move the native call preparation logic into Miri
1 parent f51c987 commit de1278b

File tree

5 files changed

+67
-61
lines changed

5 files changed

+67
-61
lines changed

compiler/rustc_const_eval/src/interpret/memory.rs

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
655655
/// The caller is responsible for calling the access hooks!
656656
///
657657
/// You almost certainly want to use `get_ptr_alloc`/`get_ptr_alloc_mut` instead.
658-
fn get_alloc_raw(
658+
pub fn get_alloc_raw(
659659
&self,
660660
id: AllocId,
661661
) -> InterpResult<'tcx, &Allocation<M::Provenance, M::AllocExtra, M::Bytes>> {
@@ -757,7 +757,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
757757
///
758758
/// Also returns a ptr to `self.extra` so that the caller can use it in parallel with the
759759
/// allocation.
760-
fn get_alloc_raw_mut(
760+
///
761+
/// You almost certainly want to use `get_ptr_alloc`/`get_ptr_alloc_mut` instead.
762+
pub fn get_alloc_raw_mut(
761763
&mut self,
762764
id: AllocId,
763765
) -> InterpResult<'tcx, (&mut Allocation<M::Provenance, M::AllocExtra, M::Bytes>, &mut M)> {
@@ -976,47 +978,36 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
976978
interp_ok(())
977979
}
978980

979-
/// Handle the effect an FFI call might have on the state of allocations.
980-
/// This overapproximates the modifications which external code might make to memory:
981-
/// We set all reachable allocations as initialized, mark all reachable provenances as exposed
982-
/// and overwrite them with `Provenance::WILDCARD`.
983-
///
984-
/// The allocations in `ids` are assumed to be already exposed.
985-
pub fn prepare_for_native_call(&mut self, ids: Vec<AllocId>) -> InterpResult<'tcx> {
981+
/// Visit all allocations reachable from the given start set, by recursively traversing the
982+
/// provenance information of those allocations.
983+
pub fn visit_reachable_allocs(
984+
&mut self,
985+
start: Vec<AllocId>,
986+
mut visit: impl FnMut(&mut Self, AllocId, &AllocInfo) -> InterpResult<'tcx>,
987+
) -> InterpResult<'tcx> {
986988
let mut done = FxHashSet::default();
987-
let mut todo = ids;
989+
let mut todo = start;
988990
while let Some(id) = todo.pop() {
989991
if !done.insert(id) {
990992
// We already saw this allocation before, don't process it again.
991993
continue;
992994
}
993995
let info = self.get_alloc_info(id);
994996

995-
// If there is no data behind this pointer, skip this.
996-
if !matches!(info.kind, AllocKind::LiveData) {
997-
continue;
998-
}
999-
1000-
// Expose all provenances in this allocation, and add them to `todo`.
1001-
let alloc = self.get_alloc_raw(id)?;
1002-
for prov in alloc.provenance().provenances() {
1003-
M::expose_provenance(self, prov)?;
1004-
if let Some(id) = prov.get_alloc_id() {
1005-
todo.push(id);
997+
// Recurse, if there is data here.
998+
// Do this *before* invoking the callback, as the callback might mutate the
999+
// allocation and e.g. replace all provenance by wildcards!
1000+
if matches!(info.kind, AllocKind::LiveData) {
1001+
let alloc = self.get_alloc_raw(id)?;
1002+
for prov in alloc.provenance().provenances() {
1003+
if let Some(id) = prov.get_alloc_id() {
1004+
todo.push(id);
1005+
}
10061006
}
10071007
}
1008-
// Also expose the provenance of the interpreter-level allocation, so it can
1009-
// be read by FFI. The `black_box` is defensive programming as LLVM likes
1010-
// to (incorrectly) optimize away ptr2int casts whose result is unused.
1011-
std::hint::black_box(alloc.get_bytes_unchecked_raw().expose_provenance());
1012-
1013-
// Prepare for possible write from native code if mutable.
1014-
if info.mutbl.is_mut() {
1015-
self.get_alloc_raw_mut(id)?
1016-
.0
1017-
.prepare_for_native_write()
1018-
.map_err(|e| e.to_interp_error(id))?;
1019-
}
1008+
1009+
// Call the callback.
1010+
visit(self, id, &info)?;
10201011
}
10211012
interp_ok(())
10221013
}
@@ -1073,7 +1064,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
10731064
todo.extend(static_roots(self));
10741065
while let Some(id) = todo.pop() {
10751066
if reachable.insert(id) {
1076-
// This is a new allocation, add the allocation it points to `todo`.
1067+
// This is a new allocation, add the allocations it points to `todo`.
1068+
// We only need to care about `alloc_map` memory here, as entirely unchanged
1069+
// global memory cannot point to memory relevant for the leak check.
10771070
if let Some((_, alloc)) = self.memory.alloc_map.get(id) {
10781071
todo.extend(
10791072
alloc.provenance().provenances().filter_map(|prov| prov.get_alloc_id()),

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

Lines changed: 1 addition & 8 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_access(&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) {
@@ -814,13 +814,6 @@ impl<Prov: Provenance, Extra, Bytes: AllocBytes> Allocation<Prov, Extra, Bytes>
814814

815815
// Set provenance of all bytes to wildcard.
816816
self.provenance.write_wildcards(self.len());
817-
818-
// Also expose the provenance of the interpreter-level allocation, so it can
819-
// be written by FFI. The `black_box` is defensive programming as LLVM likes
820-
// to (incorrectly) optimize away ptr2int casts whose result is unused.
821-
std::hint::black_box(self.get_bytes_unchecked_raw_mut().expose_provenance());
822-
823-
Ok(())
824817
}
825818

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ impl<Prov: Provenance> ProvenanceMap<Prov> {
120120
}
121121
}
122122

123-
/// Check if here is ptr-sized provenance at the given index.
123+
/// Check if there is ptr-sized provenance at the given index.
124124
/// Does not mean anything for bytewise provenance! But can be useful as an optimization.
125125
pub fn get_ptr(&self, offset: Size) -> Option<Prov> {
126126
self.ptrs.get(&offset).copied()

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

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -466,17 +466,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
466466
Some((alloc_id, Size::from_bytes(rel_offset)))
467467
}
468468

469-
/// Prepare all exposed memory for a native call.
470-
/// This overapproximates the modifications which external code might make to memory:
471-
/// We set all reachable allocations as initialized, mark all reachable provenances as exposed
472-
/// and overwrite them with `Provenance::WILDCARD`.
473-
fn prepare_exposed_for_native_call(&mut self) -> InterpResult<'tcx> {
474-
let this = self.eval_context_mut();
475-
// We need to make a deep copy of this list, but it's fine; it also serves as scratch space
476-
// for the search within `prepare_for_native_call`.
477-
let exposed: Vec<AllocId> =
478-
this.machine.alloc_addresses.get_mut().exposed.iter().copied().collect();
479-
this.prepare_for_native_call(exposed)
469+
/// Return a list of all exposed allocations.
470+
fn exposed_allocs(&self) -> Vec<AllocId> {
471+
let this = self.eval_context_ref();
472+
this.machine.alloc_addresses.borrow().exposed.iter().copied().collect()
480473
}
481474
}
482475

src/tools/miri/src/shims/native_lib/mod.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
198198
let mut libffi_args = Vec::<CArg>::with_capacity(args.len());
199199
for arg in args.iter() {
200200
if !matches!(arg.layout.backend_repr, BackendRepr::Scalar(_)) {
201-
throw_unsup_format!("only scalar argument types are support for native calls")
201+
throw_unsup_format!("only scalar argument types are supported for native calls")
202202
}
203203
let imm = this.read_immediate(arg)?;
204204
libffi_args.push(imm_to_carg(&imm, this)?);
@@ -224,16 +224,42 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
224224
this.expose_provenance(prov)?;
225225
}
226226
}
227-
228-
// Prepare all exposed memory.
229-
this.prepare_exposed_for_native_call()?;
230-
231-
// Convert them to `libffi::high::Arg` type.
227+
// Convert arguments to `libffi::high::Arg` type.
232228
let libffi_args = libffi_args
233229
.iter()
234230
.map(|arg| arg.arg_downcast())
235231
.collect::<Vec<libffi::high::Arg<'_>>>();
236232

233+
// Prepare all exposed memory (both previously exposed, and just newly exposed since a
234+
// pointer was passed as argument).
235+
this.visit_reachable_allocs(this.exposed_allocs(), |this, alloc_id, info| {
236+
// If there is no data behind this pointer, skip this.
237+
if !matches!(info.kind, AllocKind::LiveData) {
238+
return interp_ok(());
239+
}
240+
// It's okay to get raw access, what we do does not correspond to any actual
241+
// AM operation, it just approximates the state to account for the native call.
242+
let alloc = this.get_alloc_raw(alloc_id)?;
243+
// Also expose the provenance of the interpreter-level allocation, so it can
244+
// be read by FFI. The `black_box` is defensive programming as LLVM likes
245+
// to (incorrectly) optimize away ptr2int casts whose result is unused.
246+
std::hint::black_box(alloc.get_bytes_unchecked_raw().expose_provenance());
247+
// Expose all provenances in this allocation, since the native code can do $whatever.
248+
for prov in alloc.provenance().provenances() {
249+
this.expose_provenance(prov)?;
250+
}
251+
252+
// Prepare for possible write from native code if mutable.
253+
if info.mutbl.is_mut() {
254+
let alloc = &mut this.get_alloc_raw_mut(alloc_id)?.0;
255+
alloc.prepare_for_native_access();
256+
// Also expose *mutable* provenance for the interpreter-level allocation.
257+
std::hint::black_box(alloc.get_bytes_unchecked_raw_mut().expose_provenance());
258+
}
259+
260+
interp_ok(())
261+
})?;
262+
237263
// Call the function and store output, depending on return type in the function signature.
238264
let (ret, maybe_memevents) =
239265
this.call_native_with_args(link_name, dest, code_ptr, libffi_args)?;
@@ -321,7 +347,8 @@ fn imm_to_carg<'tcx>(v: &ImmTy<'tcx>, cx: &impl HasDataLayout) -> InterpResult<'
321347
CArg::USize(v.to_scalar().to_target_usize(cx)?.try_into().unwrap()),
322348
ty::RawPtr(..) => {
323349
let s = v.to_scalar().to_pointer(cx)?.addr();
324-
// This relies on the `expose_provenance` in `prepare_for_native_call`.
350+
// This relies on the `expose_provenance` in the `visit_reachable_allocs` callback
351+
// above.
325352
CArg::RawPtr(std::ptr::with_exposed_provenance_mut(s.bytes_usize()))
326353
}
327354
_ => throw_unsup_format!("unsupported argument type for native call: {}", v.layout.ty),

0 commit comments

Comments
 (0)