Skip to content

Commit 0748566

Browse files
committed
non-deterministically truncate reads/writes
1 parent bb6eb71 commit 0748566

18 files changed

+329
-116
lines changed

src/shims/files.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
167167
throw_unsup_format!("cannot write to {}", self.name());
168168
}
169169

170+
/// Determines whether this FD non-deterministically has its reads and writes shortened.
171+
fn nondet_short_accesses(&self) -> bool {
172+
true
173+
}
174+
170175
/// Seeks to the given offset (which can be relative to the beginning, end, or current position).
171176
/// Returns the new position from the start of the stream.
172177
fn seek<'tcx>(

src/shims/unix/fd.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use std::io;
55
use std::io::ErrorKind;
66

7+
use rand::Rng;
78
use rustc_abi::Size;
89

910
use crate::helpers::check_min_vararg_count;
@@ -263,9 +264,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
263264
return this.set_last_error_and_return(LibcError("EBADF"), dest);
264265
};
265266

267+
// Non-deterministically decide to further reduce the count, simulating a partial read (but
268+
// never to 0, that has different behavior).
269+
let count =
270+
if fd.nondet_short_accesses() && count >= 2 && this.machine.rng.get_mut().random() {
271+
count / 2
272+
} else {
273+
count
274+
};
275+
266276
trace!("read: FD mapped to {fd:?}");
267277
// We want to read at most `count` bytes. We are sure that `count` is not negative
268-
// because it was a target's `usize`. Also we are sure that its smaller than
278+
// because it was a target's `usize`. Also we are sure that it's smaller than
269279
// `usize::MAX` because it is bounded by the host's `isize`.
270280

271281
let finish = {
@@ -328,6 +338,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
328338
return this.set_last_error_and_return(LibcError("EBADF"), dest);
329339
};
330340

341+
// Non-deterministically decide to further reduce the count, simulating a partial write (but
342+
// never to 0, that has different behavior).
343+
let count =
344+
if fd.nondet_short_accesses() && count >= 2 && this.machine.rng.get_mut().random() {
345+
count / 2
346+
} else {
347+
count
348+
};
349+
331350
let finish = {
332351
let dest = dest.clone();
333352
callback!(

src/shims/unix/linux_like/eventfd.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ impl FileDescription for EventFd {
3737
"event"
3838
}
3939

40+
fn nondet_short_accesses(&self) -> bool {
41+
// We always read and write exactly one `u64`.
42+
false
43+
}
44+
4045
fn close<'tcx>(
4146
self,
4247
_communicate_allowed: bool,

src/shims/windows/fs.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
462462
};
463463
let io_status_info = this.project_field_named(&io_status_block, "Information")?;
464464

465+
// It seems like short writes are not a thing on Windows, so we don't truncate `count` here.
466+
// FIXME: if we are on a Unix host, short host writes are still visible to the program!
467+
465468
let finish = {
466469
let io_status = io_status.clone();
467470
let io_status_info = io_status_info.clone();
@@ -491,7 +494,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
491494
}}
492495
)
493496
};
494-
495497
desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
496498

497499
// Return status is written to `dest` and `io_status_block` on callback completion.
@@ -556,6 +558,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
556558
};
557559
let io_status_info = this.project_field_named(&io_status_block, "Information")?;
558560

561+
let fd = match handle {
562+
Handle::File(fd) => fd,
563+
_ => this.invalid_handle("NtWriteFile")?,
564+
};
565+
566+
let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
567+
568+
// It seems like short reads are not a thing on Windows, so we don't truncate `count` here.
569+
// FIXME: if we are on a Unix host, short host reads are still visible to the program!
570+
559571
let finish = {
560572
let io_status = io_status.clone();
561573
let io_status_info = io_status_info.clone();
@@ -585,14 +597,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
585597
}}
586598
)
587599
};
588-
589-
let fd = match handle {
590-
Handle::File(fd) => fd,
591-
_ => this.invalid_handle("NtWriteFile")?,
592-
};
593-
594-
let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
595-
596600
desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
597601

598602
// See NtWriteFile for commentary on this

tests/fail-dep/libc/libc-epoll-data-race.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ use std::convert::TryInto;
1010
use std::thread;
1111
use std::thread::spawn;
1212

