Auto merge of #125010 - matthiaskrgr:rollup-270pck3, r=matthiaskrgr

Rollup of 5 pull requests

Successful merges:

 - #124928 (Stabilize `byte_slice_trim_ascii` for `&[u8]`/`&str`)
 - #124954 (Document proper usage of `fmt::Error` and `fmt()`'s `Result`.)
 - #124969 (check if `x test tests` missing any test directory)
 - #124978 (Handle Deref expressions in invalid_reference_casting)
 - #125005 (Miri subtree update)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2024-05-11 12:46:54 +00:00
commit 686bfc4c42
165 changed files with 1271 additions and 1209 deletions

View file

@ -202,8 +202,10 @@ fn is_cast_to_bigger_memory_layout<'tcx>(
// if the current expr looks like this `&mut expr[index]` then just looking
// at `expr[index]` won't give us the underlying allocation, so we just skip it
// the same logic applies field access like `&mut expr.field`
if let ExprKind::Index(..) | ExprKind::Field(..) = e_alloc.kind {
// the same logic applies field access `&mut expr.field` and reborrows `&mut *expr`.
if let ExprKind::Index(..) | ExprKind::Field(..) | ExprKind::Unary(UnOp::Deref, ..) =
e_alloc.kind
{
return None;
}

View file

@ -403,7 +403,7 @@
//! is, a formatting implementation must and may only return an error if the
//! passed-in [`Formatter`] returns an error. This is because, contrary to what
//! the function signature might suggest, string formatting is an infallible
//! operation. This function only returns a result because writing to the
//! operation. This function only returns a [`Result`] because writing to the
//! underlying stream might fail and it must provide a way to propagate the fact
//! that an error has occurred back up the stack.
//!

View file

@ -0,0 +1,8 @@
Formats the value using the given formatter.
# Errors
This function should return [`Err`] if, and only if, the provided [`Formatter`] returns [`Err`].
String formatting is considered an infallible operation; this function only
returns a [`Result`] because writing to the underlying stream might fail and it must
provide a way to propagate the fact that an error has occurred back up the stack.

View file

@ -72,14 +72,24 @@ pub enum Alignment {
/// The error type which is returned from formatting a message into a stream.
///
/// This type does not support transmission of an error other than that an error
/// occurred. Any extra information must be arranged to be transmitted through
/// some other means.
/// occurred. This is because, despite the existence of this error,
/// string formatting is considered an infallible operation.
/// `fmt()` implementors should not return this `Error` unless they received it from their
/// [`Formatter`]. The only time your code should create a new instance of this
/// error is when implementing `fmt::Write`, in order to cancel the formatting operation when
/// writing to the underlying stream fails.
///
/// An important thing to remember is that the type `fmt::Error` should not be
/// Any extra information must be arranged to be transmitted through some other means,
/// such as storing it in a field to be consulted after the formatting operation has been
/// cancelled. (For example, this is how [`std::io::Write::write_fmt()`] propagates IO errors
/// during writing.)
///
/// This type, `fmt::Error`, should not be
/// confused with [`std::io::Error`] or [`std::error::Error`], which you may also
/// have in scope.
///
/// [`std::io::Error`]: ../../std/io/struct.Error.html
/// [`std::io::Write::write_fmt()`]: ../../std/io/trait.Write.html#method.write_fmt
/// [`std::error::Error`]: ../../std/error/trait.Error.html
///
/// # Examples
@ -118,8 +128,10 @@ pub trait Write {
/// This function will return an instance of [`std::fmt::Error`][Error] on error.
///
/// The purpose of that error is to abort the formatting operation when the underlying
/// destination encounters some error preventing it from accepting more text; it should
/// generally be propagated rather than handled, at least when implementing formatting traits.
/// destination encounters some error preventing it from accepting more text;
/// in particular, it does not communicate any information about *what* error occurred.
/// It should generally be propagated rather than handled, at least when implementing
/// formatting traits.
///
/// # Examples
///
@ -586,7 +598,7 @@ fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
#[rustc_diagnostic_item = "Debug"]
#[rustc_trivial_field_reads]
pub trait Debug {
/// Formats the value using the given formatter.
#[doc = include_str!("fmt_trait_method_doc.md")]
///
/// # Examples
///
@ -703,7 +715,7 @@ pub(crate) mod macros {
#[rustc_diagnostic_item = "Display"]
#[stable(feature = "rust1", since = "1.0.0")]
pub trait Display {
/// Formats the value using the given formatter.
#[doc = include_str!("fmt_trait_method_doc.md")]
///
/// # Examples
///
@ -777,7 +789,7 @@ pub trait Display {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub trait Octal {
/// Formats the value using the given formatter.
#[doc = include_str!("fmt_trait_method_doc.md")]
#[stable(feature = "rust1", since = "1.0.0")]
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
}
@ -836,7 +848,7 @@ pub trait Octal {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub trait Binary {
/// Formats the value using the given formatter.
#[doc = include_str!("fmt_trait_method_doc.md")]
#[stable(feature = "rust1", since = "1.0.0")]
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
}
@ -891,7 +903,7 @@ pub trait Binary {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub trait LowerHex {
/// Formats the value using the given formatter.
#[doc = include_str!("fmt_trait_method_doc.md")]
#[stable(feature = "rust1", since = "1.0.0")]
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
}
@ -946,7 +958,7 @@ pub trait LowerHex {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub trait UpperHex {
/// Formats the value using the given formatter.
#[doc = include_str!("fmt_trait_method_doc.md")]
#[stable(feature = "rust1", since = "1.0.0")]
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
}
@ -997,7 +1009,7 @@ pub trait UpperHex {
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "Pointer"]
pub trait Pointer {
/// Formats the value using the given formatter.
#[doc = include_str!("fmt_trait_method_doc.md")]
#[stable(feature = "rust1", since = "1.0.0")]
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
}
@ -1048,7 +1060,7 @@ pub trait Pointer {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub trait LowerExp {
/// Formats the value using the given formatter.
#[doc = include_str!("fmt_trait_method_doc.md")]
#[stable(feature = "rust1", since = "1.0.0")]
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
}
@ -1099,7 +1111,7 @@ pub trait LowerExp {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub trait UpperExp {
/// Formats the value using the given formatter.
#[doc = include_str!("fmt_trait_method_doc.md")]
#[stable(feature = "rust1", since = "1.0.0")]
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
}

View file

@ -114,18 +114,17 @@ pub fn escape_ascii(&self) -> EscapeAscii<'_> {
/// Returns a byte slice with leading ASCII whitespace bytes removed.
///
/// 'Whitespace' refers to the definition used by
/// `u8::is_ascii_whitespace`.
/// [`u8::is_ascii_whitespace`].
///
/// # Examples
///
/// ```
/// #![feature(byte_slice_trim_ascii)]
///
/// assert_eq!(b" \t hello world\n".trim_ascii_start(), b"hello world\n");
/// assert_eq!(b" ".trim_ascii_start(), b"");
/// assert_eq!(b"".trim_ascii_start(), b"");
/// ```
#[unstable(feature = "byte_slice_trim_ascii", issue = "94035")]
#[stable(feature = "byte_slice_trim_ascii", since = "CURRENT_RUSTC_VERSION")]
#[rustc_const_stable(feature = "byte_slice_trim_ascii", since = "CURRENT_RUSTC_VERSION")]
#[inline]
pub const fn trim_ascii_start(&self) -> &[u8] {
let mut bytes = self;
@ -144,18 +143,17 @@ pub const fn trim_ascii_start(&self) -> &[u8] {
/// Returns a byte slice with trailing ASCII whitespace bytes removed.
///
/// 'Whitespace' refers to the definition used by
/// `u8::is_ascii_whitespace`.
/// [`u8::is_ascii_whitespace`].
///
/// # Examples
///
/// ```
/// #![feature(byte_slice_trim_ascii)]
///
/// assert_eq!(b"\r hello world\n ".trim_ascii_end(), b"\r hello world");
/// assert_eq!(b" ".trim_ascii_end(), b"");
/// assert_eq!(b"".trim_ascii_end(), b"");
/// ```
#[unstable(feature = "byte_slice_trim_ascii", issue = "94035")]
#[stable(feature = "byte_slice_trim_ascii", since = "CURRENT_RUSTC_VERSION")]
#[rustc_const_stable(feature = "byte_slice_trim_ascii", since = "CURRENT_RUSTC_VERSION")]
#[inline]
pub const fn trim_ascii_end(&self) -> &[u8] {
let mut bytes = self;
@ -175,18 +173,17 @@ pub const fn trim_ascii_end(&self) -> &[u8] {
/// removed.
///
/// 'Whitespace' refers to the definition used by
/// `u8::is_ascii_whitespace`.
/// [`u8::is_ascii_whitespace`].
///
/// # Examples
///
/// ```
/// #![feature(byte_slice_trim_ascii)]
///
/// assert_eq!(b"\r hello world\n ".trim_ascii(), b"hello world");
/// assert_eq!(b" ".trim_ascii(), b"");
/// assert_eq!(b"".trim_ascii(), b"");
/// ```
#[unstable(feature = "byte_slice_trim_ascii", issue = "94035")]
#[stable(feature = "byte_slice_trim_ascii", since = "CURRENT_RUSTC_VERSION")]
#[rustc_const_stable(feature = "byte_slice_trim_ascii", since = "CURRENT_RUSTC_VERSION")]
#[inline]
pub const fn trim_ascii(&self) -> &[u8] {
self.trim_ascii_start().trim_ascii_end()

View file

@ -2531,15 +2531,14 @@ pub fn make_ascii_lowercase(&mut self) {
/// # Examples
///
/// ```
/// #![feature(byte_slice_trim_ascii)]
///
/// assert_eq!(" \t \u{3000}hello world\n".trim_ascii_start(), "\u{3000}hello world\n");
/// assert_eq!(" ".trim_ascii_start(), "");
/// assert_eq!("".trim_ascii_start(), "");
/// ```
#[unstable(feature = "byte_slice_trim_ascii", issue = "94035")]
#[must_use = "this returns the trimmed string as a new slice, \
without modifying the original"]
#[stable(feature = "byte_slice_trim_ascii", since = "CURRENT_RUSTC_VERSION")]
#[rustc_const_stable(feature = "byte_slice_trim_ascii", since = "CURRENT_RUSTC_VERSION")]
#[inline]
pub const fn trim_ascii_start(&self) -> &str {
// SAFETY: Removing ASCII characters from a `&str` does not invalidate
@ -2557,15 +2556,14 @@ pub const fn trim_ascii_start(&self) -> &str {
/// # Examples
///
/// ```
/// #![feature(byte_slice_trim_ascii)]
///
/// assert_eq!("\r hello world\u{3000}\n ".trim_ascii_end(), "\r hello world\u{3000}");
/// assert_eq!(" ".trim_ascii_end(), "");
/// assert_eq!("".trim_ascii_end(), "");
/// ```
#[unstable(feature = "byte_slice_trim_ascii", issue = "94035")]
#[must_use = "this returns the trimmed string as a new slice, \
without modifying the original"]
#[stable(feature = "byte_slice_trim_ascii", since = "CURRENT_RUSTC_VERSION")]
#[rustc_const_stable(feature = "byte_slice_trim_ascii", since = "CURRENT_RUSTC_VERSION")]
#[inline]
pub const fn trim_ascii_end(&self) -> &str {
// SAFETY: Removing ASCII characters from a `&str` does not invalidate
@ -2584,15 +2582,14 @@ pub const fn trim_ascii_end(&self) -> &str {
/// # Examples
///
/// ```
/// #![feature(byte_slice_trim_ascii)]
///
/// assert_eq!("\r hello world\n ".trim_ascii(), "hello world");
/// assert_eq!(" ".trim_ascii(), "");
/// assert_eq!("".trim_ascii(), "");
/// ```
#[unstable(feature = "byte_slice_trim_ascii", issue = "94035")]
#[must_use = "this returns the trimmed string as a new slice, \
without modifying the original"]
#[stable(feature = "byte_slice_trim_ascii", since = "CURRENT_RUSTC_VERSION")]
#[rustc_const_stable(feature = "byte_slice_trim_ascii", since = "CURRENT_RUSTC_VERSION")]
#[inline]
pub const fn trim_ascii(&self) -> &str {
// SAFETY: Removing ASCII characters from a `&str` does not invalidate

View file

@ -320,11 +320,13 @@ pub fn assert_single_path(&self) -> &TaskPath {
(
"tests",
&[
// tidy-alphabetical-start
"tests/assembly",
"tests/codegen",
"tests/codegen-units",
"tests/coverage",
"tests/coverage-run-rustdoc",
"tests/crashes",
"tests/debuginfo",
"tests/incremental",
"tests/mir-opt",
@ -340,6 +342,7 @@ pub fn assert_single_path(&self) -> &TaskPath {
"tests/rustdoc-ui",
"tests/ui",
"tests/ui-fulldeps",
// tidy-alphabetical-end
],
),
];

View file

@ -128,6 +128,26 @@ fn validate_path_remap() {
});
}
#[test]
fn check_missing_paths_for_x_test_tests() {
let build = Build::new(configure("test", &["A-A"], &["A-A"]));
let (_, tests_remap_paths) =
PATH_REMAP.iter().find(|(target_path, _)| *target_path == "tests").unwrap();
let tests_dir = fs::read_dir(build.src.join("tests")).unwrap();
for dir in tests_dir {
let path = dir.unwrap().path();
// Skip if not a test directory.
if path.ends_with("tests/auxiliary") || !path.is_dir() {
continue
}
assert!(tests_remap_paths.iter().any(|item| path.ends_with(*item)), "{} is missing in PATH_REMAP tests list.", path.display());
}
}
#[test]
fn test_exclude() {
let mut config = configure("test", &["A-A"], &["A-A"]);

View file

@ -9,5 +9,5 @@ tex/*/out
perf.data
perf.data.old
flamegraph.svg
tests/extern-so/libtestlib.so
tests/native-lib/libtestlib.so
.auto-*

View file

@ -72,14 +72,14 @@ For example:
You can (cross-)run the entire test suite using:
```
```sh
./miri test
MIRI_TEST_TARGET=i686-unknown-linux-gnu ./miri test
./miri test --target i686-unknown-linux-gnu
```
`./miri test FILTER` only runs those tests that contain `FILTER` in their filename (including the
base directory, e.g. `./miri test fail` will run all compile-fail tests). These filters are passed
to `cargo test`, so for multiple filters you need to use `./miri test -- FILTER1 FILTER2`.
base directory, e.g. `./miri test fail` will run all compile-fail tests). Multiple filters are
supported: `./miri test FILTER1 FILTER2` runs all tests that contain either string.
#### Fine grained logging
@ -139,9 +139,8 @@ and then you can use it as if it was installed by `rustup` as a component of the
in the `miri` toolchain's sysroot to prevent conflicts with other toolchains.
The Miri binaries in the `cargo` bin directory (usually `~/.cargo/bin`) are managed by rustup.
There's a test for the cargo wrapper in the `test-cargo-miri` directory; run
`./run-test.py` in there to execute it. Like `./miri test`, this respects the
`MIRI_TEST_TARGET` environment variable to execute the test for another target.
There's a test for the cargo wrapper in the `test-cargo-miri` directory; run `./run-test.py` in
there to execute it. You can pass `--target` to execute the test for another target.
### Using a modified standard library
@ -287,3 +286,41 @@ https. Add the following to your `.gitconfig`:
[url "git@github.com:"]
pushInsteadOf = https://github.com/
```
## Internal environment variables
The following environment variables are *internal* and must not be used by
anyone but Miri itself. They are used to communicate between different Miri
binaries, and as such worth documenting:
* `CARGO_EXTRA_FLAGS` is understood by `./miri` and passed to all host cargo invocations.
* `MIRI_BE_RUSTC` can be set to `host` or `target`. It tells the Miri driver to
actually not interpret the code but compile it like rustc would. With `target`, Miri sets
some compiler flags to prepare the code for interpretation; with `host`, this is not done.
This environment variable is useful to be sure that the compiled `rlib`s are compatible
with Miri.
* `MIRI_CALLED_FROM_SETUP` is set during the Miri sysroot build,
which will re-invoke `cargo-miri` as the `rustc` to use for this build.
* `MIRI_CALLED_FROM_RUSTDOC` when set to any value tells `cargo-miri` that it is
running as a child process of `rustdoc`, which invokes it twice for each doc-test
and requires special treatment, most notably a check-only build before interpretation.
This is set by `cargo-miri` itself when running as a `rustdoc`-wrapper.
* `MIRI_CWD` when set to any value tells the Miri driver to change to the given
directory after loading all the source files, but before commencing
interpretation. This is useful if the interpreted program wants a different
working directory at run-time than at build-time.
* `MIRI_LOCAL_CRATES` is set by `cargo-miri` to tell the Miri driver which
crates should be given special treatment in diagnostics, in addition to the
crate currently being compiled.
* `MIRI_ORIG_RUSTDOC` is set and read by different phases of `cargo-miri` to remember the
value of `RUSTDOC` from before it was overwritten.
* `MIRI_REPLACE_LIBRS_IF_NOT_TEST` when set to any value enables a hack that helps bootstrap
run the standard library tests in Miri.
* `MIRI_TEST_TARGET` is set by `./miri test` (and `./x.py test miri`) to tell the test harness about
the chosen target.
* `MIRI_VERBOSE` when set to any value tells the various `cargo-miri` phases to
perform verbose logging.
* `MIRI_HOST_SYSROOT` is set by bootstrap to tell `cargo-miri` which sysroot to use for *host*
operations.
* `RUSTC_BLESS` is set by `./miri test` (and `./x.py test miri`) to indicate bless-mode to the test
harness.

View file

@ -1,39 +1,34 @@
# Miri
An experimental interpreter for [Rust][rust]'s
[mid-level intermediate representation][mir] (MIR). It can run binaries and
test suites of cargo projects and detect certain classes of
[undefined behavior](https://doc.rust-lang.org/reference/behavior-considered-undefined.html),
for example:
Miri is an [Undefined Behavior][reference-ub] detection tool for Rust. It can run binaries and test
suites of cargo projects and detect unsafe code that fails to uphold its safety requirements. For
instance:
* Out-of-bounds memory accesses and use-after-free
* Invalid use of uninitialized data
* Violation of intrinsic preconditions (an [`unreachable_unchecked`] being
reached, calling [`copy_nonoverlapping`] with overlapping ranges, ...)
* Not sufficiently aligned memory accesses and references
* Violation of *some* basic type invariants (a `bool` that is not 0 or 1, for example,
* Violation of basic type invariants (a `bool` that is not 0 or 1, for example,
or an invalid enum discriminant)
* **Experimental**: Violations of the [Stacked Borrows] rules governing aliasing
for reference types
* **Experimental**: Violations of the [Tree Borrows] aliasing rules, as an optional
alternative to [Stacked Borrows]
* **Experimental**: Data races
* **Experimental**: Data races and emulation of weak memory effects, i.e.,
atomic reads can return outdated values.
On top of that, Miri will also tell you about memory leaks: when there is memory
still allocated at the end of the execution, and that memory is not reachable
from a global `static`, Miri will raise an error.
Miri supports almost all Rust language features; in particular, unwinding and
concurrency are properly supported (including some experimental emulation of
weak memory effects, i.e., reads can return outdated values).
You can use Miri to emulate programs on other targets, e.g. to ensure that
byte-level data manipulation works correctly both on little-endian and
big-endian systems. See
[cross-interpretation](#cross-interpretation-running-for-different-targets)
below.
Miri has already discovered some [real-world bugs](#bugs-found-by-miri). If you
Miri has already discovered many [real-world bugs](#bugs-found-by-miri). If you
found a bug with Miri, we'd appreciate if you tell us and we'll add it to the
list!
@ -45,24 +40,27 @@ clocks, are replaced by deterministic "fake" implementations. Set
(In particular, the "fake" system RNG APIs make Miri **not suited for
cryptographic use**! Do not generate keys using Miri.)
All that said, be aware that Miri will **not catch all cases of undefined
behavior** in your program, and cannot run all programs:
All that said, be aware that Miri does **not catch every violation of the Rust specification** in
your program, not least because there is no such specification. Miri uses its own approximation of
what is and is not Undefined Behavior in Rust. To the best of our knowledge, all Undefined Behavior
that has the potential to affect a program's correctness *is* being detected by Miri (modulo
[bugs][I-misses-ub]), but you should consult [the Reference][reference-ub] for the official
definition of Undefined Behavior. Miri will be updated with the Rust compiler to protect against UB
as it is understood by the current compiler, but it makes no promises about future versions of
rustc.
* There are still plenty of open questions around the basic invariants for some
types and when these invariants even have to hold. Miri tries to avoid false
positives here, so if your program runs fine in Miri right now that is by no
means a guarantee that it is UB-free when these questions get answered.
Further caveats that Miri users should be aware of:
In particular, Miri does not check that references point to valid data.
* If the program relies on unspecified details of how data is laid out, it will
still run fine in Miri -- but might break (including causing UB) on different
compiler versions or different platforms.
compiler versions or different platforms. (You can use `-Zrandomize-layout`
to detect some of these cases.)
* Program execution is non-deterministic when it depends, for example, on where
exactly in memory allocations end up, or on the exact interleaving of
concurrent threads. Miri tests one of many possible executions of your
program. You can alleviate this to some extent by running Miri with different
values for `-Zmiri-seed`, but that will still by far not explore all possible
executions.
program, but it will miss bugs that only occur in a different possible execution.
You can alleviate this to some extent by running Miri with different
values for `-Zmiri-seed`, but that will still by far not explore all possible executions.
* Miri runs the program as a platform-independent interpreter, so the program
has no access to most platform-specific APIs or FFI. A few APIs have been
implemented (such as printing to stdout, accessing environment variables, and
@ -70,8 +68,8 @@ behavior** in your program, and cannot run all programs:
not support networking. System API support varies between targets; if you run
on Windows it is a good idea to use `--target x86_64-unknown-linux-gnu` to get
better support.
* Weak memory emulation may [produce weak behaviours](https://github.com/rust-lang/miri/issues/2301)
unobservable by compiled programs running on real hardware when `SeqCst` fences are used, and it
* Weak memory emulation may [produce weak behaviors](https://github.com/rust-lang/miri/issues/2301)
when `SeqCst` fences are used that are not actually permitted by the Rust memory model, and it
cannot produce all behaviors possibly observable on real hardware.
Moreover, Miri fundamentally cannot tell you whether your code is *sound*. [Soundness] is the property
@ -87,6 +85,8 @@ coverage.
[Stacked Borrows]: https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md
[Tree Borrows]: https://perso.crans.org/vanille/treebor/
[Soundness]: https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#soundness-of-code--of-a-library
[reference-ub]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
[I-misses-ub]: https://github.com/rust-lang/miri/labels/I-misses-UB
## Using Miri
@ -97,14 +97,8 @@ Install Miri on Rust nightly via `rustup`:
rustup +nightly component add miri
```
If `rustup` says the `miri` component is unavailable, that's because not all
nightly releases come with all tools. Check out
[this website](https://rust-lang.github.io/rustup-components-history) to
determine a nightly version that comes with Miri and install that using `rustup
toolchain install nightly-YYYY-MM-DD`. Either way, all of the following commands
assume the right toolchain is pinned via `rustup override set nightly` or
`rustup override set nightly-YYYY-MM-DD`. (Alternatively, use `cargo
+nightly`/`cargo +nightly-YYYY-MM-DD` for each of the following commands.)
All the following commands assume the nightly toolchain is pinned via `rustup override set nightly`.
Alternatively, use `cargo +nightly` for each of the following commands.
Now you can run your project in Miri:
@ -118,12 +112,12 @@ dependencies. It will ask you for confirmation before installing anything.
example, `cargo miri test filter` only runs the tests containing `filter` in
their name.
You can pass arguments to Miri via `MIRIFLAGS`. For example,
You can pass [flags][miri-flags] to Miri via `MIRIFLAGS`. For example,
`MIRIFLAGS="-Zmiri-disable-stacked-borrows" cargo miri run` runs the program
without checking the aliasing of references.
When compiling code via `cargo miri`, the `cfg(miri)` config flag is set for code
that will be interpret under Miri. You can use this to ignore test cases that fail
that will be interpreted under Miri. You can use this to ignore test cases that fail
under Miri because they do things Miri does not support:
```rust
@ -159,10 +153,8 @@ endian-sensitive code.
### Running Miri on CI
To run Miri on CI, make sure that you handle the case where the latest nightly
does not ship the Miri component because it currently does not build. `rustup
toolchain install --component` knows how to handle this situation, so the
following snippet should always work:
When running Miri on CI, use the following snippet to install a nightly toolchain with the Miri
component:
```sh
rustup toolchain install nightly --component miri
@ -227,7 +219,7 @@ degree documented below):
- We have unofficial support (not maintained by the Miri team itself) for some further operating systems.
- `freebsd`: **maintainer wanted**. Supports `std::env` and parts of `std::{thread, fs}`, but not `std::sync`.
- `android`: **maintainer wanted**. Support very incomplete, but a basic "hello world" works.
- `illumos`: maintained by @devnexen. Support very incomplete, but a basic "hello world" works.
- `solaris` / `illumos`: maintained by @devnexen. Support very incomplete, but a basic "hello world" works.
- `wasm`: **maintainer wanted**. Support very incomplete, not even standard output works, but an empty `main` function works.
- For targets on other operating systems, Miri might fail before even reaching the `main` function.
@ -273,25 +265,12 @@ To get a backtrace, you need to disable isolation
RUST_BACKTRACE=1 MIRIFLAGS="-Zmiri-disable-isolation" cargo miri test
```
#### "found possibly newer version of crate `std` which `<dependency>` depends on"
Your build directory may contain artifacts from an earlier build that have/have
not been built for Miri. Run `cargo clean` before switching from non-Miri to
Miri builds and vice-versa.
#### "found crate `std` compiled by an incompatible version of rustc"
You may be running `cargo miri` with a different compiler version than the one
used to build the custom libstd that Miri uses, and Miri failed to detect that.
Try running `cargo miri clean`.
#### "no mir for `std::rt::lang_start_internal`"
This means the sysroot you are using was not compiled with Miri in mind. This
should never happen when you use `cargo miri` because that takes care of setting
up the sysroot. If you are using `miri` (the Miri driver) directly, see the
[contributors' guide](CONTRIBUTING.md) for how to use `./miri` to best do that.
## Miri `-Z` flags and environment variables
[miri-flags]: #miri--z-flags-and-environment-variables
@ -395,17 +374,17 @@ to Miri failing to detect cases of undefined behavior in a program.
this flag is **unsound**.
* `-Zmiri-disable-weak-memory-emulation` disables the emulation of some C++11 weak
memory effects.
* `-Zmiri-extern-so-file=<path to a shared object file>` is an experimental flag for providing support
for FFI calls. Functions not provided by that file are still executed via the usual Miri shims.
**WARNING**: If an invalid/incorrect `.so` file is specified, this can cause undefined behaviour in Miri itself!
And of course, Miri cannot do any checks on the actions taken by the external code.
* `-Zmiri-native-lib=<path to a shared object file>` is an experimental flag for providing support
for calling native functions from inside the interpreter via FFI. Functions not provided by that
file are still executed via the usual Miri shims.
**WARNING**: If an invalid/incorrect `.so` file is specified, this can cause Undefined Behavior in Miri itself!
And of course, Miri cannot do any checks on the actions taken by the native code.
Note that Miri has its own handling of file descriptors, so if you want to replace *some* functions
working on file descriptors, you will have to replace *all* of them, or the two kinds of
file descriptors will be mixed up.
This is **work in progress**; currently, only integer arguments and return values are
supported (and no, pointer/integer casts to work around this limitation will not work;
they will fail horribly). It also only works on unix hosts for now.
Follow [the discussion on supporting other types](https://github.com/rust-lang/miri/issues/2365).
they will fail horribly). It also only works on Linux hosts for now.
* `-Zmiri-measureme=<name>` enables `measureme` profiling for the interpreted program.
This can be used to find which parts of your program are executing slowly under Miri.
The profile is written out to a file inside a directory called `<name>`, and can be processed
@ -484,50 +463,14 @@ by all intended entry points, i.e. `cargo miri` and `./miri {test,run}`):
* `MIRI_SYSROOT` indicates the sysroot to use. When using `cargo miri`, this skips the automatic
setup -- only set this if you do not want to use the automatically created sysroot. When invoking
`cargo miri setup`, this indicates where the sysroot will be put.
* `MIRI_TEST_TARGET` (recognized by `./miri {test,run}`) indicates which target
architecture to test against. `miri` and `cargo miri` accept the `--target` flag for the same
purpose.
* `MIRI_TEST_THREADS` (recognized by `./miri test`): set the number of threads to use for running tests.
By default, the number of cores is used.
* `MIRI_NO_STD` makes sure that the target's sysroot is built without libstd. This allows testing
and running no_std programs. (Miri has a heuristic to detect no-std targets based on the target
name; this environment variable is only needed when that heuristic fails.)
* `RUSTC_BLESS` (recognized by `./miri test` and `cargo-miri-test/run-test.py`): overwrite all
`stderr` and `stdout` files instead of checking whether the output matches.
* `MIRI_SKIP_UI_CHECKS` (recognized by `./miri test`): don't check whether the
`stderr` or `stdout` files match the actual output.
The following environment variables are *internal* and must not be used by
anyone but Miri itself. They are used to communicate between different Miri
binaries, and as such worth documenting:
* `MIRI_BE_RUSTC` can be set to `host` or `target`. It tells the Miri driver to
actually not interpret the code but compile it like rustc would. With `target`, Miri sets
some compiler flags to prepare the code for interpretation; with `host`, this is not done.
This environment variable is useful to be sure that the compiled `rlib`s are compatible
with Miri.
* `MIRI_CALLED_FROM_SETUP` is set during the Miri sysroot build,
which will re-invoke `cargo-miri` as the `rustc` to use for this build.
* `MIRI_CALLED_FROM_RUSTDOC` when set to any value tells `cargo-miri` that it is
running as a child process of `rustdoc`, which invokes it twice for each doc-test
and requires special treatment, most notably a check-only build before interpretation.
This is set by `cargo-miri` itself when running as a `rustdoc`-wrapper.
* `MIRI_CWD` when set to any value tells the Miri driver to change to the given
directory after loading all the source files, but before commencing
interpretation. This is useful if the interpreted program wants a different
working directory at run-time than at build-time.
* `MIRI_LOCAL_CRATES` is set by `cargo-miri` to tell the Miri driver which
crates should be given special treatment in diagnostics, in addition to the
crate currently being compiled.
* `MIRI_ORIG_RUSTDOC` is set and read by different phases of `cargo-miri` to remember the
value of `RUSTDOC` from before it was overwritten.
* `MIRI_REPLACE_LIBRS_IF_NOT_TEST` when set to any value enables a hack that helps bootstrap
run the standard library tests in Miri.
* `MIRI_VERBOSE` when set to any value tells the various `cargo-miri` phases to
perform verbose logging.
* `MIRI_HOST_SYSROOT` is set by bootstrap to tell `cargo-miri` which sysroot to use for *host*
operations.
[testing-miri]: CONTRIBUTING.md#testing-the-miri-driver
## Miri `extern` functions

View file

@ -31,24 +31,26 @@ time ./miri build --all-targets # the build that all the `./miri test` below wil
endgroup
# Run tests. Recognizes these variables:
# - MIRI_TEST_TARGET: the target to test. Empty for host target.
# - TEST_TARGET: the target to test. Empty for host target.
# - GC_STRESS: if non-empty, run the GC stress test for the main test suite.
# - MIR_OPT: if non-empty, re-run test `pass` tests with mir-opt-level=4
# - MANY_SEEDS: if set to N, run the "many-seeds" tests N times
# - TEST_BENCH: if non-empty, check that the benchmarks all build
# - CARGO_MIRI_ENV: if non-empty, set some env vars and config to potentially confuse cargo-miri
function run_tests {
if [ -n "${MIRI_TEST_TARGET-}" ]; then
begingroup "Testing foreign architecture $MIRI_TEST_TARGET"
if [ -n "${TEST_TARGET-}" ]; then
begingroup "Testing foreign architecture $TEST_TARGET"
TARGET_FLAG="--target $TEST_TARGET"
else
begingroup "Testing host architecture"
TARGET_FLAG=""
fi
## ui test suite
if [ -n "${GC_STRESS-}" ]; then
time MIRIFLAGS="${MIRIFLAGS-} -Zmiri-provenance-gc=1" ./miri test
time MIRIFLAGS="${MIRIFLAGS-} -Zmiri-provenance-gc=1" ./miri test $TARGET_FLAG
else
time ./miri test
time ./miri test $TARGET_FLAG
fi
## advanced tests
@ -59,17 +61,17 @@ function run_tests {
# them. Also error locations change so we don't run the failing tests.
# We explicitly enable debug-assertions here, they are disabled by -O but we have tests
# which exist to check that we panic on debug assertion failures.
time MIRIFLAGS="${MIRIFLAGS-} -O -Zmir-opt-level=4 -Cdebug-assertions=yes" MIRI_SKIP_UI_CHECKS=1 ./miri test -- tests/{pass,panic}
time MIRIFLAGS="${MIRIFLAGS-} -O -Zmir-opt-level=4 -Cdebug-assertions=yes" MIRI_SKIP_UI_CHECKS=1 ./miri test $TARGET_FLAG tests/{pass,panic}
fi
if [ -n "${MANY_SEEDS-}" ]; then
# Also run some many-seeds tests.
time for FILE in tests/many-seeds/*.rs; do
./miri run "--many-seeds=0..$MANY_SEEDS" "$FILE"
./miri run "--many-seeds=0..$MANY_SEEDS" $TARGET_FLAG "$FILE"
done
fi
if [ -n "${TEST_BENCH-}" ]; then
# Check that the benchmarks build and run, but only once.
time HYPERFINE="hyperfine -w0 -r1" ./miri bench
time HYPERFINE="hyperfine -w0 -r1" ./miri bench $TARGET_FLAG
fi
## test-cargo-miri
@ -91,7 +93,7 @@ function run_tests {
echo 'build.rustc-wrapper = "thisdoesnotexist"' > .cargo/config.toml
fi
# Run the actual test
time ${PYTHON} test-cargo-miri/run-test.py
time ${PYTHON} test-cargo-miri/run-test.py $TARGET_FLAG
# Clean up
unset RUSTC MIRI
rm -rf .cargo
@ -100,17 +102,18 @@ function run_tests {
}
function run_tests_minimal {
if [ -n "${MIRI_TEST_TARGET-}" ]; then
begingroup "Testing MINIMAL foreign architecture $MIRI_TEST_TARGET: only testing $@"
if [ -n "${TEST_TARGET-}" ]; then
begingroup "Testing MINIMAL foreign architecture $TEST_TARGET: only testing $@"
TARGET_FLAG="--target $TEST_TARGET"
else
echo "run_tests_minimal requires MIRI_TEST_TARGET to be set"
echo "run_tests_minimal requires TEST_TARGET to be set"
exit 1
fi
time ./miri test -- "$@"
time ./miri test $TARGET_FLAG "$@"
# Ensure that a small smoke test of cargo-miri works.
time cargo miri run --manifest-path test-cargo-miri/no-std-smoke/Cargo.toml --target ${MIRI_TEST_TARGET-$HOST_TARGET}
time cargo miri run --manifest-path test-cargo-miri/no-std-smoke/Cargo.toml $TARGET_FLAG
endgroup
}
@ -126,34 +129,33 @@ case $HOST_TARGET in
# Extra tier 1
# With reduced many-seed count to avoid spending too much time on that.
# (All OSes and ABIs are run with 64 seeds at least once though via the macOS runner.)
MANY_SEEDS=16 MIRI_TEST_TARGET=i686-unknown-linux-gnu run_tests
MANY_SEEDS=16 MIRI_TEST_TARGET=aarch64-unknown-linux-gnu run_tests
MANY_SEEDS=16 MIRI_TEST_TARGET=x86_64-apple-darwin run_tests
MANY_SEEDS=16 MIRI_TEST_TARGET=x86_64-pc-windows-gnu run_tests
MANY_SEEDS=16 TEST_TARGET=i686-unknown-linux-gnu run_tests
MANY_SEEDS=16 TEST_TARGET=aarch64-unknown-linux-gnu run_tests
MANY_SEEDS=16 TEST_TARGET=x86_64-apple-darwin run_tests
MANY_SEEDS=16 TEST_TARGET=x86_64-pc-windows-gnu run_tests
;;
aarch64-apple-darwin)
# Host (tier 2)
GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
# Extra tier 1
MANY_SEEDS=64 MIRI_TEST_TARGET=i686-pc-windows-gnu run_tests
MANY_SEEDS=64 MIRI_TEST_TARGET=x86_64-pc-windows-msvc CARGO_MIRI_ENV=1 run_tests
MANY_SEEDS=64 TEST_TARGET=i686-pc-windows-gnu run_tests
MANY_SEEDS=64 TEST_TARGET=x86_64-pc-windows-msvc CARGO_MIRI_ENV=1 run_tests
# Extra tier 2
MIRI_TEST_TARGET=arm-unknown-linux-gnueabi run_tests
MIRI_TEST_TARGET=s390x-unknown-linux-gnu run_tests # big-endian architecture of choice
TEST_TARGET=arm-unknown-linux-gnueabi run_tests
TEST_TARGET=s390x-unknown-linux-gnu run_tests # big-endian architecture of choice
# Partially supported targets (tier 2)
VERY_BASIC="integer vec string btreemap" # common things we test on all of them (if they have std), requires no target-specific shims
BASIC="$VERY_BASIC hello hashmap alloc align" # ensures we have the shims for stdout and basic data structures
MIRI_TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal $BASIC panic/panic concurrency/simple atomic threadname libc-misc libc-random libc-time fs env num_cpus
MIRI_TEST_TARGET=i686-unknown-freebsd run_tests_minimal $BASIC panic/panic concurrency/simple atomic threadname libc-misc libc-random libc-time fs env num_cpus
MIRI_TEST_TARGET=x86_64-unknown-illumos run_tests_minimal $VERY_BASIC hello panic/panic concurrency/simple pthread-sync libc-misc libc-random
# TODO fix solaris stack guard
# MIRI_TEST_TARGET=x86_64-pc-solaris run_tests_minimal $VERY_BASIC hello panic/panic pthread-sync
MIRI_TEST_TARGET=aarch64-linux-android run_tests_minimal $VERY_BASIC hello panic/panic
MIRI_TEST_TARGET=wasm32-wasi run_tests_minimal $VERY_BASIC wasm
MIRI_TEST_TARGET=wasm32-unknown-unknown run_tests_minimal $VERY_BASIC wasm
MIRI_TEST_TARGET=thumbv7em-none-eabihf run_tests_minimal no_std
TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal $BASIC panic/panic concurrency/simple atomic threadname libc-mem libc-misc libc-random libc-time fs env num_cpus
TEST_TARGET=i686-unknown-freebsd run_tests_minimal $BASIC panic/panic concurrency/simple atomic threadname libc-mem libc-misc libc-random libc-time fs env num_cpus
TEST_TARGET=x86_64-unknown-illumos run_tests_minimal $VERY_BASIC hello panic/panic concurrency/simple pthread-sync libc-mem libc-misc libc-random
TEST_TARGET=x86_64-pc-solaris run_tests_minimal $VERY_BASIC hello panic/panic concurrency/simple pthread-sync libc-mem libc-misc libc-random
TEST_TARGET=aarch64-linux-android run_tests_minimal $VERY_BASIC hello panic/panic
TEST_TARGET=wasm32-wasi run_tests_minimal $VERY_BASIC wasm
TEST_TARGET=wasm32-unknown-unknown run_tests_minimal $VERY_BASIC wasm
TEST_TARGET=thumbv7em-none-eabihf run_tests_minimal no_std
# Custom target JSON file
MIRI_TEST_TARGET=tests/avr.json MIRI_NO_STD=1 run_tests_minimal no_std
TEST_TARGET=tests/avr.json MIRI_NO_STD=1 run_tests_minimal no_std
;;
i686-pc-windows-msvc)
# Host
@ -163,7 +165,7 @@ case $HOST_TARGET in
# Extra tier 1
# We really want to ensure a Linux target works on a Windows host,
# and a 64bit target works on a 32bit host.
MIRI_TEST_TARGET=x86_64-unknown-linux-gnu run_tests
TEST_TARGET=x86_64-unknown-linux-gnu run_tests
;;
*)
echo "FATAL: unknown host target: $HOST_TARGET"

View file

@ -1,5 +1,5 @@
use std::env;
use std::ffi::OsString;
use std::ffi::{OsStr, OsString};
use std::io::Write;
use std::ops::Not;
use std::ops::Range;
@ -23,7 +23,9 @@
impl MiriEnv {
/// Returns the location of the sysroot.
fn build_miri_sysroot(&mut self, quiet: bool) -> Result<PathBuf> {
///
/// If the target is None the sysroot will be built for the host machine.
fn build_miri_sysroot(&mut self, quiet: bool, target: Option<&OsStr>) -> Result<PathBuf> {
if let Some(miri_sysroot) = self.sh.var_os("MIRI_SYSROOT") {
// Sysroot already set, use that.
return Ok(miri_sysroot.into());
@ -35,26 +37,28 @@ fn build_miri_sysroot(&mut self, quiet: bool) -> Result<PathBuf> {
self.build(path!(self.miri_dir / "Cargo.toml"), &[], quiet)?;
self.build(&manifest_path, &[], quiet)?;
let target = &match self.sh.var("MIRI_TEST_TARGET") {
Ok(target) => vec!["--target".into(), target],
Err(_) => vec![],
};
let target_flag =
if let Some(target) = target { vec![OsStr::new("--target"), target] } else { vec![] };
let target_flag = &target_flag;
if !quiet {
match self.sh.var("MIRI_TEST_TARGET") {
Ok(target) => eprintln!("$ (building Miri sysroot for {target})"),
Err(_) => eprintln!("$ (building Miri sysroot)"),
if let Some(target) = target {
eprintln!("$ (building Miri sysroot for {})", target.to_string_lossy());
} else {
eprintln!("$ (building Miri sysroot)");
}
}
let output = cmd!(self.sh,
"cargo +{toolchain} --quiet run {cargo_extra_flags...} --manifest-path {manifest_path} --
miri setup --print-sysroot {target...}"
miri setup --print-sysroot {target_flag...}"
).read();
let Ok(output) = output else {
// Run it again (without `--print-sysroot` or `--quiet`) so the user can see the error.
cmd!(
self.sh,
"cargo +{toolchain} run {cargo_extra_flags...} --manifest-path {manifest_path} --
miri setup {target...}"
miri setup {target_flag...}"
)
.run()
.with_context(|| "`cargo miri setup` failed")?;
@ -161,13 +165,13 @@ pub fn exec(self) -> Result<()> {
Command::Install { flags } => Self::install(flags),
Command::Build { flags } => Self::build(flags),
Command::Check { flags } => Self::check(flags),
Command::Test { bless, flags } => Self::test(bless, flags),
Command::Test { bless, flags, target } => Self::test(bless, flags, target),
Command::Run { dep, verbose, many_seeds, flags } =>
Self::run(dep, verbose, many_seeds, flags),
Command::Fmt { flags } => Self::fmt(flags),
Command::Clippy { flags } => Self::clippy(flags),
Command::Cargo { flags } => Self::cargo(flags),
Command::Bench { benches } => Self::bench(benches),
Command::Bench { target, benches } => Self::bench(target, benches),
Command::Toolchain { flags } => Self::toolchain(flags),
Command::RustcPull { commit } => Self::rustc_pull(commit.clone()),
Command::RustcPush { github_user, branch } => Self::rustc_push(github_user, branch),
@ -369,7 +373,7 @@ fn rustc_push(github_user: String, branch: String) -> Result<()> {
Ok(())
}
fn bench(benches: Vec<OsString>) -> Result<()> {
fn bench(target: Option<OsString>, benches: Vec<OsString>) -> Result<()> {
// The hyperfine to use
let hyperfine = env::var("HYPERFINE");
let hyperfine = hyperfine.as_deref().unwrap_or("hyperfine -w 1 -m 5 --shell=none");
@ -377,8 +381,6 @@ fn bench(benches: Vec<OsString>) -> Result<()> {
let Some((program_name, args)) = hyperfine.split_first() else {
bail!("expected HYPERFINE environment variable to be non-empty");
};
// Extra flags to pass to cargo.
let cargo_extra_flags = std::env::var("CARGO_EXTRA_FLAGS").unwrap_or_default();
// Make sure we have an up-to-date Miri installed and selected the right toolchain.
Self::install(vec![])?;
@ -394,6 +396,14 @@ fn bench(benches: Vec<OsString>) -> Result<()> {
} else {
benches.to_owned()
};
let target_flag = if let Some(target) = target {
let mut flag = OsString::from("--target=");
flag.push(target);
flag
} else {
OsString::new()
};
let target_flag = &target_flag;
// Run the requested benchmarks
for bench in benches {
let current_bench = path!(benches_dir / bench / "Cargo.toml");
@ -401,7 +411,7 @@ fn bench(benches: Vec<OsString>) -> Result<()> {
// That seems to make Windows CI happy.
cmd!(
sh,
"{program_name} {args...} 'cargo miri run '{cargo_extra_flags}' --manifest-path \"'{current_bench}'\"'"
"{program_name} {args...} 'cargo miri run '{target_flag}' --manifest-path \"'{current_bench}'\"'"
)
.run()?;
}
@ -446,16 +456,26 @@ fn cargo(flags: Vec<OsString>) -> Result<()> {
Ok(())
}
fn test(bless: bool, flags: Vec<OsString>) -> Result<()> {
fn test(bless: bool, mut flags: Vec<OsString>, target: Option<OsString>) -> Result<()> {
let mut e = MiriEnv::new()?;
// Prepare a sysroot.
e.build_miri_sysroot(/* quiet */ false)?;
// Then test, and let caller control flags.
// Only in root project as `cargo-miri` has no tests.
// Prepare a sysroot.
e.build_miri_sysroot(/* quiet */ false, target.as_deref())?;
// Forward information to test harness.
if bless {
e.sh.set_var("RUSTC_BLESS", "Gesundheit");
}
if let Some(target) = target {
// Tell the harness which target to test.
e.sh.set_var("MIRI_TEST_TARGET", target);
}
// Make sure the flags are going to the test harness, not cargo.
flags.insert(0, "--".into());
// Then test, and let caller control flags.
// Only in root project as `cargo-miri` has no tests.
e.test(path!(e.miri_dir / "Cargo.toml"), &flags)?;
Ok(())
}
@ -467,32 +487,16 @@ fn run(
mut flags: Vec<OsString>,
) -> Result<()> {
let mut e = MiriEnv::new()?;
// Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so
// that we set the MIRI_SYSROOT up the right way. We must make sure that
// MIRI_TEST_TARGET and `--target` are in sync.
use itertools::Itertools;
let target = flags
.iter()
.take_while(|arg| *arg != "--")
.tuple_windows()
.find(|(first, _)| *first == "--target");
if let Some((_, target)) = target {
// Found it!
e.sh.set_var("MIRI_TEST_TARGET", target);
} else if let Ok(target) = std::env::var("MIRI_TEST_TARGET") {
// Convert `MIRI_TEST_TARGET` into `--target`.
flags.push("--target".into());
flags.push(target.into());
}
let target = arg_flag_value(&flags, "--target");
// Scan for "--edition", set one ourselves if that flag is not present.
let have_edition =
flags.iter().take_while(|arg| *arg != "--").any(|arg| *arg == "--edition");
let have_edition = arg_flag_value(&flags, "--edition").is_some();
if !have_edition {
flags.push("--edition=2021".into()); // keep in sync with `tests/ui.rs`.`
}
// Prepare a sysroot, and add it to the flags.
let miri_sysroot = e.build_miri_sysroot(/* quiet */ !verbose)?;
let miri_sysroot = e.build_miri_sysroot(/* quiet */ !verbose, target.as_deref())?;
flags.push("--sysroot".into());
flags.push(miri_sysroot.into());

View file

@ -31,7 +31,10 @@ pub enum Command {
/// Build miri, set up a sysroot and then run the test suite.
Test {
bless: bool,
/// Flags that are passed through to `cargo test`.
/// The cross-interpretation target.
/// If none then the host is the target.
target: Option<OsString>,
/// Flags that are passed through to the test harness.
flags: Vec<OsString>,
},
/// Build miri, set up a sysroot and then run the driver with the given <flags>.
@ -58,6 +61,7 @@ pub enum Command {
Cargo { flags: Vec<OsString> },
/// Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
Bench {
target: Option<OsString>,
/// List of benchmarks to run. By default all benchmarks are run.
benches: Vec<OsString>,
},
@ -84,9 +88,9 @@ pub enum Command {
./miri check <flags>:
Just check miri. <flags> are passed to `cargo check`.
./miri test [--bless] <flags>:
Build miri, set up a sysroot and then run the test suite. <flags> are passed
to the final `cargo test` invocation.
./miri test [--bless] [--target <target>] <flags>:
Build miri, set up a sysroot and then run the test suite.
<flags> are passed to the test harness.
./miri run [--dep] [-v|--verbose] [--many-seeds|--many-seeds=..to|--many-seeds=from..to] <flags>:
Build miri, set up a sysroot and then run the driver with the given <flags>.
@ -110,7 +114,7 @@ pub enum Command {
working directory. Note that the binaries are placed in the `miri` toolchain
sysroot, to prevent conflicts with other toolchains.
./miri bench <benches>:
./miri bench [--target <target>] <benches>:
Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed.
<benches> can explicitly list the benchmarks to run; by default, all of them are run.
@ -147,12 +151,30 @@ fn main() -> Result<()> {
Some("build") => Command::Build { flags: args.collect() },
Some("check") => Command::Check { flags: args.collect() },
Some("test") => {
let bless = args.peek().is_some_and(|a| a.to_str() == Some("--bless"));
if bless {
// Consume the flag.
let mut target = None;
let mut bless = false;
while let Some(arg) = args.peek().and_then(|s| s.to_str()) {
match arg {
"--bless" => bless = true,
"--target" => {
// Skip "--target"
args.next().unwrap();
// Next argument is the target triple.
let val = args.peek().ok_or_else(|| {
anyhow!("`--target` must be followed by target triple")
})?;
target = Some(val.to_owned());
}
// Only parse the leading flags.
_ => break,
}
// Consume the flag, look at the next one.
args.next().unwrap();
}
Command::Test { bless, flags: args.collect() }
Command::Test { bless, flags: args.collect(), target }
}
Some("run") => {
let mut dep = false;
@ -188,7 +210,29 @@ fn main() -> Result<()> {
Some("clippy") => Command::Clippy { flags: args.collect() },
Some("cargo") => Command::Cargo { flags: args.collect() },
Some("install") => Command::Install { flags: args.collect() },
Some("bench") => Command::Bench { benches: args.collect() },
Some("bench") => {
let mut target = None;
while let Some(arg) = args.peek().and_then(|s| s.to_str()) {
match arg {
"--target" => {
// Skip "--target"
args.next().unwrap();
// Next argument is the target triple.
let val = args.peek().ok_or_else(|| {
anyhow!("`--target` must be followed by target triple")
})?;
target = Some(val.to_owned());
}
// Only parse the leading flags.
_ => break,
}
// Consume the flag, look at the next one.
args.next().unwrap();
}
Command::Bench { target, benches: args.collect() }
}
Some("toolchain") => Command::Toolchain { flags: args.collect() },
Some("rustc-pull") => {
let commit = args.next().map(|a| a.to_string_lossy().into_owned());

View file

@ -27,6 +27,30 @@ pub fn flagsplit(flags: &str) -> Vec<String> {
flags.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string).collect()
}
pub fn arg_flag_value(
args: impl IntoIterator<Item = impl AsRef<OsStr>>,
flag: &str,
) -> Option<OsString> {
let mut args = args.into_iter();
while let Some(arg) = args.next() {
let arg = arg.as_ref();
if arg == "--" {
return None;
}
let Some(arg) = arg.to_str() else {
// Skip non-UTF-8 arguments.
continue;
};
if arg == flag {
// Next one is the value.
return Some(args.next()?.as_ref().to_owned());
} else if let Some(val) = arg.strip_prefix(flag).and_then(|s| s.strip_prefix("=")) {
return Some(val.to_owned().into());
}
}
None
}
/// Some extra state we track for building Miri, such as the right RUSTFLAGS.
pub struct MiriEnv {
/// miri_dir is the root of the miri repository checkout we are working in.

View file

@ -1 +1 @@
d568423a7a4ddb4b49323d96078a22f94df55fbd
ef15976387ad9c1cdceaabf469e0cf35f5852f6d

View file

@ -575,18 +575,15 @@ fn main() {
"full" => BacktraceStyle::Full,
_ => show_error!("-Zmiri-backtrace may only be 0, 1, or full"),
};
} else if let Some(param) = arg.strip_prefix("-Zmiri-extern-so-file=") {
} else if let Some(param) = arg.strip_prefix("-Zmiri-native-lib=") {
let filename = param.to_string();
if std::path::Path::new(&filename).exists() {
if let Some(other_filename) = miri_config.external_so_file {
show_error!(
"-Zmiri-extern-so-file is already set to {}",
other_filename.display()
);
if let Some(other_filename) = miri_config.native_lib {
show_error!("-Zmiri-native-lib is already set to {}", other_filename.display());
}
miri_config.external_so_file = Some(filename.into());
miri_config.native_lib = Some(filename.into());
} else {
show_error!("-Zmiri-extern-so-file `{}` does not exist", filename);
show_error!("-Zmiri-native-lib `{}` does not exist", filename);
}
} else if let Some(param) = arg.strip_prefix("-Zmiri-num-cpus=") {
let num_cpus = param

View file

@ -142,8 +142,8 @@ pub struct MiriConfig {
/// Whether Stacked Borrows and Tree Borrows retagging should recurse into fields of datatypes.
pub retag_fields: RetagFields,
/// The location of a shared object file to load when calling external functions
/// FIXME! consider allowing users to specify paths to multiple SO files, or to a directory
pub external_so_file: Option<PathBuf>,
/// FIXME! consider allowing users to specify paths to multiple files, or to a directory
pub native_lib: Option<PathBuf>,
/// Run a garbage collector for BorTags every N basic blocks.
pub gc_interval: u32,
/// The number of CPUs to be reported by miri.
@ -188,7 +188,7 @@ fn default() -> MiriConfig {
preemption_rate: 0.01, // 1%
report_progress: None,
retag_fields: RetagFields::Yes,
external_so_file: None,
native_lib: None,
gc_interval: 10_000,
num_cpus: 1,
page_size: None,

View file

@ -751,26 +751,23 @@ fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar<Provenance>> {
/// This function tries to produce the most similar OS error from the `std::io::ErrorKind`
/// as a platform-specific errnum.
fn io_error_to_errnum(
&self,
err_kind: std::io::ErrorKind,
) -> InterpResult<'tcx, Scalar<Provenance>> {
fn io_error_to_errnum(&self, err: std::io::Error) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_ref();
let target = &this.tcx.sess.target;
if target.families.iter().any(|f| f == "unix") {
for &(name, kind) in UNIX_IO_ERROR_TABLE {
if err_kind == kind {
if err.kind() == kind {
return Ok(this.eval_libc(name));
}
}
throw_unsup_format!("io error {:?} cannot be translated into a raw os error", err_kind)
throw_unsup_format!("unsupported io error: {err}")
} else if target.families.iter().any(|f| f == "windows") {
for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
if err_kind == kind {
if err.kind() == kind {
return Ok(this.eval_windows("c", name));
}
}
throw_unsup_format!("io error {:?} cannot be translated into a raw os error", err_kind);
throw_unsup_format!("unsupported io error: {err}");
} else {
throw_unsup_format!(
"converting io::Error into errnum is unsupported for OS {}",
@ -812,8 +809,8 @@ fn try_errnum_to_io_error(
}
/// Sets the last OS error using a `std::io::ErrorKind`.
fn set_last_error_from_io_error(&mut self, err_kind: std::io::ErrorKind) -> InterpResult<'tcx> {
self.set_last_error(self.io_error_to_errnum(err_kind)?)
fn set_last_error_from_io_error(&mut self, err: std::io::Error) -> InterpResult<'tcx> {
self.set_last_error(self.io_error_to_errnum(err)?)
}
/// Helper function that consumes an `std::io::Result<T>` and returns an
@ -829,7 +826,7 @@ fn try_unwrap_io_result<T: From<i32>>(
match result {
Ok(ok) => Ok(ok),
Err(e) => {
self.eval_context_mut().set_last_error_from_io_error(e.kind())?;
self.eval_context_mut().set_last_error_from_io_error(e)?;
Ok((-1).into())
}
}
@ -982,29 +979,46 @@ fn write_c_str(
Ok((true, string_length))
}
/// Read a sequence of u16 until the first null terminator.
fn read_wide_str(&self, mut ptr: Pointer<Option<Provenance>>) -> InterpResult<'tcx, Vec<u16>> {
/// Helper function to read a sequence of unsigned integers of the given size and alignment
/// until the first null terminator.
fn read_c_str_with_char_size<T>(
&self,
mut ptr: Pointer<Option<Provenance>>,
size: Size,
align: Align,
) -> InterpResult<'tcx, Vec<T>>
where
T: TryFrom<u128>,
<T as TryFrom<u128>>::Error: std::fmt::Debug,
{
assert_ne!(size, Size::ZERO);
let this = self.eval_context_ref();
let size2 = Size::from_bytes(2);
this.check_ptr_align(ptr, Align::from_bytes(2).unwrap())?;
this.check_ptr_align(ptr, align)?;
let mut wchars = Vec::new();
loop {
// FIXME: We are re-getting the allocation each time around the loop.
// Would be nice if we could somehow "extend" an existing AllocRange.
let alloc = this.get_ptr_alloc(ptr, size2)?.unwrap(); // not a ZST, so we will get a result
let wchar = alloc.read_integer(alloc_range(Size::ZERO, size2))?.to_u16()?;
if wchar == 0 {
let alloc = this.get_ptr_alloc(ptr, size)?.unwrap(); // not a ZST, so we will get a result
let wchar_int = alloc.read_integer(alloc_range(Size::ZERO, size))?.to_bits(size)?;
if wchar_int == 0 {
break;
} else {
wchars.push(wchar);
ptr = ptr.offset(size2, this)?;
wchars.push(wchar_int.try_into().unwrap());
ptr = ptr.offset(size, this)?;
}
}
Ok(wchars)
}
/// Read a sequence of u16 until the first null terminator.
fn read_wide_str(&self, ptr: Pointer<Option<Provenance>>) -> InterpResult<'tcx, Vec<u16>> {
self.read_c_str_with_char_size(ptr, Size::from_bytes(2), Align::from_bytes(2).unwrap())
}
/// Helper function to write a sequence of u16 with an added 0x0000-terminator, which is what
/// the Windows APIs usually handle. This function returns `Ok((false, length))` without trying
/// to write if `size` is not large enough to fit the contents of `os_string` plus a null
@ -1037,6 +1051,14 @@ fn write_wide_str(
Ok((true, string_length))
}
/// Read a sequence of wchar_t until the first null terminator.
/// Always returns a `Vec<u32>` no matter the size of `wchar_t`.
fn read_wchar_t_str(&self, ptr: Pointer<Option<Provenance>>) -> InterpResult<'tcx, Vec<u32>> {
let this = self.eval_context_ref();
let wchar_t = this.libc_ty_layout("wchar_t");
self.read_c_str_with_char_size(ptr, wchar_t.size, wchar_t.align.abi)
}
/// Check that the ABI is what we expect.
fn check_abi<'a>(&self, abi: Abi, exp_abi: Abi) -> InterpResult<'a, ()> {
if abi != exp_abi {

View file

@ -12,6 +12,7 @@
#![feature(let_chains)]
#![feature(lint_reasons)]
#![feature(trait_upcasting)]
#![feature(strict_overflow_ops)]
// Configure clippy and other lints
#![allow(
clippy::collapsible_else_if,

View file

@ -535,11 +535,11 @@ pub struct MiriMachine<'mir, 'tcx> {
// The total number of blocks that have been executed.
pub(crate) basic_block_count: u64,
/// Handle of the optional shared object file for external functions.
/// Handle of the optional shared object file for native functions.
#[cfg(target_os = "linux")]
pub external_so_lib: Option<(libloading::Library, std::path::PathBuf)>,
pub native_lib: Option<(libloading::Library, std::path::PathBuf)>,
#[cfg(not(target_os = "linux"))]
pub external_so_lib: Option<!>,
pub native_lib: Option<!>,
/// Run a garbage collector for BorTags every N basic blocks.
pub(crate) gc_interval: u32,
@ -665,7 +665,7 @@ pub(crate) fn new(config: &MiriConfig, layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>)
basic_block_count: 0,
clock: Clock::new(config.isolated_op == IsolatedOp::Allow),
#[cfg(target_os = "linux")]
external_so_lib: config.external_so_file.as_ref().map(|lib_file_path| {
native_lib: config.native_lib.as_ref().map(|lib_file_path| {
let target_triple = layout_cx.tcx.sess.opts.target_triple.triple();
// Check if host target == the session target.
if env!("TARGET") != target_triple {
@ -687,7 +687,7 @@ pub(crate) fn new(config: &MiriConfig, layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>)
)
}),
#[cfg(not(target_os = "linux"))]
external_so_lib: config.external_so_file.as_ref().map(|_| {
native_lib: config.native_lib.as_ref().map(|_| {
panic!("loading external .so files is only supported on Linux")
}),
gc_interval: config.gc_interval,
@ -802,7 +802,7 @@ fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
preemption_rate: _,
report_progress: _,
basic_block_count: _,
external_so_lib: _,
native_lib: _,
gc_interval: _,
since_gc: _,
num_cpus: _,

View file

@ -19,22 +19,36 @@ pub(super) fn check_alloc_request<'tcx>(size: u64, align: u64) -> InterpResult<'
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Returns the minimum alignment for the target architecture for allocations of the given size.
fn min_align(&self, size: u64, kind: MiriMemoryKind) -> Align {
/// Returns the alignment that `malloc` would guarantee for requests of the given size.
fn malloc_align(&self, size: u64) -> Align {
let this = self.eval_context_ref();
// List taken from `library/std/src/sys/pal/common/alloc.rs`.
// This list should be kept in sync with the one from libstd.
let min_align = match this.tcx.sess.target.arch.as_ref() {
// The C standard says: "The pointer returned if the allocation succeeds is suitably aligned
// so that it may be assigned to a pointer to any type of object with a fundamental
// alignment requirement and size less than or equal to the size requested."
// So first we need to figure out what the limits are for "fundamental alignment".
// This is given by `alignof(max_align_t)`. The following list is taken from
// `library/std/src/sys/pal/common/alloc.rs` (where this is called `MIN_ALIGN`) and should
// be kept in sync.
let max_fundamental_align = match this.tcx.sess.target.arch.as_ref() {
"x86" | "arm" | "mips" | "mips32r6" | "powerpc" | "powerpc64" | "wasm32" => 8,
"x86_64" | "aarch64" | "mips64" | "mips64r6" | "s390x" | "sparc64" | "loongarch64" =>
16,
arch => bug!("unsupported target architecture for malloc: `{}`", arch),
};
// Windows always aligns, even small allocations.
// Source: <https://support.microsoft.com/en-us/help/286470/how-to-use-pageheap-exe-in-windows-xp-windows-2000-and-windows-server>
// But jemalloc does not, so for the C heap we only align if the allocation is sufficiently big.
if kind == MiriMemoryKind::WinHeap || size >= min_align {
return Align::from_bytes(min_align).unwrap();
// The C standard only requires sufficient alignment for any *type* with size less than or
// equal to the size requested. Types one can define in standard C seem to never have an alignment
// bigger than their size. So if the size is 2, then only alignment 2 is guaranteed, even if
// `max_fundamental_align` is bigger.
// This matches what some real-world implementations do, see e.g.
// - https://github.com/jemalloc/jemalloc/issues/1533
// - https://github.com/llvm/llvm-project/issues/53540
// - https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2293.htm
if size >= max_fundamental_align {
return Align::from_bytes(max_fundamental_align).unwrap();
}
// C doesn't have zero-sized types, so presumably nothing is guaranteed here.
if size == 0 {
return Align::ONE;
}
// We have `size < min_align`. Round `size` *down* to the next power of two and use that.
fn prev_power_of_two(x: u64) -> u64 {
@ -82,34 +96,25 @@ fn malloc(
&mut self,
size: u64,
zero_init: bool,
kind: MiriMemoryKind,
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
let this = self.eval_context_mut();
if size == 0 {
Ok(Pointer::null())
} else {
let align = this.min_align(size, kind);
let ptr = this.allocate_ptr(Size::from_bytes(size), align, kind.into())?;
if zero_init {
// We just allocated this, the access is definitely in-bounds and fits into our address space.
this.write_bytes_ptr(
ptr.into(),
iter::repeat(0u8).take(usize::try_from(size).unwrap()),
)
.unwrap();
}
Ok(ptr.into())
let align = this.malloc_align(size);
let ptr = this.allocate_ptr(Size::from_bytes(size), align, MiriMemoryKind::C.into())?;
if zero_init {
// We just allocated this, the access is definitely in-bounds and fits into our address space.
this.write_bytes_ptr(
ptr.into(),
iter::repeat(0u8).take(usize::try_from(size).unwrap()),
)
.unwrap();
}
Ok(ptr.into())
}
fn free(
&mut self,
ptr: Pointer<Option<Provenance>>,
kind: MiriMemoryKind,
) -> InterpResult<'tcx> {
fn free(&mut self, ptr: Pointer<Option<Provenance>>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
if !this.ptr_is_null(ptr)? {
this.deallocate_ptr(ptr, None, kind.into())?;
this.deallocate_ptr(ptr, None, MiriMemoryKind::C.into())?;
}
Ok(())
}
@ -118,19 +123,12 @@ fn realloc(
&mut self,
old_ptr: Pointer<Option<Provenance>>,
new_size: u64,
kind: MiriMemoryKind,
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
let this = self.eval_context_mut();
let new_align = this.min_align(new_size, kind);
let new_align = this.malloc_align(new_size);
if this.ptr_is_null(old_ptr)? {
// Here we must behave like `malloc`.
if new_size == 0 {
Ok(Pointer::null())
} else {
let new_ptr =
this.allocate_ptr(Size::from_bytes(new_size), new_align, kind.into())?;
Ok(new_ptr.into())
}
self.malloc(new_size, /*zero_init*/ false)
} else {
if new_size == 0 {
// C, in their infinite wisdom, made this UB.
@ -142,7 +140,7 @@ fn realloc(
None,
Size::from_bytes(new_size),
new_align,
kind.into(),
MiriMemoryKind::C.into(),
)?;
Ok(new_ptr.into())
}

View file

@ -1,289 +0,0 @@
use libffi::{high::call as ffi, low::CodePtr};
use std::ops::Deref;
use rustc_middle::ty::{self as ty, IntTy, Ty, UintTy};
use rustc_span::Symbol;
use rustc_target::abi::HasDataLayout;
use crate::*;
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Extract the scalar value from the result of reading a scalar from the machine,
/// and convert it to a `CArg`.
fn scalar_to_carg(
k: Scalar<Provenance>,
arg_type: Ty<'tcx>,
cx: &impl HasDataLayout,
) -> InterpResult<'tcx, CArg> {
match arg_type.kind() {
// If the primitive provided can be converted to a type matching the type pattern
// then create a `CArg` of this primitive value with the corresponding `CArg` constructor.
// the ints
ty::Int(IntTy::I8) => {
return Ok(CArg::Int8(k.to_i8()?));
}
ty::Int(IntTy::I16) => {
return Ok(CArg::Int16(k.to_i16()?));
}
ty::Int(IntTy::I32) => {
return Ok(CArg::Int32(k.to_i32()?));
}
ty::Int(IntTy::I64) => {
return Ok(CArg::Int64(k.to_i64()?));
}
ty::Int(IntTy::Isize) => {
// This will fail if host != target, but then the entire FFI thing probably won't work well
// in that situation.
return Ok(CArg::ISize(k.to_target_isize(cx)?.try_into().unwrap()));
}
// the uints
ty::Uint(UintTy::U8) => {
return Ok(CArg::UInt8(k.to_u8()?));
}
ty::Uint(UintTy::U16) => {
return Ok(CArg::UInt16(k.to_u16()?));
}
ty::Uint(UintTy::U32) => {
return Ok(CArg::UInt32(k.to_u32()?));
}
ty::Uint(UintTy::U64) => {
return Ok(CArg::UInt64(k.to_u64()?));
}
ty::Uint(UintTy::Usize) => {
// This will fail if host != target, but then the entire FFI thing probably won't work well
// in that situation.
return Ok(CArg::USize(k.to_target_usize(cx)?.try_into().unwrap()));
}
_ => {}
}
// If no primitives were returned then we have an unsupported type.
throw_unsup_format!(
"unsupported scalar argument type to external C function: {:?}",
arg_type
);
}
/// Call external C function and
/// store output, depending on return type in the function signature.
fn call_external_c_and_store_return<'a>(
&mut self,
link_name: Symbol,
dest: &MPlaceTy<'tcx, Provenance>,
ptr: CodePtr,
libffi_args: Vec<libffi::high::Arg<'a>>,
) -> InterpResult<'tcx, ()> {
let this = self.eval_context_mut();
// Unsafe because of the call to external C code.
// Because this is calling a C function it is not necessarily sound,
// but there is no way around this and we've checked as much as we can.
unsafe {
// If the return type of a function is a primitive integer type,
// then call the function (`ptr`) with arguments `libffi_args`, store the return value as the specified
// primitive integer type, and then write this value out to the miri memory as an integer.
match dest.layout.ty.kind() {
// ints
ty::Int(IntTy::I8) => {
let x = ffi::call::<i8>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Int(IntTy::I16) => {
let x = ffi::call::<i16>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Int(IntTy::I32) => {
let x = ffi::call::<i32>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Int(IntTy::I64) => {
let x = ffi::call::<i64>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Int(IntTy::Isize) => {
let x = ffi::call::<isize>(ptr, libffi_args.as_slice());
// `isize` doesn't `impl Into<i128>`, so convert manually.
// Convert to `i64` since this covers both 32- and 64-bit machines.
this.write_int(i64::try_from(x).unwrap(), dest)?;
return Ok(());
}
// uints
ty::Uint(UintTy::U8) => {
let x = ffi::call::<u8>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Uint(UintTy::U16) => {
let x = ffi::call::<u16>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Uint(UintTy::U32) => {
let x = ffi::call::<u32>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Uint(UintTy::U64) => {
let x = ffi::call::<u64>(ptr, libffi_args.as_slice());
this.write_int(x, dest)?;
return Ok(());
}
ty::Uint(UintTy::Usize) => {
let x = ffi::call::<usize>(ptr, libffi_args.as_slice());
// `usize` doesn't `impl Into<i128>`, so convert manually.
// Convert to `u64` since this covers both 32- and 64-bit machines.
this.write_int(u64::try_from(x).unwrap(), dest)?;
return Ok(());
}
// Functions with no declared return type (i.e., the default return)
// have the output_type `Tuple([])`.
ty::Tuple(t_list) =>
if t_list.len() == 0 {
ffi::call::<()>(ptr, libffi_args.as_slice());
return Ok(());
},
_ => {}
}
// FIXME ellen! deal with all the other return types
throw_unsup_format!("unsupported return type to external C function: {:?}", link_name);
}
}
/// Get the pointer to the function of the specified name in the shared object file,
/// if it exists. The function must be in the shared object file specified: we do *not*
/// return pointers to functions in dependencies of the library.
fn get_func_ptr_explicitly_from_lib(&mut self, link_name: Symbol) -> Option<CodePtr> {
let this = self.eval_context_mut();
// Try getting the function from the shared library.
// On windows `_lib_path` will be unused, hence the name starting with `_`.
let (lib, _lib_path) = this.machine.external_so_lib.as_ref().unwrap();
let func: libloading::Symbol<'_, unsafe extern "C" fn()> = unsafe {
match lib.get(link_name.as_str().as_bytes()) {
Ok(x) => x,
Err(_) => {
return None;
}
}
};
// FIXME: this is a hack!
// The `libloading` crate will automatically load system libraries like `libc`.
// On linux `libloading` is based on `dlsym`: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#202
// and `dlsym`(https://linux.die.net/man/3/dlsym) looks through the dependency tree of the
// library if it can't find the symbol in the library itself.
// So, in order to check if the function was actually found in the specified
// `machine.external_so_lib` we need to check its `dli_fname` and compare it to
// the specified SO file path.
// This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`,
// from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411
// using the `libc` crate where this interface is public.
// No `libc::dladdr` on windows.
let mut info = std::mem::MaybeUninit::<libc::Dl_info>::uninit();
unsafe {
if libc::dladdr(*func.deref() as *const _, info.as_mut_ptr()) != 0 {
if std::ffi::CStr::from_ptr(info.assume_init().dli_fname).to_str().unwrap()
!= _lib_path.to_str().unwrap()
{
return None;
}
}
}
// Return a pointer to the function.
Some(CodePtr(*func.deref() as *mut _))
}
/// Call specified external C function, with supplied arguments.
/// Need to convert all the arguments from their hir representations to
/// a form compatible with C (through `libffi` call).
/// Then, convert return from the C call into a corresponding form that
/// can be stored in Miri internal memory.
fn call_external_c_fct(
&mut self,
link_name: Symbol,
dest: &MPlaceTy<'tcx, Provenance>,
args: &[OpTy<'tcx, Provenance>],
) -> InterpResult<'tcx, bool> {
// Get the pointer to the function in the shared object file if it exists.
let code_ptr = match self.get_func_ptr_explicitly_from_lib(link_name) {
Some(ptr) => ptr,
None => {
// Shared object file does not export this function -- try the shims next.
return Ok(false);
}
};
let this = self.eval_context_mut();
// Get the function arguments, and convert them to `libffi`-compatible form.
let mut libffi_args = Vec::<CArg>::with_capacity(args.len());
for cur_arg in args.iter() {
libffi_args.push(Self::scalar_to_carg(
this.read_scalar(cur_arg)?,
cur_arg.layout.ty,
this,
)?);
}
// Convert them to `libffi::high::Arg` type.
let libffi_args = libffi_args
.iter()
.map(|cur_arg| cur_arg.arg_downcast())
.collect::<Vec<libffi::high::Arg<'_>>>();
// Call the function and store output, depending on return type in the function signature.
self.call_external_c_and_store_return(link_name, dest, code_ptr, libffi_args)?;
Ok(true)
}
}
#[derive(Debug, Clone)]
/// Enum of supported arguments to external C functions.
// We introduce this enum instead of just calling `ffi::arg` and storing a list
// of `libffi::high::Arg` directly, because the `libffi::high::Arg` just wraps a reference
// to the value it represents: https://docs.rs/libffi/latest/libffi/high/call/struct.Arg.html
// and we need to store a copy of the value, and pass a reference to this copy to C instead.
pub enum CArg {
/// 8-bit signed integer.
Int8(i8),
/// 16-bit signed integer.
Int16(i16),
/// 32-bit signed integer.
Int32(i32),
/// 64-bit signed integer.
Int64(i64),
/// isize.
ISize(isize),
/// 8-bit unsigned integer.
UInt8(u8),
/// 16-bit unsigned integer.
UInt16(u16),
/// 32-bit unsigned integer.
UInt32(u32),
/// 64-bit unsigned integer.
UInt64(u64),
/// usize.
USize(usize),
}
impl<'a> CArg {
/// Convert a `CArg` to a `libffi` argument type.
fn arg_downcast(&'a self) -> libffi::high::Arg<'a> {
match self {
CArg::Int8(i) => ffi::arg(i),
CArg::Int16(i) => ffi::arg(i),
CArg::Int32(i) => ffi::arg(i),
CArg::Int64(i) => ffi::arg(i),
CArg::ISize(i) => ffi::arg(i),
CArg::UInt8(i) => ffi::arg(i),
CArg::UInt16(i) => ffi::arg(i),
CArg::UInt32(i) => ffi::arg(i),
CArg::UInt64(i) => ffi::arg(i),
CArg::USize(i) => ffi::arg(i),
}
}
}

View file

@ -211,12 +211,12 @@ fn emulate_foreign_item_inner(
// First deal with any external C functions in linked .so file.
#[cfg(target_os = "linux")]
if this.machine.external_so_lib.as_ref().is_some() {
use crate::shims::ffi_support::EvalContextExt as _;
if this.machine.native_lib.as_ref().is_some() {
use crate::shims::native_lib::EvalContextExt as _;
// An Ok(false) here means that the function being called was not exported
// by the specified `.so` file; we should continue and check if it corresponds to
// a provided shim.
if this.call_external_c_fct(link_name, dest, args)? {
if this.call_native_fn(link_name, dest, args)? {
return Ok(EmulateItemResult::NeedsJumping);
}
}
@ -421,7 +421,7 @@ fn emulate_foreign_item_inner(
"malloc" => {
let [size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let size = this.read_target_usize(size)?;
let res = this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::C)?;
let res = this.malloc(size, /*zero_init:*/ false)?;
this.write_pointer(res, dest)?;
}
"calloc" => {
@ -432,20 +432,20 @@ fn emulate_foreign_item_inner(
let size = items
.checked_mul(len)
.ok_or_else(|| err_ub_format!("overflow during calloc size computation"))?;
let res = this.malloc(size, /*zero_init:*/ true, MiriMemoryKind::C)?;
let res = this.malloc(size, /*zero_init:*/ true)?;
this.write_pointer(res, dest)?;
}
"free" => {
let [ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
this.free(ptr, MiriMemoryKind::C)?;
this.free(ptr)?;
}
"realloc" => {
let [old_ptr, new_size] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let old_ptr = this.read_pointer(old_ptr)?;
let new_size = this.read_target_usize(new_size)?;
let res = this.realloc(old_ptr, new_size, MiriMemoryKind::C)?;
let res = this.realloc(old_ptr, new_size)?;
this.write_pointer(res, dest)?;
}
@ -657,6 +657,16 @@ fn emulate_foreign_item_inner(
dest,
)?;
}
"wcslen" => {
let [ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
// This reads at least 1 byte, so we are already enforcing that this is a valid pointer.
let n = this.read_wchar_t_str(ptr)?.len();
this.write_scalar(
Scalar::from_target_usize(u64::try_from(n).unwrap(), this),
dest,
)?;
}
"memcpy" => {
let [ptr_dest, ptr_src, n] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;

View file

@ -2,9 +2,9 @@
mod alloc;
mod backtrace;
#[cfg(target_os = "linux")]
pub mod ffi_support;
pub mod foreign_items;
#[cfg(target_os = "linux")]
pub mod native_lib;
pub mod unix;
pub mod windows;
mod x86;

View file

@ -0,0 +1,242 @@
//! Implements calling functions from a native library.
use libffi::{high::call as ffi, low::CodePtr};
use std::ops::Deref;
use rustc_middle::ty::{self as ty, IntTy, UintTy};
use rustc_span::Symbol;
use rustc_target::abi::{Abi, HasDataLayout};
use crate::*;
impl<'mir, 'tcx: 'mir> EvalContextExtPriv<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Call native host function and return the output as an immediate.
fn call_native_with_args<'a>(
&mut self,
link_name: Symbol,
dest: &MPlaceTy<'tcx, Provenance>,
ptr: CodePtr,
libffi_args: Vec<libffi::high::Arg<'a>>,
) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
let this = self.eval_context_mut();
// Call the function (`ptr`) with arguments `libffi_args`, and obtain the return value
// as the specified primitive integer type
let scalar = match dest.layout.ty.kind() {
// ints
ty::Int(IntTy::I8) => {
// Unsafe because of the call to native code.
// Because this is calling a C function it is not necessarily sound,
// but there is no way around this and we've checked as much as we can.
let x = unsafe { ffi::call::<i8>(ptr, libffi_args.as_slice()) };
Scalar::from_i8(x)
}
ty::Int(IntTy::I16) => {
let x = unsafe { ffi::call::<i16>(ptr, libffi_args.as_slice()) };
Scalar::from_i16(x)
}
ty::Int(IntTy::I32) => {
let x = unsafe { ffi::call::<i32>(ptr, libffi_args.as_slice()) };
Scalar::from_i32(x)
}
ty::Int(IntTy::I64) => {
let x = unsafe { ffi::call::<i64>(ptr, libffi_args.as_slice()) };
Scalar::from_i64(x)
}
ty::Int(IntTy::Isize) => {
let x = unsafe { ffi::call::<isize>(ptr, libffi_args.as_slice()) };
Scalar::from_target_isize(x.try_into().unwrap(), this)
}
// uints
ty::Uint(UintTy::U8) => {
let x = unsafe { ffi::call::<u8>(ptr, libffi_args.as_slice()) };
Scalar::from_u8(x)
}
ty::Uint(UintTy::U16) => {
let x = unsafe { ffi::call::<u16>(ptr, libffi_args.as_slice()) };
Scalar::from_u16(x)
}
ty::Uint(UintTy::U32) => {
let x = unsafe { ffi::call::<u32>(ptr, libffi_args.as_slice()) };
Scalar::from_u32(x)
}
ty::Uint(UintTy::U64) => {
let x = unsafe { ffi::call::<u64>(ptr, libffi_args.as_slice()) };
Scalar::from_u64(x)
}
ty::Uint(UintTy::Usize) => {
let x = unsafe { ffi::call::<usize>(ptr, libffi_args.as_slice()) };
Scalar::from_target_usize(x.try_into().unwrap(), this)
}
// Functions with no declared return type (i.e., the default return)
// have the output_type `Tuple([])`.
ty::Tuple(t_list) if t_list.len() == 0 => {
unsafe { ffi::call::<()>(ptr, libffi_args.as_slice()) };
return Ok(ImmTy::uninit(dest.layout));
}
_ => throw_unsup_format!("unsupported return type for native call: {:?}", link_name),
};
Ok(ImmTy::from_scalar(scalar, dest.layout))
}
/// Get the pointer to the function of the specified name in the shared object file,
/// if it exists. The function must be in the shared object file specified: we do *not*
/// return pointers to functions in dependencies of the library.
fn get_func_ptr_explicitly_from_lib(&mut self, link_name: Symbol) -> Option<CodePtr> {
let this = self.eval_context_mut();
// Try getting the function from the shared library.
// On windows `_lib_path` will be unused, hence the name starting with `_`.
let (lib, _lib_path) = this.machine.native_lib.as_ref().unwrap();
let func: libloading::Symbol<'_, unsafe extern "C" fn()> = unsafe {
match lib.get(link_name.as_str().as_bytes()) {
Ok(x) => x,
Err(_) => {
return None;
}
}
};
// FIXME: this is a hack!
// The `libloading` crate will automatically load system libraries like `libc`.
// On linux `libloading` is based on `dlsym`: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#202
// and `dlsym`(https://linux.die.net/man/3/dlsym) looks through the dependency tree of the
// library if it can't find the symbol in the library itself.
// So, in order to check if the function was actually found in the specified
// `machine.external_so_lib` we need to check its `dli_fname` and compare it to
// the specified SO file path.
// This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`,
// from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411
// using the `libc` crate where this interface is public.
let mut info = std::mem::MaybeUninit::<libc::Dl_info>::uninit();
unsafe {
if libc::dladdr(*func.deref() as *const _, info.as_mut_ptr()) != 0 {
if std::ffi::CStr::from_ptr(info.assume_init().dli_fname).to_str().unwrap()
!= _lib_path.to_str().unwrap()
{
return None;
}
}
}
// Return a pointer to the function.
Some(CodePtr(*func.deref() as *mut _))
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Call the native host function, with supplied arguments.
/// Needs to convert all the arguments from their Miri representations to
/// a native form (through `libffi` call).
/// Then, convert the return value from the native form into something that
/// can be stored in Miri's internal memory.
fn call_native_fn(
&mut self,
link_name: Symbol,
dest: &MPlaceTy<'tcx, Provenance>,
args: &[OpTy<'tcx, Provenance>],
) -> InterpResult<'tcx, bool> {
let this = self.eval_context_mut();
// Get the pointer to the function in the shared object file if it exists.
let code_ptr = match this.get_func_ptr_explicitly_from_lib(link_name) {
Some(ptr) => ptr,
None => {
// Shared object file does not export this function -- try the shims next.
return Ok(false);
}
};
// Get the function arguments, and convert them to `libffi`-compatible form.
let mut libffi_args = Vec::<CArg>::with_capacity(args.len());
for arg in args.iter() {
if !matches!(arg.layout.abi, Abi::Scalar(_)) {
throw_unsup_format!("only scalar argument types are support for native calls")
}
libffi_args.push(imm_to_carg(this.read_immediate(arg)?, this)?);
}
// Convert them to `libffi::high::Arg` type.
let libffi_args = libffi_args
.iter()
.map(|arg| arg.arg_downcast())
.collect::<Vec<libffi::high::Arg<'_>>>();
// Call the function and store output, depending on return type in the function signature.
let ret = this.call_native_with_args(link_name, dest, code_ptr, libffi_args)?;
this.write_immediate(*ret, dest)?;
Ok(true)
}
}
#[derive(Debug, Clone)]
/// Enum of supported arguments to external C functions.
// We introduce this enum instead of just calling `ffi::arg` and storing a list
// of `libffi::high::Arg` directly, because the `libffi::high::Arg` just wraps a reference
// to the value it represents: https://docs.rs/libffi/latest/libffi/high/call/struct.Arg.html
// and we need to store a copy of the value, and pass a reference to this copy to C instead.
enum CArg {
/// 8-bit signed integer.
Int8(i8),
/// 16-bit signed integer.
Int16(i16),
/// 32-bit signed integer.
Int32(i32),
/// 64-bit signed integer.
Int64(i64),
/// isize.
ISize(isize),
/// 8-bit unsigned integer.
UInt8(u8),
/// 16-bit unsigned integer.
UInt16(u16),
/// 32-bit unsigned integer.
UInt32(u32),
/// 64-bit unsigned integer.
UInt64(u64),
/// usize.
USize(usize),
}
impl<'a> CArg {
/// Convert a `CArg` to a `libffi` argument type.
fn arg_downcast(&'a self) -> libffi::high::Arg<'a> {
match self {
CArg::Int8(i) => ffi::arg(i),
CArg::Int16(i) => ffi::arg(i),
CArg::Int32(i) => ffi::arg(i),
CArg::Int64(i) => ffi::arg(i),
CArg::ISize(i) => ffi::arg(i),
CArg::UInt8(i) => ffi::arg(i),
CArg::UInt16(i) => ffi::arg(i),
CArg::UInt32(i) => ffi::arg(i),
CArg::UInt64(i) => ffi::arg(i),
CArg::USize(i) => ffi::arg(i),
}
}
}
/// Extract the scalar value from the result of reading a scalar from the machine,
/// and convert it to a `CArg`.
fn imm_to_carg<'tcx>(
v: ImmTy<'tcx, Provenance>,
cx: &impl HasDataLayout,
) -> InterpResult<'tcx, CArg> {
Ok(match v.layout.ty.kind() {
// If the primitive provided can be converted to a type matching the type pattern
// then create a `CArg` of this primitive value with the corresponding `CArg` constructor.
// the ints
ty::Int(IntTy::I8) => CArg::Int8(v.to_scalar().to_i8()?),
ty::Int(IntTy::I16) => CArg::Int16(v.to_scalar().to_i16()?),
ty::Int(IntTy::I32) => CArg::Int32(v.to_scalar().to_i32()?),
ty::Int(IntTy::I64) => CArg::Int64(v.to_scalar().to_i64()?),
ty::Int(IntTy::Isize) =>
CArg::ISize(v.to_scalar().to_target_isize(cx)?.try_into().unwrap()),
// the uints
ty::Uint(UintTy::U8) => CArg::UInt8(v.to_scalar().to_u8()?),
ty::Uint(UintTy::U16) => CArg::UInt16(v.to_scalar().to_u16()?),
ty::Uint(UintTy::U32) => CArg::UInt32(v.to_scalar().to_u32()?),
ty::Uint(UintTy::U64) => CArg::UInt64(v.to_scalar().to_u64()?),
ty::Uint(UintTy::Usize) =>
CArg::USize(v.to_scalar().to_target_usize(cx)?.try_into().unwrap()),
_ => throw_unsup_format!("unsupported argument type for native call: {}", v.layout.ty),
})
}

View file

@ -228,7 +228,7 @@ fn getcwd(
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`getcwd`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?;
return Ok(Pointer::null());
}
@ -241,7 +241,7 @@ fn getcwd(
let erange = this.eval_libc("ERANGE");
this.set_last_error(erange)?;
}
Err(e) => this.set_last_error_from_io_error(e.kind())?,
Err(e) => this.set_last_error_from_io_error(e)?,
}
Ok(Pointer::null())
@ -255,7 +255,7 @@ fn chdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32>
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`chdir`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?;
return Ok(-1);
}
@ -263,7 +263,7 @@ fn chdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32>
match env::set_current_dir(path) {
Ok(()) => Ok(0),
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
this.set_last_error_from_io_error(e)?;
Ok(-1)
}
}

View file

@ -312,7 +312,7 @@ fn fcntl(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32>
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fcntl`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?;
return Ok(-1);
}
@ -394,7 +394,7 @@ fn read(
Ok(read_bytes)
}
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
this.set_last_error_from_io_error(e)?;
Ok(-1)
}
}

View file

@ -310,7 +310,7 @@ fn emulate_foreign_item_inner(
this.write_null(dest)?;
}
Some(len) => {
let res = this.realloc(ptr, len, MiriMemoryKind::C)?;
let res = this.realloc(ptr, len)?;
this.write_pointer(res, dest)?;
}
}

View file

@ -137,37 +137,28 @@ fn file_type_to_d_type(
&mut self,
file_type: std::io::Result<FileType>,
) -> InterpResult<'tcx, i32> {
#[cfg(unix)]
use std::os::unix::fs::FileTypeExt;
let this = self.eval_context_mut();
match file_type {
Ok(file_type) => {
if file_type.is_dir() {
Ok(this.eval_libc("DT_DIR").to_u8()?.into())
} else if file_type.is_file() {
Ok(this.eval_libc("DT_REG").to_u8()?.into())
} else if file_type.is_symlink() {
Ok(this.eval_libc("DT_LNK").to_u8()?.into())
} else {
match () {
_ if file_type.is_dir() => Ok(this.eval_libc("DT_DIR").to_u8()?.into()),
_ if file_type.is_file() => Ok(this.eval_libc("DT_REG").to_u8()?.into()),
_ if file_type.is_symlink() => Ok(this.eval_libc("DT_LNK").to_u8()?.into()),
// Certain file types are only supported when the host is a Unix system.
// (i.e. devices and sockets) If it is, check those cases, if not, fall back to
// DT_UNKNOWN sooner.
#[cfg(unix)]
{
use std::os::unix::fs::FileTypeExt;
if file_type.is_block_device() {
Ok(this.eval_libc("DT_BLK").to_u8()?.into())
} else if file_type.is_char_device() {
Ok(this.eval_libc("DT_CHR").to_u8()?.into())
} else if file_type.is_fifo() {
Ok(this.eval_libc("DT_FIFO").to_u8()?.into())
} else if file_type.is_socket() {
Ok(this.eval_libc("DT_SOCK").to_u8()?.into())
} else {
Ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
}
}
#[cfg(not(unix))]
Ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
_ if file_type.is_block_device() =>
Ok(this.eval_libc("DT_BLK").to_u8()?.into()),
#[cfg(unix)]
_ if file_type.is_char_device() => Ok(this.eval_libc("DT_CHR").to_u8()?.into()),
#[cfg(unix)]
_ if file_type.is_fifo() => Ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
#[cfg(unix)]
_ if file_type.is_socket() => Ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
// Fallback
_ => Ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
}
}
Err(e) =>
@ -387,7 +378,7 @@ fn open(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`open`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?;
return Ok(-1);
}
@ -443,7 +434,7 @@ fn unlink(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`unlink`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?;
return Ok(-1);
}
@ -474,7 +465,7 @@ fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`symlink`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?;
return Ok(-1);
}
@ -775,7 +766,7 @@ fn rename(
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`rename`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?;
return Ok(-1);
}
@ -803,7 +794,7 @@ fn mkdir(
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`mkdir`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?;
return Ok(-1);
}
@ -831,7 +822,7 @@ fn rmdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32>
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`rmdir`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?;
return Ok(-1);
}
@ -868,7 +859,7 @@ fn opendir(
Ok(Scalar::from_target_usize(id, this))
}
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
this.set_last_error_from_io_error(e)?;
Ok(Scalar::null_ptr(this))
}
}
@ -956,7 +947,7 @@ fn linux_readdir64(
None
}
Some(Err(e)) => {
this.set_last_error_from_io_error(e.kind())?;
this.set_last_error_from_io_error(e)?;
None
}
};
@ -1308,13 +1299,12 @@ fn readlink(
Ok(path_bytes.len().try_into().unwrap())
}
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
this.set_last_error_from_io_error(e)?;
Ok(-1)
}
}
}
#[cfg_attr(not(unix), allow(unused))]
fn isatty(
&mut self,
miri_fd: &OpTy<'tcx, Provenance>,
@ -1392,7 +1382,7 @@ fn realpath(
Ok(Scalar::from_maybe_pointer(dest, this))
}
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
this.set_last_error_from_io_error(e)?;
Ok(Scalar::from_target_usize(0, this))
}
}
@ -1467,8 +1457,8 @@ fn mkstemp(&mut self, template_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
fopts.mode(0o600);
// Do not allow others to read or modify this file.
fopts.mode(0o600);
fopts.custom_flags(libc::O_EXCL);
}
#[cfg(windows)]
@ -1513,7 +1503,7 @@ fn mkstemp(&mut self, template_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx
_ => {
// "On error, -1 is returned, and errno is set to
// indicate the error"
this.set_last_error_from_io_error(e.kind())?;
this.set_last_error_from_io_error(e)?;
return Ok(-1);
}
},
@ -1592,7 +1582,7 @@ fn from_meta<'tcx>(
let metadata = match metadata {
Ok(metadata) => metadata,
Err(e) => {
ecx.set_last_error_from_io_error(e.kind())?;
ecx.set_last_error_from_io_error(e)?;
return Ok(None);
}
};

View file

@ -117,6 +117,7 @@ fn emulate_foreign_item_inner(
// `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)`
// is called if a `HashMap` is created the regular way (e.g. HashMap<K, V>).
id if id == sys_getrandom => {
// Used by getrandom 0.1
// The first argument is the syscall id, so skip over it.
if args.len() < 4 {
throw_ub_format!(
@ -124,7 +125,16 @@ fn emulate_foreign_item_inner(
args.len()
);
}
getrandom(this, &args[1], &args[2], &args[3], dest)?;
let ptr = this.read_pointer(&args[1])?;
let len = this.read_target_usize(&args[2])?;
// The only supported flags are GRND_RANDOM and GRND_NONBLOCK,
// neither of which have any effect on our current PRNG.
// See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
let _flags = this.read_scalar(&args[3])?.to_i32();
this.gen_random(ptr, len)?;
this.write_scalar(Scalar::from_target_usize(len, this), dest)?;
}
// `futex` is used by some synchronization primitives.
id if id == sys_futex => {
@ -196,24 +206,3 @@ fn emulate_foreign_item_inner(
Ok(EmulateItemResult::NeedsJumping)
}
}
// Shims the linux `getrandom` syscall.
fn getrandom<'tcx>(
this: &mut MiriInterpCx<'_, 'tcx>,
ptr: &OpTy<'tcx, Provenance>,
len: &OpTy<'tcx, Provenance>,
flags: &OpTy<'tcx, Provenance>,
dest: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let ptr = this.read_pointer(ptr)?;
let len = this.read_target_usize(len)?;
// The only supported flags are GRND_RANDOM and GRND_NONBLOCK,
// neither of which have any effect on our current PRNG.
// See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
let _flags = this.read_scalar(flags)?.to_i32();
this.gen_random(ptr, len)?;
this.write_scalar(Scalar::from_target_usize(len, this), dest)?;
Ok(())
}

View file

@ -42,9 +42,12 @@ fn mmap(
let map_shared = this.eval_libc_i32("MAP_SHARED");
let map_fixed = this.eval_libc_i32("MAP_FIXED");
// This is a horrible hack, but on MacOS the guard page mechanism uses mmap
// This is a horrible hack, but on MacOS and Solaris the guard page mechanism uses mmap
// in a way we do not support. We just give it the return value it expects.
if this.frame_in_std() && this.tcx.sess.target.os == "macos" && (flags & map_fixed) != 0 {
if this.frame_in_std()
&& matches!(&*this.tcx.sess.target.os, "macos" | "solaris")
&& (flags & map_fixed) != 0
{
return Ok(Scalar::from_maybe_pointer(Pointer::from_addr_invalid(addr), this));
}

View file

@ -2,7 +2,6 @@
use rustc_target::spec::abi::Abi;
use crate::*;
use shims::EmulateItemResult;
pub fn is_dyn_sym(_name: &str) -> bool {
false
@ -26,6 +25,24 @@ fn emulate_foreign_item_inner(
this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
}
"stack_getbounds" => {
let [stack] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let stack = this.deref_pointer_as(stack, this.libc_ty_layout("stack_t"))?;
this.write_int_fields_named(
&[
("ss_sp", this.machine.stack_addr.into()),
("ss_size", this.machine.stack_size.into()),
// field set to 0 means not in an alternate signal stack
// https://docs.oracle.com/cd/E86824_01/html/E54766/stack-getbounds-3c.html
("ss_flags", 0),
],
&stack,
)?;
this.write_null(dest)?;
}
_ => return Ok(EmulateItemResult::NotSupported),
}
Ok(EmulateItemResult::NeedsJumping)

View file

@ -153,7 +153,7 @@ fn GetCurrentDirectoryW(
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`GetCurrentDirectoryW`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?;
return Ok(Scalar::from_u32(0));
}
@ -166,7 +166,7 @@ fn GetCurrentDirectoryW(
this.write_path_to_wide_str(&cwd, buf, size)?,
)));
}
Err(e) => this.set_last_error_from_io_error(e.kind())?,
Err(e) => this.set_last_error_from_io_error(e)?,
}
Ok(Scalar::from_u32(0))
}
@ -185,7 +185,7 @@ fn SetCurrentDirectoryW(
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`SetCurrentDirectoryW`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?;
return Ok(this.eval_windows("c", "FALSE"));
}
@ -193,7 +193,7 @@ fn SetCurrentDirectoryW(
match env::set_current_dir(path) {
Ok(()) => Ok(this.eval_windows("c", "TRUE")),
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
this.set_last_error_from_io_error(e)?;
Ok(this.eval_windows("c", "FALSE"))
}
}

View file

@ -5,10 +5,9 @@
use std::str;
use rustc_span::Symbol;
use rustc_target::abi::Size;
use rustc_target::abi::{Align, Size};
use rustc_target::spec::abi::Abi;
use crate::shims::alloc::EvalContextExt as _;
use crate::shims::os_str::bytes_to_os_str;
use crate::shims::windows::*;
use crate::*;
@ -225,7 +224,7 @@ fn emulate_foreign_item_inner(
let filename = this.read_path_from_wide_str(filename)?;
let result = match win_absolute(&filename)? {
Err(err) => {
this.set_last_error_from_io_error(err.kind())?;
this.set_last_error_from_io_error(err)?;
Scalar::from_u32(0) // return zero upon failure
}
Ok(abs_filename) => {
@ -248,8 +247,21 @@ fn emulate_foreign_item_inner(
let size = this.read_target_usize(size)?;
let heap_zero_memory = 0x00000008; // HEAP_ZERO_MEMORY
let zero_init = (flags & heap_zero_memory) == heap_zero_memory;
let res = this.malloc(size, zero_init, MiriMemoryKind::WinHeap)?;
this.write_pointer(res, dest)?;
// Alignment is twice the pointer size.
// Source: <https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapalloc>
let align = this.tcx.pointer_size().bytes().strict_mul(2);
let ptr = this.allocate_ptr(
Size::from_bytes(size),
Align::from_bytes(align).unwrap(),
MiriMemoryKind::WinHeap.into(),
)?;
if zero_init {
this.write_bytes_ptr(
ptr.into(),
iter::repeat(0u8).take(usize::try_from(size).unwrap()),
)?;
}
this.write_pointer(ptr, dest)?;
}
"HeapFree" => {
let [handle, flags, ptr] =
@ -257,23 +269,41 @@ fn emulate_foreign_item_inner(
this.read_target_isize(handle)?;
this.read_scalar(flags)?.to_u32()?;
let ptr = this.read_pointer(ptr)?;
this.free(ptr, MiriMemoryKind::WinHeap)?;
// "This pointer can be NULL." It doesn't say what happens then, but presumably nothing.
// (https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapfree)
if !this.ptr_is_null(ptr)? {
this.deallocate_ptr(ptr, None, MiriMemoryKind::WinHeap.into())?;
}
this.write_scalar(Scalar::from_i32(1), dest)?;
}
"HeapReAlloc" => {
let [handle, flags, ptr, size] =
let [handle, flags, old_ptr, size] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
this.read_target_isize(handle)?;
this.read_scalar(flags)?.to_u32()?;
let ptr = this.read_pointer(ptr)?;
let old_ptr = this.read_pointer(old_ptr)?;
let size = this.read_target_usize(size)?;
let res = this.realloc(ptr, size, MiriMemoryKind::WinHeap)?;
this.write_pointer(res, dest)?;
let align = this.tcx.pointer_size().bytes().strict_mul(2); // same as above
// The docs say that `old_ptr` must come from an earlier HeapAlloc or HeapReAlloc,
// so unlike C `realloc` we do *not* allow a NULL here.
// (https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heaprealloc)
let new_ptr = this.reallocate_ptr(
old_ptr,
None,
Size::from_bytes(size),
Align::from_bytes(align).unwrap(),
MiriMemoryKind::WinHeap.into(),
)?;
this.write_pointer(new_ptr, dest)?;
}
"LocalFree" => {
let [ptr] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
this.free(ptr, MiriMemoryKind::WinLocal)?;
// "If the hMem parameter is NULL, LocalFree ignores the parameter and returns NULL."
// (https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-localfree)
if !this.ptr_is_null(ptr)? {
this.deallocate_ptr(ptr, None, MiriMemoryKind::WinLocal.into())?;
}
this.write_null(dest)?;
}
@ -513,6 +543,7 @@ fn emulate_foreign_item_inner(
throw_machine_stop!(TerminationInfo::Exit { code: code.into(), leak_check: false });
}
"SystemFunction036" => {
// used by getrandom 0.1
// This is really 'RtlGenRandom'.
let [ptr, len] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
@ -522,6 +553,7 @@ fn emulate_foreign_item_inner(
this.write_scalar(Scalar::from_bool(true), dest)?;
}
"ProcessPrng" => {
// used by `std`
let [ptr, len] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
@ -530,6 +562,7 @@ fn emulate_foreign_item_inner(
this.write_scalar(Scalar::from_i32(1), dest)?;
}
"BCryptGenRandom" => {
// used by getrandom 0.2
let [algorithm, ptr, len, flags] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let algorithm = this.read_scalar(algorithm)?;

View file

@ -10,6 +10,7 @@ import os
import re
import subprocess
import sys
import argparse
CGREEN = '\33[32m'
CBOLD = '\33[1m'
@ -25,8 +26,8 @@ def cargo_miri(cmd, quiet = True):
args = ["cargo", "miri", cmd] + CARGO_EXTRA_FLAGS
if quiet:
args += ["-q"]
if 'MIRI_TEST_TARGET' in os.environ:
args += ["--target", os.environ['MIRI_TEST_TARGET']]
if ARGS.target:
args += ["--target", ARGS.target]
return args
def normalize_stdout(str):
@ -35,7 +36,7 @@ def normalize_stdout(str):
return str
def check_output(actual, path, name):
if os.environ.get("RUSTC_BLESS", "0") != "0":
if ARGS.bless:
# Write the output only if bless is set
open(path, mode='w').write(actual)
return True
@ -133,7 +134,7 @@ def test_cargo_miri_run():
def test_cargo_miri_test():
# rustdoc is not run on foreign targets
is_foreign = 'MIRI_TEST_TARGET' in os.environ
is_foreign = ARGS.target is not None
default_ref = "test.cross-target.stdout.ref" if is_foreign else "test.default.stdout.ref"
filter_ref = "test.filter.cross-target.stdout.ref" if is_foreign else "test.filter.stdout.ref"
@ -182,16 +183,22 @@ def test_cargo_miri_test():
env={'MIRIFLAGS': "-Zmiri-permissive-provenance"},
)
args_parser = argparse.ArgumentParser(description='`cargo miri` testing')
args_parser.add_argument('--target', help='the target to test')
args_parser.add_argument('--bless', help='bless the reference files', action='store_true')
ARGS = args_parser.parse_args()
os.chdir(os.path.dirname(os.path.realpath(__file__)))
os.environ["CARGO_TARGET_DIR"] = "target" # this affects the location of the target directory that we need to check
os.environ["RUST_TEST_NOCAPTURE"] = "0" # this affects test output, so make sure it is not set
os.environ["RUST_TEST_THREADS"] = "1" # avoid non-deterministic output due to concurrent test runs
target_str = " for target {}".format(os.environ['MIRI_TEST_TARGET']) if 'MIRI_TEST_TARGET' in os.environ else ""
target_str = " for target {}".format(ARGS.target) if ARGS.target else ""
print(CGREEN + CBOLD + "## Running `cargo miri` tests{}".format(target_str) + CEND)
test_cargo_miri_run()
test_cargo_miri_test()
# Ensure we did not create anything outside the expected target dir.
for target_dir in ["target", "custom-run", "custom-test", "config-cli"]:
if os.listdir(target_dir) != ["miri"]:

View file

@ -17,12 +17,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "backtrace"
version = "0.3.71"
@ -50,12 +44,6 @@ version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytes"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]]
name = "cc"
version = "1.0.96"
@ -141,16 +129,6 @@ version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.21"
@ -192,7 +170,6 @@ dependencies = [
"libc",
"num_cpus",
"page_size",
"rand",
"tempfile",
"tokio",
"windows-sys 0.52.0",
@ -233,41 +210,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "parking_lot"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.52.5",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.81"
@ -286,45 +234,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.14",
]
[[package]]
name = "redox_syscall"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
dependencies = [
"bitflags",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@ -344,27 +253,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "socket2"
version = "0.5.7"
@ -405,13 +293,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",

View file

@ -15,11 +15,10 @@ tempfile = "3"
getrandom_01 = { package = "getrandom", version = "0.1" }
getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"] }
rand = { version = "0.8", features = ["small_rng"] }
[target.'cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))'.dependencies]
page_size = "0.6"
tokio = { version = "1.24", features = ["full"] }
tokio = { version = "1.24", features = ["macros", "rt-multi-thread", "time", "net"] }
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.52", features = [ "Win32_Foundation", "Win32_System_Threading" ] }

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
/// Test that destroying a pthread_cond twice fails, even without a check for number validity

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
//@ignore-target-apple: Our macOS condattr don't have any fields so we do not notice this.
/// Test that destroying a pthread_condattr twice fails, even without a check for number validity

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
//@error-in-other-file: the main thread terminated without waiting for all remaining threads
// Check that we terminate the program when the main thread terminates.

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
//! The thread function must have exactly one argument.

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
//! The thread function must have exactly one argument.

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
// Joining a detached thread is undefined behavior.

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
// Joining an already joined thread is undefined behavior.

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
// Joining the main thread is undefined behavior.

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
// Joining the same thread from multiple threads is undefined behavior.

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
// We are making scheduler assumptions here.
//@compile-flags: -Zmiri-preemption-rate=0

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
//
// Check that if we pass NULL attribute, then we get the default mutex type.

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
//@error-in-other-file: deadlock
use std::cell::UnsafeCell;

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
//
// Check that if we do not set the mutex type, it is the default.

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
fn main() {
unsafe {

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
/// Test that destroying a pthread_mutex twice fails, even without a check for number validity

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
fn main() {
unsafe {

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
fn main() {
unsafe {

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
use std::cell::UnsafeCell;
use std::sync::Arc;

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
/// Test that destroying a pthread_mutexattr twice fails, even without a check for number validity

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
fn main() {
let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER);

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
fn main() {
let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER);

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
/// Test that destroying a pthread_rwlock twice fails, even without a check for number validity

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
fn main() {
let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER);

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
use std::cell::UnsafeCell;
use std::sync::Arc;

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
fn main() {
let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER);

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
//@error-in-other-file: deadlock
use std::cell::UnsafeCell;

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
fn main() {
let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER);

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
//@error-in-other-file: deadlock
use std::cell::UnsafeCell;

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
fn main() {
let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER);

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No pthreads on Windows
use std::cell::UnsafeCell;
use std::sync::Arc;

View file

@ -1,5 +1,5 @@
//@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No libc env support on Windows
use std::env;
use std::thread;

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No libc IO on Windows
//@compile-flags: -Zmiri-disable-isolation
// FIXME: standard handles cannot be closed (https://github.com/rust-lang/rust/issues/40032)

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No libc IO on Windows
fn main() -> std::io::Result<()> {
let mut bytes = [0u8; 512];

View file

@ -1,4 +1,4 @@
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No mkstemp on Windows
//@compile-flags: -Zmiri-disable-isolation
fn main() {

View file

@ -1,5 +1,5 @@
//@compile-flags: -Zmiri-disable-isolation
//@ignore-target-windows: No libc on Windows
//@ignore-target-windows: No libc IO on Windows
fn main() -> std::io::Result<()> {
let mut bytes = [0u8; 512];

Some files were not shown because too many files have changed in this diff Show more