Skip to content

Commit 83f1b38

Browse files
committed
Auto merge of #3809 - RalfJung:fd-refcell, r=oli-obk
FD: remove big surrounding RefCell, simplify socketpair A while ago, I added the big implicit RefCell for all file descriptions since it avoided interior mutability in `eventfd`. However, this requires us to hold the RefCell "lock" around the entire invocation of the `read`/`write` methods on an FD, which is not great. For instance, if an FD wants to update epoll notifications from inside its `read`/`write`, it is very crucial that the notification check does not end up accessing the FD itself. Such cycles, however, occur naturally: - eventfd wants to update notifications for itself - socketfd wants to update notifications on its "peer", which will in turn check *its* peer to see whether that buffer is empty -- and my peer's peer is myself. This then also lets us simplify socketpair, which currently holds a weak reference to its peer *and* a weak reference to the peer's buffer -- that was previously needed precisely to avoid this issue.
2 parents 1a51dd9 + 883e477 commit 83f1b38

File tree

6 files changed

+294
-399
lines changed

6 files changed

+294
-399
lines changed

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

Lines changed: 51 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
//! standard file descriptors (stdin/stdout/stderr).
33
44
use std::any::Any;
5-
use std::cell::{Ref, RefCell, RefMut};
65
use std::collections::BTreeMap;
76
use std::io::{self, ErrorKind, IsTerminal, Read, SeekFrom, Write};
7+
use std::ops::Deref;
88
use std::rc::Rc;
99
use std::rc::Weak;
1010

