Skip to content

Commit 35842d5

Browse files
committed
files: make read take callback to store result, rather than writing to 'dest' directly
1 parent cd13b44 commit 35842d5

File tree

5 files changed

+86
-69
lines changed

5 files changed

+86
-69
lines changed

src/tools/miri/src/shims/files.rs

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::any::Any;
22
use std::collections::BTreeMap;
3-
use std::io::{IsTerminal, Read, SeekFrom, Write};
3+
use std::io::{IsTerminal, SeekFrom, Write};
44
use std::marker::CoercePointee;
55
use std::ops::Deref;
66
use std::rc::{Rc, Weak};
@@ -140,8 +140,8 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
140140
_communicate_allowed: bool,
141141
_ptr: Pointer,
142142
_len: usize,
143-
_dest: &MPlaceTy<'tcx>,
144143
_ecx: &mut MiriInterpCx<'tcx>,
144+
_finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
145145
) -> InterpResult<'tcx> {
146146
throw_unsup_format!("cannot read from {}", self.name());
147147
}
@@ -207,19 +207,16 @@ impl FileDescription for io::Stdin {
207207
communicate_allowed: bool,
208208
ptr: Pointer,
209209
len: usize,
210-
dest: &MPlaceTy<'tcx>,
211210
ecx: &mut MiriInterpCx<'tcx>,
211+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
212212
) -> InterpResult<'tcx> {
213-
let mut bytes = vec![0; len];
214213
if !communicate_allowed {
215214
// We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
216215
helpers::isolation_abort_error("`read` from stdin")?;
217216
}
218-
let result = Read::read(&mut &*self, &mut bytes);
219-
match result {
220-
Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
221-
Err(e) => ecx.set_last_error_and_return(e, dest),
222-
}
217+
218+
let result = ecx.read_from_host(&*self, len, ptr)?;
219+
finish.call(ecx, result)
223220
}
224221

225222
fn is_tty(&self, communicate_allowed: bool) -> bool {
@@ -405,28 +402,28 @@ impl FdTable {
405402

406403
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
407404
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
408-
/// Helper to implement `FileDescription::read`:
409-
/// This is only used when `read` is successful.
410-
/// `actual_read_size` should be the return value of some underlying `read` call that used
411-
/// `bytes` as its output buffer.
412-
/// The length of `bytes` must not exceed either the host's or the target's `isize`.
413-
/// `bytes` is written to `buf` and the size is written to `dest`.
414-
fn return_read_success(
405+
/// Read data from a host `Read` type, store the result into machine memory,
406+
/// and return whether that worked.
407+
fn read_from_host(
415408
&mut self,
416-
buf: Pointer,
417-
bytes: &[u8],
418-
actual_read_size: usize,
419-
dest: &MPlaceTy<'tcx>,
420-
) -> InterpResult<'tcx> {
409+
mut file: impl io::Read,
410+
len: usize,
411+
ptr: Pointer,
412+
) -> InterpResult<'tcx, Result<usize, IoError>> {
421413
let this = self.eval_context_mut();
422-
// If reading to `bytes` did not fail, we write those bytes to the buffer.
423-
// Crucially, if fewer than `bytes.len()` bytes were read, only write
424-
// that much into the output buffer!
425-
this.write_bytes_ptr(buf, bytes[..actual_read_size].iter().copied())?;
426414

427-
// The actual read size is always less than what got originally requested so this cannot fail.
428-
this.write_int(u64::try_from(actual_read_size).unwrap(), dest)?;
429-
interp_ok(())
415+
let mut bytes = vec![0; len];
416+
let result = file.read(&mut bytes);
417+
match result {
418+
Ok(read_size) => {
419+
// If reading to `bytes` did not fail, we write those bytes to the buffer.
420+
// Crucially, if fewer than `bytes.len()` bytes were read, only write
421+
// that much into the output buffer!
422+
this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
423+
interp_ok(Ok(read_size))
424+
}
425+
Err(e) => interp_ok(Err(IoError::HostError(e))),
426+
}
430427
}
431428

432429
/// Helper to implement `FileDescription::write`:

src/tools/miri/src/shims/unix/fd.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ pub trait UnixFileDescription: FileDescription {
3030
_offset: u64,
3131
_ptr: Pointer,
3232
_len: usize,
33-
_dest: &MPlaceTy<'tcx>,
3433
_ecx: &mut MiriInterpCx<'tcx>,
34+
_finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
3535
) -> InterpResult<'tcx> {
3636
throw_unsup_format!("cannot pread from {}", self.name());
3737
}
@@ -236,7 +236,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
236236
let count = usize::try_from(count).unwrap(); // now it fits in a `usize`
237237
let communicate = this.machine.communicate();
238238