13+
#[path = "../../utils/libc.rs"]
14+
mod libc_utils;
15+
1316
#[track_caller]
1417
fn check_epoll_wait<const N: usize>(epfd: i32, expected_notifications: &[(u32, u64)]) {
1518
let epoll_event = libc::epoll_event { events: 0, u64: 0 };
@@ -69,12 +72,12 @@ fn main() {
6972
unsafe { VAL_ONE = 41 };
7073

7174
let data = "abcde".as_bytes().as_ptr();
72-
let res = unsafe { libc::write(fds_a[0], data as *const libc::c_void, 5) };
75+
let res = unsafe { libc_utils::write_all(fds_a[0], data as *const libc::c_void, 5) };
7376
assert_eq!(res, 5);
7477

7578
unsafe { VAL_TWO = 51 };
7679

77-
let res = unsafe { libc::write(fds_b[0], data as *const libc::c_void, 5) };
80+
let res = unsafe { libc_utils::write_all(fds_b[0], data as *const libc::c_void, 5) };
7881
assert_eq!(res, 5);
7982
});
8083
thread::yield_now();

tests/fail-dep/libc/socketpair_block_read_twice.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
//@ignore-target: windows # No libc socketpair on Windows
22
//~^ERROR: deadlocked
3-
//~^^ERROR: deadlocked
43
// test_race depends on a deterministic schedule.
54
//@compile-flags: -Zmiri-deterministic-concurrency
65
//@error-in-other-file: deadlock
6+
//@error-in-other-file: deadlock
7+
//@error-in-other-file: deadlock
8+
//@require-annotations-for-level: error
79

810
use std::thread;
911

12+
#[path = "../../utils/libc.rs"]
13+
mod libc_utils;
14+
1015
// Test the behaviour of a thread being blocked on read, get unblocked, then blocked again.
1116

1217
// The expected execution is
@@ -23,22 +28,27 @@ fn main() {
2328
let thread1 = thread::spawn(move || {
2429
// Let this thread block on read.
2530
let mut buf: [u8; 3] = [0; 3];
26-
let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
31+
let res = unsafe {
32+
libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
33+
//~^NOTE: inside closure
34+
};
2735
assert_eq!(res, 3);
2836
assert_eq!(&buf, "abc".as_bytes());
2937
});
3038
let thread2 = thread::spawn(move || {
3139
// Let this thread block on read.
3240
let mut buf: [u8; 3] = [0; 3];
33-
let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
34-
//~^ERROR: deadlocked
41+
let res = unsafe {
42+
libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
43+
//~^NOTE: inside closure
44+
};
3545
assert_eq!(res, 3);
3646
assert_eq!(&buf, "abc".as_bytes());
3747
});
3848
let thread3 = thread::spawn(move || {
3949
// Unblock thread1 by writing something.
4050
let data = "abc".as_bytes().as_ptr();
41-
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
51+
let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 3) };
4252
assert_eq!(res, 3);
4353
});
4454
thread1.join().unwrap();

tests/fail-dep/libc/socketpair_block_read_twice.stderr

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
error: the evaluated program deadlocked
2+
--> tests/fail-dep/libc/../../utils/libc.rs:LL:CC
3+
|
4+
LL | let res = libc::read(fd, buf.add(read_so_far), count - read_so_far);
5+
| ^ this thread got stuck here
6+
|
7+
= note: BACKTRACE on thread `unnamed-ID`:
8+
= note: inside `libc_utils::read_all` at tests/fail-dep/libc/../../utils/libc.rs:LL:CC
9+
note: inside closure
10+
--> tests/fail-dep/libc/socketpair_block_read_twice.rs:LL:CC
11+
|
12+
LL | libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
115
error: the evaluated program deadlocked
216
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
317
|
@@ -11,23 +25,22 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) };
1125
note: inside `main`
1226
--> tests/fail-dep/libc/socketpair_block_read_twice.rs:LL:CC
1327
|
14-
LL | thread2.join().unwrap();
28+
LL | thread1.join().unwrap();
1529
| ^^^^^^^^^^^^^^
1630

1731
error: the evaluated program deadlocked
32+
--> tests/fail-dep/libc/../../utils/libc.rs:LL:CC
33+
|
34+
LL | let res = libc::read(fd, buf.add(read_so_far), count - read_so_far);
35+
| ^ this thread got stuck here
1836
|
19-
= note: this thread got stuck here
20-
= note: (no span available)
2137
= note: BACKTRACE on thread `unnamed-ID`:
22-
23-
error: the evaluated program deadlocked
38+
= note: inside `libc_utils::read_all` at tests/fail-dep/libc/../../utils/libc.rs:LL:CC
39+
note: inside closure
2440
--> tests/fail-dep/libc/socketpair_block_read_twice.rs:LL:CC
2541
|
26-
LL | let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
27-
| ^ this thread got stuck here
28-
|
29-
= note: BACKTRACE on thread `unnamed-ID`:
30-
= note: inside closure at tests/fail-dep/libc/socketpair_block_read_twice.rs:LL:CC
42+
LL | libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
43+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3144