@@ -27,9 +27,9 @@ pub trait FileDescription: std::fmt::Debug + Any {
2727

2828
/// Reads as much as possible into the given buffer, and returns the number of bytes read.
2929
fn read<'tcx>(
30-
&mut self,
30+
&self,
31+
_self_ref: &FileDescriptionRef,
3132
_communicate_allowed: bool,
32-
_fd_id: FdId,
3333
_bytes: &mut [u8],
3434
_ecx: &mut MiriInterpCx<'tcx>,
3535
) -> InterpResult<'tcx, io::Result<usize>> {
@@ -38,9 +38,9 @@ pub trait FileDescription: std::fmt::Debug + Any {
3838

3939
/// Writes as much as possible from the given buffer, and returns the number of bytes written.
4040
fn write<'tcx>(
41-
&mut self,
41+
&self,
42+
_self_ref: &FileDescriptionRef,
4243
_communicate_allowed: bool,
43-
_fd_id: FdId,
4444
_bytes: &[u8],
4545
_ecx: &mut MiriInterpCx<'tcx>,
4646
) -> InterpResult<'tcx, io::Result<usize>> {
@@ -50,7 +50,7 @@ pub trait FileDescription: std::fmt::Debug + Any {
5050
/// Reads as much as possible into the given buffer from a given offset,
5151
/// and returns the number of bytes read.
5252
fn pread<'tcx>(
53-
&mut self,
53+
&self,
5454
_communicate_allowed: bool,
5555
_bytes: &mut [u8],
5656
_offset: u64,
@@ -62,7 +62,7 @@ pub trait FileDescription: std::fmt::Debug + Any {
6262
/// Writes as much as possible from the given buffer starting at a given offset,
6363
/// and returns the number of bytes written.
6464
fn pwrite<'tcx>(
65-
&mut self,
65+
&self,
6666
_communicate_allowed: bool,
6767
_bytes: &[u8],
6868
_offset: u64,
@@ -74,7 +74,7 @@ pub trait FileDescription: std::fmt::Debug + Any {
7474
/// Seeks to the given offset (which can be relative to the beginning, end, or current position).
7575
/// Returns the new position from the start of the stream.
7676
fn seek<'tcx>(
77-
&mut self,
77+
&self,
7878
_communicate_allowed: bool,
7979
_offset: SeekFrom,
8080
) -> InterpResult<'tcx, io::Result<u64>> {
@@ -111,14 +111,9 @@ pub trait FileDescription: std::fmt::Debug + Any {
111111

112112
impl dyn FileDescription {
113113
#[inline(always)]
114-
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
114+
pub fn downcast<T: Any>(&self) -> Option<&T> {
115115
(self as &dyn Any).downcast_ref()
116116
}
117-
118-
#[inline(always)]
119-
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
120-
(self as &mut dyn Any).downcast_mut()
121-
}
122117
}
123118

124119
impl FileDescription for io::Stdin {
@@ -127,17 +122,17 @@ impl FileDescription for io::Stdin {
127122
}
128123

129124
fn read<'tcx>(
130-
&mut self,
125+
&self,
126+
_self_ref: &FileDescriptionRef,
131127
communicate_allowed: bool,
132-
_fd_id: FdId,
133128
bytes: &mut [u8],
134129
_ecx: &mut MiriInterpCx<'tcx>,
135130
) -> InterpResult<'tcx, io::Result<usize>> {
136131
if !communicate_allowed {
137132
// We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
138133
helpers::isolation_abort_error("`read` from stdin")?;
139134
}
140-
Ok(Read::read(self, bytes))
135+
Ok(Read::read(&mut { self }, bytes))
141136
}
142137

143138
fn is_tty(&self, communicate_allowed: bool) -> bool {
@@ -151,14 +146,14 @@ impl FileDescription for io::Stdout {
151146
}
152147

153148
fn write<'tcx>(
154-
&mut self,
149+
&self,
150+
_self_ref: &FileDescriptionRef,
155151
_communicate_allowed: bool,
156-
_fd_id: FdId,
157152
bytes: &[u8],
158153
_ecx: &mut MiriInterpCx<'tcx>,
159154
) -> InterpResult<'tcx, io::Result<usize>> {
160155
// We allow writing to stderr even with isolation enabled.
161-
let result = Write::write(self, bytes);
156+
let result = Write::write(&mut { self }, bytes);
162157
// Stdout is buffered, flush to make sure it appears on the
163158
// screen. This is the write() syscall of the interpreted
164159
// program, we want it to correspond to a write() syscall on
@@ -180,9 +175,9 @@ impl FileDescription for io::Stderr {
180175
}
181176

182177
fn write<'tcx>(
183-
&mut self,
178+
&self,
179+
_self_ref: &FileDescriptionRef,
184180
_communicate_allowed: bool,
185-
_fd_id: FdId,
186181
bytes: &[u8],
187182
_ecx: &mut MiriInterpCx<'tcx>,
188183
) -> InterpResult<'tcx, io::Result<usize>> {
@@ -206,9 +201,9 @@ impl FileDescription for NullOutput {
206201
}
207202