239-
// We temporarily dup the FD to be able to retain mutable access to `this`.
239+
// Get the FD.
240240
let Some(fd) = this.machine.fds.get(fd_num) else {
241241
trace!("read: FD not found");
242242
return this.set_last_error_and_return(LibcError("EBADF"), dest);
@@ -247,13 +247,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
247247
// because it was a target's `usize`. Also we are sure that its smaller than
248248
// `usize::MAX` because it is bounded by the host's `isize`.
249249

250+
let finish = {
251+
let dest = dest.clone();
252+
callback!(
253+
@capture<'tcx> {
254+
count: usize,
255+
dest: MPlaceTy<'tcx>,
256+
}
257+
|this, result: Result<usize, IoError>| {
258+
match result {
259+
Ok(read_size) => {
260+
assert!(read_size <= count);
261+
// This must fit since `count` fits.
262+
this.write_int(u64::try_from(read_size).unwrap(), &dest)
263+
}
264+
Err(e) => {
265+
this.set_last_error_and_return(e, &dest)
266+
}
267+
}}
268+
)
269+
};
250270
match offset {
251-
None => fd.read(communicate, buf, count, dest, this)?,
271+
None => fd.read(communicate, buf, count, this, finish)?,
252272
Some(offset) => {
253273
let Ok(offset) = u64::try_from(offset) else {
254274
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
255275
};
256-
fd.as_unix().pread(communicate, offset, buf, count, dest, this)?
276+
fd.as_unix().pread(communicate, offset, buf, count, this, finish)?
257277
}
258278
};
259279
interp_ok(())

src/tools/miri/src/shims/unix/fs.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,13 @@ impl FileDescription for FileHandle {
3535
communicate_allowed: bool,
3636
ptr: Pointer,
3737
len: usize,
38-
dest: &MPlaceTy<'tcx>,
3938
ecx: &mut MiriInterpCx<'tcx>,
39+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
4040
) -> InterpResult<'tcx> {
4141
assert!(communicate_allowed, "isolation should have prevented even opening a file");
42-
let mut bytes = vec![0; len];
43-
let result = (&mut &self.file).read(&mut bytes);
44-
match result {
45-
Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
46-
Err(e) => ecx.set_last_error_and_return(e, dest),
47-
}
42+
43+
let result = ecx.read_from_host(&self.file, len, ptr)?;
44+
finish.call(ecx, result)
4845
}
4946

5047
fn write<'tcx>(
@@ -119,8 +116,8 @@ impl UnixFileDescription for FileHandle {
119116
offset: u64,
120117
ptr: Pointer,
121118
len: usize,
122-
dest: &MPlaceTy<'tcx>,
123119
ecx: &mut MiriInterpCx<'tcx>,
120+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
124121
) -> InterpResult<'tcx> {
125122
assert!(communicate_allowed, "isolation should have prevented even opening a file");
126123
let mut bytes = vec![0; len];
@@ -137,11 +134,17 @@ impl UnixFileDescription for FileHandle {
137134
.expect("failed to restore file position, this shouldn't be possible");
138135
res
139136
};
140-
let result = f();
141-
match result {
142-
Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
143-
Err(e) => ecx.set_last_error_and_return(e, dest),
144-
}
137+
let result = match f() {
138+
Ok(read_size) => {
139+
// If reading to `bytes` did not fail, we write those bytes to the buffer.
140+
// Crucially, if fewer than `bytes.len()` bytes were read, only write
141+
// that much into the output buffer!
142+
ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
143+
Ok(read_size)
144+
}
145+
Err(e) => Err(IoError::HostError(e)),
146+
};
147+
finish.call(ecx, result)
145148
}
146149

