mirror of
https://github.com/rust-lang/rust
synced 2024-10-14 12:33:57 +00:00
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:
commit
686bfc4c42
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
//!
|
||||
|
|
8
library/core/src/fmt/fmt_trait_method_doc.md
Normal file
8
library/core/src/fmt/fmt_trait_method_doc.md
Normal 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.
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
],
|
||||
),
|
||||
];
|
||||
|
|
|
@ -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"]);
|
||||
|
|
2
src/tools/miri/.gitignore
vendored
2
src/tools/miri/.gitignore
vendored
|
@ -9,5 +9,5 @@ tex/*/out
|
|||
perf.data
|
||||
perf.data.old
|
||||
flamegraph.svg
|
||||
tests/extern-so/libtestlib.so
|
||||
tests/native-lib/libtestlib.so
|
||||
.auto-*
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1 +1 @@
|
|||
d568423a7a4ddb4b49323d96078a22f94df55fbd
|
||||
ef15976387ad9c1cdceaabf469e0cf35f5852f6d
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: _,
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)?;
|
||||
|
|
|
@ -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;
|
||||
|
|
242
src/tools/miri/src/shims/native_lib.rs
Normal file
242
src/tools/miri/src/shims/native_lib.rs
Normal 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),
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)?;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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"]:
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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" ] }
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
@ -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;
|
|
@ -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.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
//@ignore-target-windows: No libc on Windows
|
||||
//@ignore-target-windows: No pthreads on Windows
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
|
@ -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
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
//@ignore-target-windows: No libc on Windows
|
||||
//@ignore-target-windows: No pthreads on Windows
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
|
@ -1,4 +1,4 @@
|
|||
//@ignore-target-windows: No libc on Windows
|
||||
//@ignore-target-windows: No pthreads on Windows
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
|
@ -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;
|
|
@ -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
|
||||
|
|
@ -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);
|
|
@ -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);
|
|
@ -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
|
||||
|
|
@ -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);
|
|
@ -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;
|
|
@ -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);
|
|
@ -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;
|
|
@ -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);
|
|
@ -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;
|
|
@ -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);
|
|
@ -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;
|
|
@ -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;
|
|
@ -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)
|
|
@ -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];
|
|
@ -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() {
|
|
@ -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
Loading…
Reference in a new issue