3245
error: the evaluated program deadlocked
3346
|

tests/fail-dep/libc/socketpair_block_write_twice.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
// test_race depends on a deterministic schedule.
55
//@compile-flags: -Zmiri-deterministic-concurrency
66
//@error-in-other-file: deadlock
7+
//@error-in-other-file: deadlock
8+
//@require-annotations-for-level: error
79

810
use std::thread;
911

12+
#[path = "../../utils/libc.rs"]
13+
mod libc_utils;
14+
1015
// Test the behaviour of a thread being blocked on write, get unblocked, then blocked again.
1116

1217
// The expected execution is
@@ -21,25 +26,28 @@ fn main() {
2126
assert_eq!(res, 0);
2227
let arr1: [u8; 212992] = [1; 212992];
2328
// Exhaust the space in the buffer so the subsequent write will block.
24-
let res = unsafe { libc::write(fds[0], arr1.as_ptr() as *const libc::c_void, 212992) };
29+
let res =
30+
unsafe { libc_utils::write_all(fds[0], arr1.as_ptr() as *const libc::c_void, 212992) };
2531
assert_eq!(res, 212992);
2632
let thread1 = thread::spawn(move || {
2733
let data = "abc".as_bytes().as_ptr();
2834
// The write below will be blocked because the buffer is already full.
29-
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
35+
let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 3) };
3036
assert_eq!(res, 3);
3137
});
3238
let thread2 = thread::spawn(move || {
3339
let data = "abc".as_bytes().as_ptr();
3440
// The write below will be blocked because the buffer is already full.
35-
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
36-
//~^ERROR: deadlocked
41+
let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 3) };
42+
//~^NOTE: inside closure
3743
assert_eq!(res, 3);
3844
});
3945
let thread3 = thread::spawn(move || {
4046
// Unblock thread1 by freeing up some space.
4147
let mut buf: [u8; 3] = [0; 3];
42-
let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
48+
let res = unsafe {
49+
libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
50+
};
4351
assert_eq!(res, 3);
4452
assert_eq!(buf, [1, 1, 1]);
4553
});

tests/fail-dep/libc/socketpair_block_write_twice.stderr

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,18 @@ error: the evaluated program deadlocked
2121
= note: BACKTRACE on thread `unnamed-ID`:
2222

2323
error: the evaluated program deadlocked
24-
--> tests/fail-dep/libc/socketpair_block_write_twice.rs:LL:CC
24+
--> tests/fail-dep/libc/../../utils/libc.rs:LL:CC
2525
|
26-
LL | let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
27-
| ^ this thread got stuck here
26+
LL | let res = libc::write(fd, buf.add(written_so_far), count - written_so_far);
27+
| ^ this thread got stuck here
2828
|
2929
= note: BACKTRACE on thread `unnamed-ID`:
30-
= note: inside closure at tests/fail-dep/libc/socketpair_block_write_twice.rs:LL:CC
30+
= note: inside `libc_utils::write_all` at tests/fail-dep/libc/../../utils/libc.rs:LL:CC
31+
note: inside closure
32+
--> tests/fail-dep/libc/socketpair_block_write_twice.rs:LL:CC
33+
|
34+
LL | let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 3) };
35+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3136

3237
error: the evaluated program deadlocked
3338
|

tests/pass-dep/libc/libc-epoll-blocking.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use std::convert::TryInto;
66
use std::thread;
77
use std::thread::spawn;
88

9+
#[path = "../../utils/libc.rs"]
10+
mod libc_utils;
11+
912
// This is a set of testcases for blocking epoll.
1013

1114
fn main() {
@@ -97,7 +100,7 @@ fn test_epoll_block_then_unblock() {
97100
let thread1 = spawn(move || {
98101
thread::yield_now();
99102
let data = "abcde".as_bytes().as_ptr();
100-
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
103+
let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
101104
assert_eq!(res, 5);
102105
});
103106
check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], 10);
@@ -130,7 +133,7 @@ fn test_notification_after_timeout() {
130133

131134
// Trigger epoll notification after timeout.
132135
let data = "abcde".as_bytes().as_ptr();
133-
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
136+
let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
134137
assert_eq!(res, 5);
135138

136139
// Check the result of the notification.

0 commit comments

Comments
 (0)