208203
fn write<'tcx>(
209-
&mut self,
204+
&self,
205+
_self_ref: &FileDescriptionRef,
210206
_communicate_allowed: bool,
211-
_fd_id: FdId,
212207
bytes: &[u8],
213208
_ecx: &mut MiriInterpCx<'tcx>,
214209
) -> InterpResult<'tcx, io::Result<usize>> {
@@ -221,26 +216,23 @@ impl FileDescription for NullOutput {
221216
#[derive(Clone, Debug)]
222217
pub struct FileDescWithId<T: FileDescription + ?Sized> {
223218
id: FdId,
224-
file_description: RefCell<Box<T>>,
219+
file_description: Box<T>,
225220
}
226221

227222
#[derive(Clone, Debug)]
228223
pub struct FileDescriptionRef(Rc<FileDescWithId<dyn FileDescription>>);
229224

230-
impl FileDescriptionRef {
231-
fn new(fd: impl FileDescription, id: FdId) -> Self {
232-
FileDescriptionRef(Rc::new(FileDescWithId {
233-
id,
234-
file_description: RefCell::new(Box::new(fd)),
235-
}))
236-
}
225+
impl Deref for FileDescriptionRef {
226+
type Target = dyn FileDescription;
237227

238-
pub fn borrow(&self) -> Ref<'_, dyn FileDescription> {
239-
Ref::map(self.0.file_description.borrow(), |fd| fd.as_ref())
228+
fn deref(&self) -> &Self::Target {
229+
&*self.0.file_description
240230
}
231+
}
241232

242-
pub fn borrow_mut(&self) -> RefMut<'_, dyn FileDescription> {
243-
RefMut::map(self.0.file_description.borrow_mut(), |fd| fd.as_mut())
233+
impl FileDescriptionRef {
234+
fn new(fd: impl FileDescription, id: FdId) -> Self {
235+
FileDescriptionRef(Rc::new(FileDescWithId { id, file_description: Box::new(fd) }))
244236
}
245237

246238
pub fn close<'tcx>(
@@ -256,7 +248,7 @@ impl FileDescriptionRef {
256248
// Remove entry from the global epoll_event_interest table.
257249
ecx.machine.epoll_interests.remove(id);
258250

259-
RefCell::into_inner(fd.file_description).close(communicate_allowed, ecx)
251+
fd.file_description.close(communicate_allowed, ecx)
260252
}
261253
None => Ok(Ok(())),
262254
}
@@ -269,16 +261,6 @@ impl FileDescriptionRef {
269261
pub fn get_id(&self) -> FdId {
270262
self.0.id
271263
}
272-
273-
/// Function used to retrieve the readiness events of a file description and insert
274-
/// an `EpollEventInstance` into the ready list if the file description is ready.
275-
pub(crate) fn check_and_update_readiness<'tcx>(
276-
&self,
277-
ecx: &mut InterpCx<'tcx, MiriMachine<'tcx>>,
278-
) -> InterpResult<'tcx, ()> {
279-
use crate::shims::unix::linux::epoll::EvalContextExt;
280-
ecx.check_and_update_readiness(self.get_id(), || self.borrow_mut().get_epoll_ready_events())
281-
}
282264
}
283265

284266
/// Holds a weak reference to the actual file description.
@@ -334,11 +316,20 @@ impl FdTable {
334316
fds
335317
}
336318

