Skip to content

Commit 583aacb

Browse files
authored
Unrolled build for #143324
Rollup merge of #143324 - RalfJung:native-call-prep, r=oli-obk interpret: move the native call preparation logic into Miri `@nia-e` has to do a bunch of changes to this logic for her native call ptrace work, and it's getting annoying that the logic is split between Miri and rustc. So this moves the logic to Miri, keeping just the generic traversal part in rustc. It is unfortunate that this means we have to expose `get_alloc_raw`/`get_alloc_raw_mut`... I hope the function name is scary enough to reduce the risk of misuse. r? `@oli-obk`
2 parents 25face9 + de1278b commit 583aacb

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)