147150
fn pwrite<'tcx>(

src/tools/miri/src/shims/unix/linux_like/eventfd.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,20 @@ impl FileDescription for EventFd {
5151
_communicate_allowed: bool,
5252
ptr: Pointer,
5353
len: usize,
54-
dest: &MPlaceTy<'tcx>,
5554
ecx: &mut MiriInterpCx<'tcx>,
55+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
5656
) -> InterpResult<'tcx> {
5757
// We're treating the buffer as a `u64`.
5858
let ty = ecx.machine.layouts.u64;
5959
// Check the size of slice, and return error only if the size of the slice < 8.
6060
if len < ty.size.bytes_usize() {
61-
return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest);
61+
return finish.call(ecx, Err(ErrorKind::InvalidInput.into()));
6262
}
6363

6464
// Turn the pointer into a place at the right type.
6565
let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty);
6666

67-
eventfd_read(buf_place, dest, self, ecx)
67+
eventfd_read(buf_place, self, ecx, finish)
6868
}
6969

7070
/// A write call adds the 8-byte integer value supplied in
@@ -260,19 +260,18 @@ fn eventfd_write<'tcx>(
260260
/// else just return the current counter value to the caller and set the counter to 0.
261261
fn eventfd_read<'tcx>(
262262
buf_place: MPlaceTy<'tcx>,
263-
dest: &MPlaceTy<'tcx>,
264263
eventfd: FileDescriptionRef<EventFd>,
265264
ecx: &mut MiriInterpCx<'tcx>,
265+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
266266
) -> InterpResult<'tcx> {
267267
// Set counter to 0, get old value.
268268
let counter = eventfd.counter.replace(0);
269269

270270
// Block when counter == 0.
271271
if counter == 0 {
272272
if eventfd.is_nonblock {
273-
return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
273+
return finish.call(ecx, Err(ErrorKind::WouldBlock.into()));
274274
}
275-
let dest = dest.clone();
276275

277276
eventfd.blocked_read_tid.borrow_mut().push(ecx.active_thread());
278277

@@ -283,15 +282,15 @@ fn eventfd_read<'tcx>(
283282
callback!(
284283
@capture<'tcx> {
285284
buf_place: MPlaceTy<'tcx>,
286-
dest: MPlaceTy<'tcx>,
285+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
287286
weak_eventfd: WeakFileDescriptionRef<EventFd>,
288287
}
289288
|this, unblock: UnblockKind| {
290289
assert_eq!(unblock, UnblockKind::Ready);
291290
// When we get unblocked, try again. We know the ref is still valid,
292291
// otherwise there couldn't be a `write` that unblocks us.
293292
let eventfd_ref = weak_eventfd.upgrade().unwrap();
294-
eventfd_read(buf_place, &dest, eventfd_ref, this)
293+
eventfd_read(buf_place, eventfd_ref, this, finish)
295294
}
296295
),
297296
);
@@ -317,7 +316,7 @@ fn eventfd_read<'tcx>(
317316
ecx.check_and_update_readiness(eventfd)?;
318317

319318
// Tell userspace how many bytes we put into the buffer.
320-
return ecx.write_int(buf_place.layout.size.bytes(), dest);
319+
return finish.call(ecx, Ok(buf_place.layout.size.bytes_usize()));
321320
}
322321
interp_ok(())
323322
}

src/tools/miri/src/shims/unix/unnamed_socket.rs

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use std::cell::{Cell, OnceCell, RefCell};
66
use std::collections::VecDeque;
77
use std::io;
8-
use std::io::{ErrorKind, Read};
8+
use std::io::ErrorKind;
99

1010
use rustc_abi::Size;
1111

@@ -92,10 +92,10 @@ impl FileDescription for AnonSocket {
9292
_communicate_allowed: bool,
9393
ptr: Pointer,
9494
len: usize,
95-
dest: &MPlaceTy<'tcx>,
9695
ecx: &mut MiriInterpCx<'tcx>,
96+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
9797
) -> InterpResult<'tcx> {
98-
anonsocket_read(self, len, ptr, dest, ecx)
98+
anonsocket_read(self, ptr, len, ecx, finish)
9999
}
100100