337-
/// Insert a new file description to the FdTable.
338-
pub fn insert_new(&mut self, fd: impl FileDescription) -> i32 {
319+
pub fn new_ref(&mut self, fd: impl FileDescription) -> FileDescriptionRef {
339320
let file_handle = FileDescriptionRef::new(fd, self.next_file_description_id);
340321
self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
341-
self.insert_ref_with_min_fd(file_handle, 0)
322+
file_handle
323+
}
324+
325+
/// Insert a new file description to the FdTable.
326+
pub fn insert_new(&mut self, fd: impl FileDescription) -> i32 {
327+
let fd_ref = self.new_ref(fd);
328+
self.insert(fd_ref)
329+
}
330+
331+
pub fn insert(&mut self, fd_ref: FileDescriptionRef) -> i32 {
332+
self.insert_ref_with_min_fd(fd_ref, 0)
342333
}
343334

344335
/// Insert a file description, giving it a file descriptor that is at least `min_fd`.
@@ -368,17 +359,7 @@ impl FdTable {
368359
new_fd
369360
}
370361

371-
pub fn get(&self, fd: i32) -> Option<Ref<'_, dyn FileDescription>> {
372-
let fd = self.fds.get(&fd)?;
373-
Some(fd.borrow())
374-
}
375-
376-
pub fn get_mut(&self, fd: i32) -> Option<RefMut<'_, dyn FileDescription>> {
377-
let fd = self.fds.get(&fd)?;
378-
Some(fd.borrow_mut())
379-
}
380-
381-
pub fn get_ref(&self, fd: i32) -> Option<FileDescriptionRef> {
362+
pub fn get(&self, fd: i32) -> Option<FileDescriptionRef> {
382363
let fd = self.fds.get(&fd)?;
383364
Some(fd.clone())
384365
}
@@ -397,7 +378,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
397378
fn dup(&mut self, old_fd: i32) -> InterpResult<'tcx, Scalar> {
398379
let this = self.eval_context_mut();
399380

400-
let Some(dup_fd) = this.machine.fds.get_ref(old_fd) else {
381+
let Some(dup_fd) = this.machine.fds.get(old_fd) else {
401382
return Ok(Scalar::from_i32(this.fd_not_found()?));
402383
};
403384
Ok(Scalar::from_i32(this.machine.fds.insert_ref_with_min_fd(dup_fd, 0)))
@@ -406,7 +387,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
406387
fn dup2(&mut self, old_fd: i32, new_fd: i32) -> InterpResult<'tcx, Scalar> {
407388
let this = self.eval_context_mut();
408389

409-
let Some(dup_fd) = this.machine.fds.get_ref(old_fd) else {
390+
let Some(dup_fd) = this.machine.fds.get(old_fd) else {
410391
return Ok(Scalar::from_i32(this.fd_not_found()?));
411392
};
412393
if new_fd != old_fd {
@@ -492,7 +473,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
492473
}
493474
let start = this.read_scalar(&args[2])?.to_i32()?;
494475

495-
match this.machine.fds.get_ref(fd) {
476+
match this.machine.fds.get(fd) {
496477
Some(dup_fd) =>
497478
Ok(Scalar::from_i32(this.machine.fds.insert_ref_with_min_fd(dup_fd, start))),
498479
None => Ok(Scalar::from_i32(this.fd_not_found()?)),
@@ -565,7 +546,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
565546
let communicate = this.machine.communicate();
566547

567548
// We temporarily dup the FD to be able to retain mutable access to `this`.
568-
let Some(fd) = this.machine.fds.get_ref(fd) else {
549+
let Some(fd) = this.machine.fds.get(fd) else {
569550
trace!("read: FD not found");
570551
return Ok(Scalar::from_target_isize(this.fd_not_found()?, this));
571552
};
@@ -576,14 +557,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
576557
// `usize::MAX` because it is bounded by the host's `isize`.
577558
let mut bytes = vec![0; usize::try_from(count).unwrap()];
578559
let result = match offset {
579-
None => fd.borrow_mut().read(communicate, fd.get_id(), &mut bytes, this),
560+
None => fd.read(&fd, communicate, &mut bytes, this),
580561
Some(offset) => {
581562
let Ok(offset) = u64::try_from(offset) else {
582563
let einval = this.eval_libc("EINVAL");
583564
this.set_last_error(einval)?;
584565
return Ok(Scalar::from_target_isize(-1, this));
585566
};
586-
fd.borrow_mut().pread(communicate, &mut bytes, offset, this)
567+
fd.pread(communicate, &mut bytes, offset, this)
587568
}
588569
};
589570

@@ -629,19 +610,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
629610

630611
let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?.to_owned();
631612
// We temporarily dup the FD to be able to retain mutable access to `this`.
632-
let Some(fd) = this.machine.fds.get_ref(fd) else {
613+
let Some(fd) = this.machine.fds.get(fd) else {
633614
return Ok(Scalar::from_target_isize(this.fd_not_found()?, this));
634615
};
635616

636617
let result = match offset {
637-
None => fd.borrow_mut().write(communicate, fd.get_id(), &bytes, this),
618+
None => fd.write(&fd, communicate, &bytes, this),
638619
Some(offset) => {
639620
let Ok(offset) = u64::try_from(offset) else {
640621
let einval = this.eval_libc("EINVAL");
641622
this.set_last_error(einval)?;
642623
return Ok(Scalar::from_target_isize(-1, this));
643624
};
644-
fd.borrow_mut().pwrite(communicate, &bytes, offset, this)
625+
fd.pwrite(communicate, &bytes, offset, this)
645626
}
646627
};
647628

0 commit comments

Comments
 (0)