101101
fn write<'tcx>(
@@ -205,14 +205,14 @@ fn anonsocket_write<'tcx>(
205205
/// Read from AnonSocket and return the number of bytes read.
206206
fn anonsocket_read<'tcx>(
207207
self_ref: FileDescriptionRef<AnonSocket>,
208-
len: usize,
209208
ptr: Pointer,
210-
dest: &MPlaceTy<'tcx>,
209+
len: usize,
211210
ecx: &mut MiriInterpCx<'tcx>,
211+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
212212
) -> InterpResult<'tcx> {
213213
// Always succeed on read size 0.
214214
if len == 0 {
215-
return ecx.return_read_success(ptr, &[], 0, dest);
215+
return finish.call(ecx, Ok(0));
216216
}
217217

218218
let Some(readbuf) = &self_ref.readbuf else {
@@ -225,43 +225,41 @@ fn anonsocket_read<'tcx>(
225225
if self_ref.peer_fd().upgrade().is_none() {
226226
// Socketpair with no peer and empty buffer.
227227
// 0 bytes successfully read indicates end-of-file.
228-
return ecx.return_read_success(ptr, &[], 0, dest);
228+
return finish.call(ecx, Ok(0));
229229
} else if self_ref.is_nonblock {
230230
// Non-blocking socketpair with writer and empty buffer.
231231
// https://linux.die.net/man/2/read
232232
// EAGAIN or EWOULDBLOCK can be returned for socket,
233233
// POSIX.1-2001 allows either error to be returned for this case.
234234
// Since there is no ErrorKind for EAGAIN, WouldBlock is used.
235-
return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
235+
return finish.call(ecx, Err(ErrorKind::WouldBlock.into()));
236236
} else {
237237
self_ref.blocked_read_tid.borrow_mut().push(ecx.active_thread());
238238
// Blocking socketpair with writer and empty buffer.
239239
// Block the current thread; only keep a weak ref for this.
240240
let weak_self_ref = FileDescriptionRef::downgrade(&self_ref);
241-
let dest = dest.clone();
242241
ecx.block_thread(
243242
BlockReason::UnnamedSocket,
244243
None,
245244
callback!(
246245
@capture<'tcx> {
247246
weak_self_ref: WeakFileDescriptionRef<AnonSocket>,
248-
len: usize,
249247
ptr: Pointer,
250-
dest: MPlaceTy<'tcx>,
248+
len: usize,
249+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
251250
}
252251
|this, unblock: UnblockKind| {
253252
assert_eq!(unblock, UnblockKind::Ready);
254253
// If we got unblocked, then our peer successfully upgraded its weak
255254
// ref to us. That means we can also upgrade our weak ref.
256255
let self_ref = weak_self_ref.upgrade().unwrap();
257-
anonsocket_read(self_ref, len, ptr, &dest, this)
256+
anonsocket_read(self_ref, ptr, len, this, finish)
258257
}
259258
),
260259
);
261260
}
262261
} else {
263262
// There's data to be read!
264-
let mut bytes = vec![0; len];
265263
let mut readbuf = readbuf.borrow_mut();
266264
// Synchronize with all previous writes to this buffer.
267265
// FIXME: this over-synchronizes; a more precise approach would be to
@@ -270,7 +268,7 @@ fn anonsocket_read<'tcx>(
270268

271269
// Do full read / partial read based on the space available.
272270
// Conveniently, `read` exists on `VecDeque` and has exactly the desired behavior.
273-
let actual_read_size = readbuf.buf.read(&mut bytes[..]).unwrap();
271+
let read_size = ecx.read_from_host(&mut readbuf.buf, len, ptr)?.unwrap();
274272

275273
// Need to drop before others can access the readbuf again.
276274
drop(readbuf);
@@ -293,7 +291,7 @@ fn anonsocket_read<'tcx>(
293291
ecx.check_and_update_readiness(peer_fd)?;
294292
};
295293

296-
return ecx.return_read_success(ptr, &bytes, actual_read_size, dest);
294+
return finish.call(ecx, Ok(read_size));
297295
}
298296
interp_ok(())
299297
}

0 commit comments

Comments
 (0)