diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index 8b0916f511117..2e49131982297 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -1,11 +1,7 @@ name: CI on: - push: - # Run in PRs and for bors, but not on master. - branches: - - 'auto' - - 'try' + merge_group: pull_request: branches: - 'master' @@ -38,7 +34,7 @@ jobs: # The `style` job only runs on Linux; this makes sure the Windows-host-specific # code is also covered by clippy. - name: Check clippy - if: matrix.os == 'windows-latest' + if: ${{ matrix.os == 'windows-latest' }} run: ./miri clippy -- -D warnings - name: Test Miri @@ -62,27 +58,25 @@ jobs: - name: rustdoc run: RUSTDOCFLAGS="-Dwarnings" ./miri doc --document-private-items - # These jobs doesn't actually test anything, but they're only used to tell - # bors the build completed, as there is no practical way to detect when a - # workflow is successful listening to webhooks only. - # + # Summary job for the merge queue. # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! - end-success: - name: bors build finished - runs-on: ubuntu-latest + # And they should be added below in `cron-fail-notify` as well. + conclusion: needs: [build, style] - if: github.event.pusher.name == 'bors' && success() - steps: - - name: mark the job as a success - run: exit 0 - end-failure: - name: bors build finished + # We need to ensure this job does *not* get skipped if its dependencies fail, + # because a skipped job is considered a success by GitHub. So we have to + # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run + # when the workflow is canceled manually. + if: ${{ !cancelled() }} runs-on: ubuntu-latest - needs: [build, style] - if: github.event.pusher.name == 'bors' && (failure() || cancelled()) steps: - - name: mark the job as a failure - run: exit 1 + # Manually check the status of all dependencies. `if: failure()` does not work. + - name: Conclusion + run: | + # Print the dependent jobs to see them in the CI log + jq -C <<< '${{ toJson(needs) }}' + # Check if all jobs that we depend on (in the needs array) were successful. + jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' cron-fail-notify: name: cronjob failure notification @@ -93,7 +87,7 @@ jobs: # ... and create a PR. pull-requests: write needs: [build, style] - if: github.event_name == 'schedule' && failure() + if: ${{ github.event_name == 'schedule' && failure() }} steps: # Send a Zulip notification - name: Install zulip-send @@ -145,7 +139,7 @@ jobs: git push -u origin $BRANCH - name: Create Pull Request run: | - PR=$(gh pr create -B master --title 'Automatic Rustup' --body '') + PR=$(gh pr create -B master --title 'Automatic Rustup' --body 'Please close and re-open this PR to trigger CI, then enable auto-merge.') ~/.local/bin/zulip-send --user $ZULIP_BOT_EMAIL --api-key $ZULIP_API_TOKEN --site https://rust-lang.zulipchat.com \ --stream miri --subject "Miri Build Failure ($(date -u +%Y-%m))" \ --message "A PR doing a rustc-pull [has been automatically created]($PR) for your convenience." diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index ad1b2f4d0c3d1..4e7cbc50ca03b 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -154,7 +154,7 @@ case $HOST_TARGET in TEST_TARGET=i686-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random threadname pthread fs libc-pipe TEST_TARGET=x86_64-unknown-illumos run_tests_minimal $BASIC $UNIX time hashmap random thread sync available-parallelism tls libc-pipe TEST_TARGET=x86_64-pc-solaris run_tests_minimal $BASIC $UNIX time hashmap random thread sync available-parallelism tls libc-pipe - TEST_TARGET=aarch64-linux-android run_tests_minimal $BASIC $UNIX time hashmap pthread --skip threadname + TEST_TARGET=aarch64-linux-android run_tests_minimal $BASIC $UNIX time hashmap threadname pthread TEST_TARGET=wasm32-wasip2 run_tests_minimal $BASIC wasm TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std empty_main wasm # this target doesn't really have std TEST_TARGET=thumbv7em-none-eabihf run_tests_minimal no_std diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 8b9e7efdff908..133edd3191d5b 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -17a19e684cdf3ca088af8b4da6a6209d128913f4 +814df6e50eaf89b90793e7d9618bb60f1f18377a diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs index 1684abeec6bfb..5624c4c479ef6 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs @@ -424,7 +424,11 @@ impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> { } #[inline(never)] // This is only called on fatal code paths - pub(super) fn protector_error(&self, item: &Item, kind: ProtectorKind) -> InterpErrorKind<'tcx> { + pub(super) fn protector_error( + &self, + item: &Item, + kind: ProtectorKind, + ) -> InterpErrorKind<'tcx> { let protected = match kind { ProtectorKind::WeakProtector => "weakly protected", ProtectorKind::StrongProtector => "strongly protected", diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs index 09ec2cb46b05e..776d2561b430f 100644 --- a/src/tools/miri/src/intrinsics/mod.rs +++ b/src/tools/miri/src/intrinsics/mod.rs @@ -145,6 +145,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(Scalar::from_bool(branch), dest)?; } + "floorf16" | "ceilf16" | "truncf16" | "roundf16" | "rintf16" => { + let [f] = check_arg_count(args)?; + let f = this.read_scalar(f)?.to_f16()?; + let mode = match intrinsic_name { + "floorf16" => Round::TowardNegative, + "ceilf16" => Round::TowardPositive, + "truncf16" => Round::TowardZero, + "roundf16" => Round::NearestTiesToAway, + "rintf16" => Round::NearestTiesToEven, + _ => bug!(), + }; + let res = f.round_to_integral(mode).value; + let res = this.adjust_nan(res, &[f]); + this.write_scalar(res, dest)?; + } "floorf32" | "ceilf32" | "truncf32" | "roundf32" | "rintf32" => { let [f] = check_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; @@ -175,6 +190,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } + "floorf128" | "ceilf128" | "truncf128" | "roundf128" | "rintf128" => { + let [f] = check_arg_count(args)?; + let f = this.read_scalar(f)?.to_f128()?; + let mode = match intrinsic_name { + "floorf128" => Round::TowardNegative, + "ceilf128" => Round::TowardPositive, + "truncf128" => Round::TowardZero, + "roundf128" => Round::NearestTiesToAway, + "rintf128" => Round::NearestTiesToEven, + _ => bug!(), + }; + let res = f.round_to_integral(mode).value; + let res = this.adjust_nan(res, &[f]); + this.write_scalar(res, dest)?; + } #[rustfmt::skip] | "sinf32" diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs index 0799b93dbb0e0..f15b83a054fdb 100644 --- a/src/tools/miri/src/intrinsics/simd.rs +++ b/src/tools/miri/src/intrinsics/simd.rs @@ -1,7 +1,8 @@ use either::Either; use rustc_apfloat::{Float, Round}; +use rustc_middle::ty::FloatTy; use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::{mir, ty, ty::FloatTy}; +use rustc_middle::{mir, ty}; use rustc_span::{Symbol, sym}; use rustc_target::abi::{Endian, HasDataLayout}; @@ -630,12 +631,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let (right, right_len) = this.project_to_simd(right)?; let (dest, dest_len) = this.project_to_simd(dest)?; - let index = generic_args[2] - .expect_const() - .try_to_valtree() - .unwrap() - .0 - .unwrap_branch(); + let index = + generic_args[2].expect_const().try_to_valtree().unwrap().0.unwrap_branch(); let index_len = index.len(); assert_eq!(left_len, right_len); diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 660f2e493bc0f..938d1ca319e0f 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -147,7 +147,7 @@ pub use crate::range_map::RangeMap; pub use crate::shims::EmulateItemResult; pub use crate::shims::env::{EnvVars, EvalContextExt as _}; pub use crate::shims::foreign_items::{DynSym, EvalContextExt as _}; -pub use crate::shims::io_error::{EvalContextExt as _, LibcError}; +pub use crate::shims::io_error::{EvalContextExt as _, IoError, LibcError}; pub use crate::shims::os_str::EvalContextExt as _; pub use crate::shims::panic::{CatchUnwindData, EvalContextExt as _}; pub use crate::shims::time::EvalContextExt as _; diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index f6f91e589697a..12f8facfd023e 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -447,8 +447,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } else { // If this does not fit in an isize, return null and, on Unix, set errno. if this.target_os_is_unix() { - let einval = this.eval_libc("ENOMEM"); - this.set_last_error(einval)?; + this.set_last_error(LibcError("ENOMEM"))?; } this.write_null(dest)?; } @@ -464,8 +463,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } else { // On size overflow, return null and, on Unix, set errno. if this.target_os_is_unix() { - let einval = this.eval_libc("ENOMEM"); - this.set_last_error(einval)?; + this.set_last_error(LibcError("ENOMEM"))?; } this.write_null(dest)?; } @@ -486,8 +484,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } else { // If this does not fit in an isize, return null and, on Unix, set errno. if this.target_os_is_unix() { - let einval = this.eval_libc("ENOMEM"); - this.set_last_error(einval)?; + this.set_last_error(LibcError("ENOMEM"))?; } this.write_null(dest)?; } diff --git a/src/tools/miri/src/shims/io_error.rs b/src/tools/miri/src/shims/io_error.rs index 38aa181cb4f05..04491f0542bd8 100644 --- a/src/tools/miri/src/shims/io_error.rs +++ b/src/tools/miri/src/shims/io_error.rs @@ -141,6 +141,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_i32(-1)) } + /// Sets the last OS error and return `-1` as a `i64`-typed Scalar + fn set_last_error_and_return_i64( + &mut self, + err: impl Into, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + this.set_last_error(err)?; + interp_ok(Scalar::from_i64(-1)) + } + /// Gets the last error variable. fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 12c7679608ded..6436823b0fdfe 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -81,9 +81,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } else if relative_clocks.contains(&clk_id) { this.machine.clock.now().duration_since(this.machine.clock.epoch()) } else { - let einval = this.eval_libc("EINVAL"); - this.set_last_error(einval)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EINVAL")); }; let tv_sec = duration.as_secs(); @@ -109,9 +107,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Using tz is obsolete and should always be null let tz = this.read_pointer(tz_op)?; if !this.ptr_is_null(tz)? { - let einval = this.eval_libc("EINVAL"); - this.set_last_error(einval)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EINVAL")); } let duration = system_time_to_duration(&SystemTime::now())?; @@ -323,9 +319,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let duration = match this.read_timespec(&req)? { Some(duration) => duration, None => { - let einval = this.eval_libc("EINVAL"); - this.set_last_error(einval)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EINVAL")); } }; diff --git a/src/tools/miri/src/shims/unix/android/foreign_items.rs b/src/tools/miri/src/shims/unix/android/foreign_items.rs index 583a1f6500963..b6f04951fc7e7 100644 --- a/src/tools/miri/src/shims/unix/android/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/android/foreign_items.rs @@ -1,6 +1,7 @@ use rustc_span::Symbol; use rustc_target::spec::abi::Abi; +use crate::shims::unix::android::thread::prctl; use crate::*; pub fn is_dyn_sym(_name: &str) -> bool { @@ -25,6 +26,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } + // Threading + "prctl" => prctl(this, link_name, abi, args, dest)?, + _ => return interp_ok(EmulateItemResult::NotSupported), } interp_ok(EmulateItemResult::NeedsReturn) diff --git a/src/tools/miri/src/shims/unix/android/mod.rs b/src/tools/miri/src/shims/unix/android/mod.rs index 09c6507b24f84..1f2a74bac5935 100644 --- a/src/tools/miri/src/shims/unix/android/mod.rs +++ b/src/tools/miri/src/shims/unix/android/mod.rs @@ -1 +1,2 @@ pub mod foreign_items; +pub mod thread; diff --git a/src/tools/miri/src/shims/unix/android/thread.rs b/src/tools/miri/src/shims/unix/android/thread.rs new file mode 100644 index 0000000000000..6f5f0f74a2295 --- /dev/null +++ b/src/tools/miri/src/shims/unix/android/thread.rs @@ -0,0 +1,57 @@ +use rustc_span::Symbol; +use rustc_target::abi::Size; +use rustc_target::spec::abi::Abi; + +use crate::helpers::check_min_arg_count; +use crate::shims::unix::thread::EvalContextExt as _; +use crate::*; + +const TASK_COMM_LEN: usize = 16; + +pub fn prctl<'tcx>( + this: &mut MiriInterpCx<'tcx>, + link_name: Symbol, + abi: Abi, + args: &[OpTy<'tcx>], + dest: &MPlaceTy<'tcx>, +) -> InterpResult<'tcx> { + // We do not use `check_shim` here because `prctl` is variadic. The argument + // count is checked bellow. + this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?; + + // FIXME: Use constants once https://github.com/rust-lang/libc/pull/3941 backported to the 0.2 branch. + let pr_set_name = 15; + let pr_get_name = 16; + + let [op] = check_min_arg_count("prctl", args)?; + let res = match this.read_scalar(op)?.to_i32()? { + op if op == pr_set_name => { + let [_, name] = check_min_arg_count("prctl(PR_SET_NAME, ...)", args)?; + let name = this.read_scalar(name)?; + let thread = this.pthread_self()?; + // The Linux kernel silently truncates long names. + // https://www.man7.org/linux/man-pages/man2/PR_SET_NAME.2const.html + let res = + this.pthread_setname_np(thread, name, TASK_COMM_LEN, /* truncate */ true)?; + assert!(res); + Scalar::from_u32(0) + } + op if op == pr_get_name => { + let [_, name] = check_min_arg_count("prctl(PR_GET_NAME, ...)", args)?; + let name = this.read_scalar(name)?; + let thread = this.pthread_self()?; + let len = Scalar::from_target_usize(TASK_COMM_LEN as u64, this); + this.check_ptr_access( + name.to_pointer(this)?, + Size::from_bytes(TASK_COMM_LEN), + CheckInAllocMsg::MemoryAccessTest, + )?; + let res = this.pthread_getname_np(thread, name, len, /* truncate*/ false)?; + assert!(res); + Scalar::from_u32(0) + } + op => throw_unsup_format!("Miri does not support `prctl` syscall with op={}", op), + }; + this.write_scalar(res, dest)?; + interp_ok(()) +} diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs index e3914640037e3..f3db56695fc2c 100644 --- a/src/tools/miri/src/shims/unix/fd.rs +++ b/src/tools/miri/src/shims/unix/fd.rs @@ -423,7 +423,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); let Some(fd) = this.machine.fds.get(old_fd_num) else { - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); }; interp_ok(Scalar::from_i32(this.machine.fds.insert(fd))) } @@ -432,7 +432,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); let Some(fd) = this.machine.fds.get(old_fd_num) else { - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); }; if new_fd_num != old_fd_num { // Close new_fd if it is previously opened. @@ -448,7 +448,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn flock(&mut self, fd_num: i32, op: i32) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); let Some(fd) = this.machine.fds.get(fd_num) else { - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); }; // We need to check that there aren't unsupported options in `op`. @@ -498,11 +498,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // `FD_CLOEXEC` value without checking if the flag is set for the file because `std` // always sets this flag when opening a file. However we still need to check that the // file itself is open. - interp_ok(Scalar::from_i32(if this.machine.fds.is_fd_num(fd_num) { - this.eval_libc_i32("FD_CLOEXEC") + if !this.machine.fds.is_fd_num(fd_num) { + this.set_last_error_and_return_i32(LibcError("EBADF")) } else { - this.fd_not_found()? - })) + interp_ok(this.eval_libc("FD_CLOEXEC")) + } } cmd if cmd == f_dupfd || cmd == f_dupfd_cloexec => { // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part @@ -521,7 +521,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if let Some(fd) = this.machine.fds.get(fd_num) { interp_ok(Scalar::from_i32(this.machine.fds.insert_with_min_num(fd, start))) } else { - interp_ok(Scalar::from_i32(this.fd_not_found()?)) + this.set_last_error_and_return_i32(LibcError("EBADF")) } } cmd if this.tcx.sess.target.os == "macos" @@ -547,7 +547,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let fd_num = this.read_scalar(fd_op)?.to_i32()?; let Some(fd) = this.machine.fds.remove(fd_num) else { - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); }; let result = fd.close(this.machine.communicate(), this)?; // return `0` if close is successful @@ -555,17 +555,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) } - /// Function used when a file descriptor does not exist. It returns `Ok(-1)`and sets - /// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses - /// `T: From` instead of `i32` directly because some fs functions return different integer - /// types (like `read`, that returns an `i64`). - fn fd_not_found>(&mut self) -> InterpResult<'tcx, T> { - let this = self.eval_context_mut(); - let ebadf = this.eval_libc("EBADF"); - this.set_last_error(ebadf)?; - interp_ok((-1).into()) - } - /// Read data from `fd` into buffer specified by `buf` and `count`. /// /// If `offset` is `None`, reads data from current cursor position associated with `fd` @@ -599,9 +588,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // We temporarily dup the FD to be able to retain mutable access to `this`. let Some(fd) = this.machine.fds.get(fd_num) else { trace!("read: FD not found"); - let res: i32 = this.fd_not_found()?; - this.write_int(res, dest)?; - return interp_ok(()); + return this.set_last_error_and_return(LibcError("EBADF"), dest); }; trace!("read: FD mapped to {fd:?}"); @@ -646,9 +633,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // We temporarily dup the FD to be able to retain mutable access to `this`. let Some(fd) = this.machine.fds.get(fd_num) else { - let res: i32 = this.fd_not_found()?; - this.write_int(res, dest)?; - return interp_ok(()); + return this.set_last_error_and_return(LibcError("EBADF"), dest); }; match offset { diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 7ba9898192018..355c93c444ccf 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -362,8 +362,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // FreeBSD: https://man.freebsd.org/cgi/man.cgi?query=reallocarray match this.compute_size_in_bytes(Size::from_bytes(size), nmemb) { None => { - let enmem = this.eval_libc("ENOMEM"); - this.set_last_error(enmem)?; + this.set_last_error(LibcError("ENOMEM"))?; this.write_null(dest)?; } Some(len) => { @@ -653,13 +652,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let chunk_size = CpuAffinityMask::chunk_size(this); if this.ptr_is_null(mask)? { - let efault = this.eval_libc("EFAULT"); - this.set_last_error(efault)?; - this.write_int(-1, dest)?; + this.set_last_error_and_return(LibcError("EFAULT"), dest)?; } else if cpusetsize == 0 || cpusetsize.checked_rem(chunk_size).unwrap() != 0 { // we only copy whole chunks of size_of::() - this.set_last_error(LibcError("EINVAL"))?; - this.write_int(-1, dest)?; + this.set_last_error_and_return(LibcError("EINVAL"), dest)?; } else if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&thread_id) { let cpuset = cpuset.clone(); // we only copy whole chunks of size_of::() @@ -668,9 +664,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } else { // The thread whose ID is pid could not be found - let esrch = this.eval_libc("ESRCH"); - this.set_last_error(esrch)?; - this.write_int(-1, dest)?; + this.set_last_error_and_return(LibcError("ESRCH"), dest)?; } } "sched_setaffinity" => { @@ -695,9 +689,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; if this.ptr_is_null(mask)? { - let efault = this.eval_libc("EFAULT"); - this.set_last_error(efault)?; - this.write_int(-1, dest)?; + this.set_last_error_and_return(LibcError("EFAULT"), dest)?; } else { // NOTE: cpusetsize might be smaller than `CpuAffinityMask::CPU_MASK_BYTES`. // Any unspecified bytes are treated as zero here (none of the CPUs are configured). @@ -713,8 +705,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } None => { // The intersection between the mask and the available CPUs was empty. - this.set_last_error(LibcError("EINVAL"))?; - this.write_int(-1, dest)?; + this.set_last_error_and_return(LibcError("EINVAL"), dest)?; } } } @@ -770,9 +761,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // macOS: https://keith.github.io/xcode-man-pages/getentropy.2.html // Solaris/Illumos: https://illumos.org/man/3C/getentropy if bufsize > 256 { - let err = this.eval_libc("EIO"); - this.set_last_error(err)?; - this.write_int(-1, dest)?; + this.set_last_error_and_return(LibcError("EIO"), dest)?; } else { this.gen_random(buf, bufsize)?; this.write_null(dest)?; diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs index 5204e57705a73..71953aca98905 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -29,6 +29,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.read_scalar(thread)?, this.read_scalar(name)?, max_len, + /* truncate */ false, )?; } "pthread_get_name_np" => { diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 4b3ae8e0520b3..f7436d7f089b1 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -149,6 +149,7 @@ impl FileDescription for FileHandle { // to handle possible errors correctly. let result = self.file.sync_all(); // Now we actually close the file and return the result. + drop(*self); interp_ok(result) } else { // We drop the file, this closes it but ignores any errors @@ -157,6 +158,7 @@ impl FileDescription for FileHandle { // `/dev/urandom` which are read-only. Check // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439 // for a deeper discussion. + drop(*self); interp_ok(Ok(())) } } @@ -229,6 +231,8 @@ impl FileDescription for FileHandle { TRUE => Ok(()), FALSE => { let mut err = io::Error::last_os_error(); + // This only runs on Windows hosts so we can use `raw_os_error`. + // We have to be careful not to forward that error code to target code. let code: u32 = err.raw_os_error().unwrap().try_into().unwrap(); if matches!(code, ERROR_IO_PENDING | ERROR_LOCK_VIOLATION) { if lock_nb { @@ -337,15 +341,10 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()), } } - Err(e) => - match e.raw_os_error() { - Some(error) => interp_ok(error), - None => - throw_unsup_format!( - "the error {} couldn't be converted to a return value", - e - ), - }, + Err(_) => { + // Fallback on error + interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()) + } } } } @@ -528,8 +527,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let o_tmpfile = this.eval_libc_i32("O_TMPFILE"); if flag & o_tmpfile == o_tmpfile { // if the flag contains `O_TMPFILE` then we return a graceful error - this.set_last_error(LibcError("EOPNOTSUPP"))?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP")); } } @@ -548,9 +546,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // O_NOFOLLOW only fails when the trailing component is a symlink; // the entire rest of the path can still contain symlinks. if path.is_symlink() { - let eloop = this.eval_libc("ELOOP"); - this.set_last_error(eloop)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("ELOOP")); } } mirror |= o_nofollow; @@ -565,8 +561,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // 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(ErrorKind::PermissionDenied)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); } let fd = options @@ -584,8 +579,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let seek_from = if whence == this.eval_libc_i32("SEEK_SET") { if offset < 0 { // Negative offsets return `EINVAL`. - this.set_last_error(LibcError("EINVAL"))?; - return interp_ok(Scalar::from_i64(-1)); + return this.set_last_error_and_return_i64(LibcError("EINVAL")); } else { SeekFrom::Start(u64::try_from(offset).unwrap()) } @@ -594,14 +588,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } else if whence == this.eval_libc_i32("SEEK_END") { SeekFrom::End(i64::try_from(offset).unwrap()) } else { - this.set_last_error(LibcError("EINVAL"))?; - return interp_ok(Scalar::from_i64(-1)); + return this.set_last_error_and_return_i64(LibcError("EINVAL")); }; let communicate = this.machine.communicate(); let Some(fd) = this.machine.fds.get(fd_num) else { - return interp_ok(Scalar::from_i64(this.fd_not_found()?)); + return this.set_last_error_and_return_i64(LibcError("EBADF")); }; let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap()); drop(fd); @@ -618,8 +611,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // 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(ErrorKind::PermissionDenied)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); } let result = remove_file(path).map(|_| 0); @@ -649,8 +641,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // 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(ErrorKind::PermissionDenied)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); } let result = create_link(&target, &linkpath).map(|_| 0); @@ -674,15 +665,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`stat`", reject_with)?; - let eacc = this.eval_libc("EACCES"); - this.set_last_error(eacc)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EACCES")); } // `stat` always follows symlinks. let metadata = match FileMetadata::from_path(this, &path, true)? { - Some(metadata) => metadata, - None => return interp_ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno + Ok(metadata) => metadata, + Err(err) => return this.set_last_error_and_return_i32(err), }; interp_ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?)) @@ -706,14 +695,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`lstat`", reject_with)?; - let eacc = this.eval_libc("EACCES"); - this.set_last_error(eacc)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EACCES")); } let metadata = match FileMetadata::from_path(this, &path, false)? { - Some(metadata) => metadata, - None => return interp_ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno + Ok(metadata) => metadata, + Err(err) => return this.set_last_error_and_return_i32(err), }; interp_ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?)) @@ -736,12 +723,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`fstat`", reject_with)?; // Set error code as "EBADF" (bad fd) - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); } let metadata = match FileMetadata::from_fd_num(this, fd)? { - Some(metadata) => metadata, - None => return interp_ok(Scalar::from_i32(-1)), + Ok(metadata) => metadata, + Err(err) => return this.set_last_error_and_return_i32(err), }; interp_ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?)) } @@ -766,9 +753,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`. if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? { - let efault = this.eval_libc("EFAULT"); - this.set_last_error(efault)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EFAULT")); } let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?; @@ -801,16 +786,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") { // since `path` is provided, either absolute or // relative to CWD, `EACCES` is the most relevant. - this.eval_libc("EACCES") + LibcError("EACCES") } else { // `dirfd` is set to target file, and `path` is empty // (or we would have hit the `throw_unsup_format` // above). `EACCES` would violate the spec. assert!(empty_path_flag); - this.eval_libc("EBADF") + LibcError("EBADF") }; - this.set_last_error(ecode)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(ecode); } // the `_mask_op` parameter specifies the file information that the caller requested. @@ -831,8 +815,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { FileMetadata::from_path(this, &path, follow_symlink)? }; let metadata = match metadata { - Some(metadata) => metadata, - None => return interp_ok(Scalar::from_i32(-1)), + Ok(metadata) => metadata, + Err(err) => return this.set_last_error_and_return_i32(err), }; // The `mode` field specifies the type of the file and the permissions over the file for @@ -939,9 +923,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let newpath_ptr = this.read_pointer(newpath_op)?; if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? { - let efault = this.eval_libc("EFAULT"); - this.set_last_error(efault)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EFAULT")); } let oldpath = this.read_path_from_c_str(oldpath_ptr)?; @@ -950,8 +932,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // 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(ErrorKind::PermissionDenied)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); } let result = rename(oldpath, newpath).map(|_| 0); @@ -974,8 +955,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // 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(ErrorKind::PermissionDenied)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); } #[cfg_attr(not(unix), allow(unused_mut))] @@ -1002,8 +982,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // 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(ErrorKind::PermissionDenied)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied); } let result = remove_dir(path).map(|_| 0i32); @@ -1019,8 +998,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`opendir`", reject_with)?; - let eacc = this.eval_libc("EACCES"); - this.set_last_error(eacc)?; + this.set_last_error(LibcError("EACCES"))?; return interp_ok(Scalar::null_ptr(this)); } @@ -1052,8 +1030,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`readdir`", reject_with)?; - let eacc = this.eval_libc("EBADF"); - this.set_last_error(eacc)?; + this.set_last_error(LibcError("EBADF"))?; return interp_ok(Scalar::null_ptr(this)); } @@ -1152,14 +1129,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`readdir_r`", reject_with)?; - // Set error code as "EBADF" (bad fd) - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + // Return error code, do *not* set `errno`. + return interp_ok(this.eval_libc("EBADF")); } let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| { err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir") })?; - interp_ok(Scalar::from_i32(match open_dir.read_dir.next() { + interp_ok(match open_dir.read_dir.next() { Some(Ok(dir_entry)) => { // Write into entry, write pointer to result, return 0 on success. // The name is written with write_os_str_to_c_str, while the rest of the @@ -1237,25 +1214,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result_place = this.deref_pointer(result_op)?; this.write_scalar(this.read_scalar(entry_op)?, &result_place)?; - 0 + Scalar::from_i32(0) } None => { // end of stream: return 0, assign *result=NULL this.write_null(&this.deref_pointer(result_op)?)?; - 0 + Scalar::from_i32(0) } - Some(Err(e)) => - match e.raw_os_error() { - // return positive error number on error - Some(error) => error, - None => { - throw_unsup_format!( - "the error {} couldn't be converted to a return value", - e - ) - } - }, - })) + Some(Err(e)) => { + // return positive error number on error (do *not* set last error) + this.io_error_to_errnum(e)? + } + }) } fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { @@ -1264,20 +1234,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let dirp = this.read_target_usize(dirp_op)?; // Reject if isolation is enabled. - interp_ok(Scalar::from_i32( - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`closedir`", reject_with)?; - this.fd_not_found()? - } else if let Some(open_dir) = this.machine.dirs.streams.remove(&dirp) { - if let Some(entry) = open_dir.entry { - this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?; - } - drop(open_dir); - 0 - } else { - this.fd_not_found()? - }, - )) + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`closedir`", reject_with)?; + return this.set_last_error_and_return_i32(LibcError("EBADF")); + } + + let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else { + return this.set_last_error_and_return_i32(LibcError("EBADF")); + }; + if let Some(entry) = open_dir.entry.take() { + this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?; + } + // We drop the `open_dir`, which will close the host dir handle. + drop(open_dir); + + interp_ok(Scalar::from_i32(0)) } fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> { @@ -1287,11 +1258,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`ftruncate64`", reject_with)?; // Set error code as "EBADF" (bad fd) - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); } let Some(fd) = this.machine.fds.get(fd_num) else { - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); }; // FIXME: Support ftruncate64 for all FDs @@ -1307,14 +1278,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_i32(result)) } else { drop(fd); - this.set_last_error(LibcError("EINVAL"))?; - interp_ok(Scalar::from_i32(-1)) + this.set_last_error_and_return_i32(LibcError("EINVAL")) } } else { drop(fd); // The file is not writable - this.set_last_error(LibcError("EINVAL"))?; - interp_ok(Scalar::from_i32(-1)) + this.set_last_error_and_return_i32(LibcError("EINVAL")) } } @@ -1332,7 +1301,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`fsync`", reject_with)?; // Set error code as "EBADF" (bad fd) - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); } self.ffullsync_fd(fd) @@ -1341,7 +1310,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); let Some(fd) = this.machine.fds.get(fd_num) else { - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); }; // Only regular files support synchronization. let FileHandle { file, writable } = fd.downcast::().ok_or_else(|| { @@ -1361,11 +1330,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`fdatasync`", reject_with)?; // Set error code as "EBADF" (bad fd) - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); } let Some(fd) = this.machine.fds.get(fd) else { - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); }; // Only regular files support synchronization. let FileHandle { file, writable } = fd.downcast::().ok_or_else(|| { @@ -1391,26 +1360,24 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let flags = this.read_scalar(flags_op)?.to_i32()?; if offset < 0 || nbytes < 0 { - this.set_last_error(LibcError("EINVAL"))?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EINVAL")); } let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE") | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE") | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER"); if flags & allowed_flags != flags { - this.set_last_error(LibcError("EINVAL"))?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EINVAL")); } // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`sync_file_range`", reject_with)?; // Set error code as "EBADF" (bad fd) - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); } let Some(fd) = this.machine.fds.get(fd) else { - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); }; // Only regular files support synchronization. let FileHandle { file, writable } = fd.downcast::().ok_or_else(|| { @@ -1436,8 +1403,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`readlink`", reject_with)?; - let eacc = this.eval_libc("EACCES"); - this.set_last_error(eacc)?; + this.set_last_error(LibcError("EACCES"))?; return interp_ok(-1); } @@ -1475,11 +1441,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if fd.is_tty(this.machine.communicate()) { return interp_ok(Scalar::from_i32(1)); } else { - this.eval_libc("ENOTTY") + LibcError("ENOTTY") } } else { // FD does not exist - this.eval_libc("EBADF") + LibcError("EBADF") }; this.set_last_error(error)?; interp_ok(Scalar::from_i32(0)) @@ -1499,8 +1465,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`realpath`", reject_with)?; - let eacc = this.eval_libc("EACCES"); - this.set_last_error(eacc)?; + this.set_last_error(LibcError("EACCES"))?; return interp_ok(Scalar::from_target_usize(0, this)); } @@ -1530,8 +1495,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Note that we do not explicitly handle `FILENAME_MAX` // (different from `PATH_MAX` above) as it is Linux-specific and // seems like a bit of a mess anyway: . - let enametoolong = this.eval_libc("ENAMETOOLONG"); - this.set_last_error(enametoolong)?; + this.set_last_error(LibcError("ENAMETOOLONG"))?; return interp_ok(Scalar::from_target_usize(0, this)); } processed_ptr @@ -1574,9 +1538,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`mkstemp`", reject_with)?; - let eacc = this.eval_libc("EACCES"); - this.set_last_error(eacc)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EACCES")); } // Get the bytes of the suffix we expect in _target_ encoding. @@ -1592,8 +1554,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // If we don't find the suffix, it is an error. if last_six_char_bytes != suffix_bytes { - this.set_last_error(LibcError("EINVAL"))?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EINVAL")); } // At this point we know we have 6 ASCII 'X' characters as a suffix. @@ -1658,17 +1619,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { _ => { // "On error, -1 is returned, and errno is set to // indicate the error" - this.set_last_error(e)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(e); } }, } } // We ran out of attempts to create the file, return an error. - let eexist = this.eval_libc("EEXIST"); - this.set_last_error(eexist)?; - interp_ok(Scalar::from_i32(-1)) + this.set_last_error_and_return_i32(LibcError("EEXIST")) } } @@ -1702,7 +1660,7 @@ impl FileMetadata { ecx: &mut MiriInterpCx<'tcx>, path: &Path, follow_symlink: bool, - ) -> InterpResult<'tcx, Option> { + ) -> InterpResult<'tcx, Result> { let metadata = if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) }; @@ -1712,9 +1670,9 @@ impl FileMetadata { fn from_fd_num<'tcx>( ecx: &mut MiriInterpCx<'tcx>, fd_num: i32, - ) -> InterpResult<'tcx, Option> { + ) -> InterpResult<'tcx, Result> { let Some(fd) = ecx.machine.fds.get(fd_num) else { - return ecx.fd_not_found().map(|_: i32| None); + return interp_ok(Err(LibcError("EBADF"))); }; let file = &fd @@ -1734,12 +1692,11 @@ impl FileMetadata { fn from_meta<'tcx>( ecx: &mut MiriInterpCx<'tcx>, metadata: Result, - ) -> InterpResult<'tcx, Option> { + ) -> InterpResult<'tcx, Result> { let metadata = match metadata { Ok(metadata) => metadata, Err(e) => { - ecx.set_last_error(e)?; - return interp_ok(None); + return interp_ok(Err(e.into())); } }; @@ -1762,6 +1719,6 @@ impl FileMetadata { let modified = extract_sec_and_nsec(metadata.modified())?; // FIXME: Provide more fields using platform specific methods. - interp_ok(Some(FileMetadata { mode, size, created, accessed, modified })) + interp_ok(Ok(FileMetadata { mode, size, created, accessed, modified })) } } diff --git a/src/tools/miri/src/shims/unix/linux/epoll.rs b/src/tools/miri/src/shims/unix/linux/epoll.rs index cafc7161d2624..de108665e9f8c 100644 --- a/src/tools/miri/src/shims/unix/linux/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux/epoll.rs @@ -256,23 +256,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let epollhup = this.eval_libc_u32("EPOLLHUP"); let epollerr = this.eval_libc_u32("EPOLLERR"); - // Fail on unsupported operations. - if op & epoll_ctl_add != epoll_ctl_add - && op & epoll_ctl_mod != epoll_ctl_mod - && op & epoll_ctl_del != epoll_ctl_del - { - throw_unsup_format!("epoll_ctl: encountered unknown unsupported operation {:#x}", op); - } - // Throw EINVAL if epfd and fd have the same value. if epfd_value == fd { - this.set_last_error(LibcError("EINVAL"))?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EINVAL")); } // Check if epfd is a valid epoll file descriptor. let Some(epfd) = this.machine.fds.get(epfd_value) else { - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); }; let epoll_file_description = epfd .downcast::() @@ -282,7 +273,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let ready_list = &epoll_file_description.ready_list; let Some(fd_ref) = this.machine.fds.get(fd) else { - return interp_ok(Scalar::from_i32(this.fd_not_found()?)); + return this.set_last_error_and_return_i32(LibcError("EBADF")); }; let id = fd_ref.get_id(); @@ -332,15 +323,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Check the existence of fd in the interest list. if op == epoll_ctl_add { if interest_list.contains_key(&epoll_key) { - let eexist = this.eval_libc("EEXIST"); - this.set_last_error(eexist)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EEXIST")); } } else { if !interest_list.contains_key(&epoll_key) { - let enoent = this.eval_libc("ENOENT"); - this.set_last_error(enoent)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("ENOENT")); } } @@ -368,15 +355,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Notification will be returned for current epfd if there is event in the file // descriptor we registered. check_and_update_one_event_interest(&fd_ref, interest, id, this)?; - return interp_ok(Scalar::from_i32(0)); + interp_ok(Scalar::from_i32(0)) } else if op == epoll_ctl_del { let epoll_key = (id, fd); // Remove epoll_event_interest from interest_list. let Some(epoll_interest) = interest_list.remove(&epoll_key) else { - let enoent = this.eval_libc("ENOENT"); - this.set_last_error(enoent)?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("ENOENT")); }; // All related Weak will fail to upgrade after the drop. drop(epoll_interest); @@ -394,9 +379,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { .unwrap() .retain(|event| event.upgrade().is_some()); - return interp_ok(Scalar::from_i32(0)); + interp_ok(Scalar::from_i32(0)) + } else { + throw_unsup_format!("unsupported epoll_ctl operation: {op}"); } - interp_ok(Scalar::from_i32(-1)) } /// The `epoll_wait()` system call waits for events on the `Epoll` @@ -447,9 +433,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let timeout = this.read_scalar(timeout)?.to_i32()?; if epfd_value <= 0 || maxevents <= 0 { - this.set_last_error(LibcError("EINVAL"))?; - this.write_int(-1, dest)?; - return interp_ok(()); + return this.set_last_error_and_return(LibcError("EINVAL"), dest); } // This needs to come after the maxevents value check, or else maxevents.try_into().unwrap() @@ -460,9 +444,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; let Some(epfd) = this.machine.fds.get(epfd_value) else { - let result_value: i32 = this.fd_not_found()?; - this.write_int(result_value, dest)?; - return interp_ok(()); + return this.set_last_error_and_return(LibcError("EBADF"), dest); }; // Create a weak ref of epfd and pass it to callback so we will make sure that epfd // is not close after the thread unblocks. diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs index e73bde1ddb660..6616a9845c09b 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -84,6 +84,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.read_scalar(thread)?, this.read_scalar(name)?, TASK_COMM_LEN, + /* truncate */ false, )?; let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") }; this.write_scalar(res, dest)?; diff --git a/src/tools/miri/src/shims/unix/linux/mem.rs b/src/tools/miri/src/shims/unix/linux/mem.rs index 4f2e17d50c8b4..d5f9669b36095 100644 --- a/src/tools/miri/src/shims/unix/linux/mem.rs +++ b/src/tools/miri/src/shims/unix/linux/mem.rs @@ -24,7 +24,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // old_address must be a multiple of the page size #[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero if old_address.addr().bytes() % this.machine.page_size != 0 || new_size == 0 { - this.set_last_error(this.eval_libc("EINVAL"))?; + this.set_last_error(LibcError("EINVAL"))?; return interp_ok(this.eval_libc("MAP_FAILED")); } @@ -38,7 +38,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if flags & this.eval_libc_i32("MREMAP_MAYMOVE") == 0 { // We only support MREMAP_MAYMOVE, so not passing the flag is just a failure - this.set_last_error(this.eval_libc("EINVAL"))?; + this.set_last_error(LibcError("EINVAL"))?; return interp_ok(this.eval_libc("MAP_FAILED")); } diff --git a/src/tools/miri/src/shims/unix/linux/sync.rs b/src/tools/miri/src/shims/unix/linux/sync.rs index 941011bfac640..c258be78f7647 100644 --- a/src/tools/miri/src/shims/unix/linux/sync.rs +++ b/src/tools/miri/src/shims/unix/linux/sync.rs @@ -63,8 +63,7 @@ pub fn futex<'tcx>( }; if bitset == 0 { - this.set_last_error(LibcError("EINVAL"))?; - this.write_scalar(Scalar::from_target_isize(-1, this), dest)?; + this.set_last_error_and_return(LibcError("EINVAL"), dest)?; return interp_ok(()); } @@ -75,9 +74,7 @@ pub fn futex<'tcx>( let duration = match this.read_timespec(&timeout)? { Some(duration) => duration, None => { - this.set_last_error(LibcError("EINVAL"))?; - this.write_scalar(Scalar::from_target_isize(-1, this), dest)?; - return interp_ok(()); + return this.set_last_error_and_return(LibcError("EINVAL"), dest); } }; let timeout_clock = if op & futex_realtime == futex_realtime { @@ -153,14 +150,12 @@ pub fn futex<'tcx>( Scalar::from_target_isize(0, this), // retval_succ Scalar::from_target_isize(-1, this), // retval_timeout dest.clone(), - this.eval_libc("ETIMEDOUT"), + this.eval_libc("ETIMEDOUT"), // errno_timeout ); } else { // The futex value doesn't match the expected value, so we return failure // right away without sleeping: -1 and errno set to EAGAIN. - let eagain = this.eval_libc("EAGAIN"); - this.set_last_error(eagain)?; - this.write_scalar(Scalar::from_target_isize(-1, this), dest)?; + return this.set_last_error_and_return(LibcError("EAGAIN"), dest); } } // FUTEX_WAKE: (int *addr, int op = FUTEX_WAKE, int val) @@ -180,9 +175,7 @@ pub fn futex<'tcx>( u32::MAX }; if bitset == 0 { - this.set_last_error(LibcError("EINVAL"))?; - this.write_scalar(Scalar::from_target_isize(-1, this), dest)?; - return interp_ok(()); + return this.set_last_error_and_return(LibcError("EINVAL"), dest); } // Together with the SeqCst fence in futex_wait, this makes sure that futex_wait // will see the latest value on addr which could be changed by our caller diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index b199992245cd4..cd07bc9e013fd 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -181,6 +181,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { thread, this.read_scalar(name)?, this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?.try_into().unwrap(), + /* truncate */ false, )? { Scalar::from_u32(0) } else { diff --git a/src/tools/miri/src/shims/unix/mem.rs b/src/tools/miri/src/shims/unix/mem.rs index 9273748ef3bfd..9371edfc83db2 100644 --- a/src/tools/miri/src/shims/unix/mem.rs +++ b/src/tools/miri/src/shims/unix/mem.rs @@ -57,11 +57,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // First, we do some basic argument validation as required by mmap if (flags & (map_private | map_shared)).count_ones() != 1 { - this.set_last_error(this.eval_libc("EINVAL"))?; + this.set_last_error(LibcError("EINVAL"))?; return interp_ok(this.eval_libc("MAP_FAILED")); } if length == 0 { - this.set_last_error(this.eval_libc("EINVAL"))?; + this.set_last_error(LibcError("EINVAL"))?; return interp_ok(this.eval_libc("MAP_FAILED")); } @@ -103,11 +103,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let align = this.machine.page_align(); let Some(map_length) = length.checked_next_multiple_of(this.machine.page_size) else { - this.set_last_error(this.eval_libc("EINVAL"))?; + this.set_last_error(LibcError("EINVAL"))?; return interp_ok(this.eval_libc("MAP_FAILED")); }; if map_length > this.target_usize_max() { - this.set_last_error(this.eval_libc("EINVAL"))?; + this.set_last_error(LibcError("EINVAL"))?; return interp_ok(this.eval_libc("MAP_FAILED")); } @@ -134,16 +134,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // as a dealloc. #[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero if addr.addr().bytes() % this.machine.page_size != 0 { - this.set_last_error(this.eval_libc("EINVAL"))?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EINVAL")); } let Some(length) = length.checked_next_multiple_of(this.machine.page_size) else { - this.set_last_error(this.eval_libc("EINVAL"))?; - return interp_ok(Scalar::from_i32(-1)); + return this.set_last_error_and_return_i32(LibcError("EINVAL")); }; if length > this.target_usize_max() { - this.set_last_error(this.eval_libc("EINVAL"))?; + this.set_last_error(LibcError("EINVAL"))?; return interp_ok(this.eval_libc("MAP_FAILED")); } diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs index 7f3d0f07bdce0..c9c1b01b8b1c7 100644 --- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs @@ -30,6 +30,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.read_scalar(thread)?, this.read_scalar(name)?, max_len, + /* truncate */ false, )?; let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") }; this.write_scalar(res, dest)?; diff --git a/src/tools/miri/src/shims/unix/thread.rs b/src/tools/miri/src/shims/unix/thread.rs index 7f97afc8e4b12..51256d800a432 100644 --- a/src/tools/miri/src/shims/unix/thread.rs +++ b/src/tools/miri/src/shims/unix/thread.rs @@ -64,23 +64,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } /// Set the name of the specified thread. If the name including the null terminator - /// is longer than `name_max_len`, then `false` is returned. + /// is longer or equals to `name_max_len`, then if `truncate` is set the truncated name + /// is used as the thread name, otherwise `false` is returned. fn pthread_setname_np( &mut self, thread: Scalar, name: Scalar, name_max_len: usize, + truncate: bool, ) -> InterpResult<'tcx, bool> { let this = self.eval_context_mut(); let thread = thread.to_int(this.libc_ty_layout("pthread_t").size)?; let thread = ThreadId::try_from(thread).unwrap(); let name = name.to_pointer(this)?; - let name = this.read_c_str(name)?.to_owned(); + let mut name = this.read_c_str(name)?.to_owned(); // Comparing with `>=` to account for null terminator. if name.len() >= name_max_len { - return interp_ok(false); + if truncate { + name.truncate(name_max_len.saturating_sub(1)); + } else { + return interp_ok(false); + } } this.set_thread_name(thread, name); diff --git a/src/tools/miri/src/shims/windows/sync.rs b/src/tools/miri/src/shims/windows/sync.rs index f8861085fe5de..f7566a8112da7 100644 --- a/src/tools/miri/src/shims/windows/sync.rs +++ b/src/tools/miri/src/shims/windows/sync.rs @@ -202,7 +202,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Scalar::from_i32(1), // retval_succ Scalar::from_i32(0), // retval_timeout dest.clone(), - this.eval_windows("c", "ERROR_TIMEOUT"), + this.eval_windows("c", "ERROR_TIMEOUT"), // errno_timeout ); } diff --git a/src/tools/miri/test_dependencies/Cargo.lock b/src/tools/miri/test_dependencies/Cargo.lock index 64bfc84ef2006..0a5e9f62dd9fb 100644 --- a/src/tools/miri/test_dependencies/Cargo.lock +++ b/src/tools/miri/test_dependencies/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -128,9 +128,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.158" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "linux-raw-sys" diff --git a/src/tools/miri/tests/fail-dep/libc/prctl-get-name-buffer-too-small.rs b/src/tools/miri/tests/fail-dep/libc/prctl-get-name-buffer-too-small.rs new file mode 100644 index 0000000000000..4b731866aca1d --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/prctl-get-name-buffer-too-small.rs @@ -0,0 +1,10 @@ +//! Ensure we report UB when the buffer is smaller than 16 bytes (even if the thread +//! name would fit in the smaller buffer). +//@only-target: android # Miri supports prctl for Android only + +fn main() { + let mut buf = vec![0u8; 15]; + unsafe { + libc::prctl(libc::PR_GET_NAME, buf.as_mut_ptr().cast::()); //~ ERROR: memory access failed: expected a pointer to 16 bytes of memory, but got alloc952 which is only 15 bytes from the end of the allocation + } +} diff --git a/src/tools/miri/tests/fail-dep/libc/prctl-get-name-buffer-too-small.stderr b/src/tools/miri/tests/fail-dep/libc/prctl-get-name-buffer-too-small.stderr new file mode 100644 index 0000000000000..275a38e593c8f --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/prctl-get-name-buffer-too-small.stderr @@ -0,0 +1,21 @@ +error: Undefined Behavior: memory access failed: expected a pointer to 16 bytes of memory, but got ALLOC which is only 15 bytes from the end of the allocation + --> tests/fail-dep/libc/prctl-threadname.rs:LL:CC + | +LL | libc::prctl(libc::PR_GET_NAME, buf.as_mut_ptr().cast::()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: expected a pointer to 16 bytes of memory, but got ALLOC which is only 15 bytes from the end of the allocation + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> tests/fail-dep/libc/prctl-threadname.rs:LL:CC + | +LL | let mut buf = vec![0u8; 15]; + | ^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `main` at tests/fail-dep/libc/prctl-threadname.rs:LL:CC + = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair-data-race.rs b/src/tools/miri/tests/fail-dep/libc/socketpair-data-race.rs index f4c009456d296..55491da9f60d7 100644 --- a/src/tools/miri/tests/fail-dep/libc/socketpair-data-race.rs +++ b/src/tools/miri/tests/fail-dep/libc/socketpair-data-race.rs @@ -1,7 +1,7 @@ //! This is a regression test for : we had some //! faulty logic around `release_clock` that led to this code not reporting a data race. //@ignore-target: windows # no libc socketpair on Windows -//@compile-flags: -Zmiri-preemption-rate=0 +//@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-address-reuse-rate=0 use std::thread; fn main() { diff --git a/src/tools/miri/tests/fail/provenance/ptr_invalid.rs b/src/tools/miri/tests/fail/provenance/ptr_invalid.rs index d4479f32e56fb..c91f4ec158fee 100644 --- a/src/tools/miri/tests/fail/provenance/ptr_invalid.rs +++ b/src/tools/miri/tests/fail/provenance/ptr_invalid.rs @@ -1,4 +1,3 @@ - // Ensure that a `ptr::without_provenance` ptr is truly invalid. fn main() { let x = 42; diff --git a/src/tools/miri/tests/fail/tail_calls/dangling-local-var.rs b/src/tools/miri/tests/fail/tail_calls/dangling-local-var.rs new file mode 100644 index 0000000000000..b69f7ee9dff65 --- /dev/null +++ b/src/tools/miri/tests/fail/tail_calls/dangling-local-var.rs @@ -0,0 +1,16 @@ +#![feature(explicit_tail_calls)] +#![allow(incomplete_features)] + +fn g(x: *const i32) { + let _val = unsafe { *x }; //~ERROR: has been freed, so this pointer is dangling +} + +fn f(_x: *const i32) { + let local = 0; + let ptr = &local as *const i32; + become g(ptr) +} + +fn main() { + f(std::ptr::null()); +} diff --git a/src/tools/miri/tests/fail/tail_calls/dangling-local-var.stderr b/src/tools/miri/tests/fail/tail_calls/dangling-local-var.stderr new file mode 100644 index 0000000000000..6acd69ab3f8cf --- /dev/null +++ b/src/tools/miri/tests/fail/tail_calls/dangling-local-var.stderr @@ -0,0 +1,30 @@ +error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling + --> tests/fail/tail_calls/dangling-local-var.rs:LL:CC + | +LL | let _val = unsafe { *x }; + | ^^ memory access failed: ALLOC has been freed, so this pointer is dangling + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> tests/fail/tail_calls/dangling-local-var.rs:LL:CC + | +LL | let local = 0; + | ^^^^^ +help: ALLOC was deallocated here: + --> tests/fail/tail_calls/dangling-local-var.rs:LL:CC + | +LL | } + | ^ + = note: BACKTRACE (of the first span): + = note: inside `g` at tests/fail/tail_calls/dangling-local-var.rs:LL:CC +note: inside `main` + --> tests/fail/tail_calls/dangling-local-var.rs:LL:CC + | +LL | f(std::ptr::null()); + | ^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass-dep/libc/prctl-threadname.rs b/src/tools/miri/tests/pass-dep/libc/prctl-threadname.rs new file mode 100644 index 0000000000000..87ae3753fea70 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/prctl-threadname.rs @@ -0,0 +1,74 @@ +//@only-target: android # Miri supports prctl for Android only +use std::ffi::{CStr, CString}; +use std::thread; + +// The Linux kernel all names 16 bytes long including the null terminator. +const MAX_THREAD_NAME_LEN: usize = 16; + +fn main() { + // The short name should be shorter than 16 bytes which POSIX promises + // for thread names. The length includes a null terminator. + let short_name = "test_named".to_owned(); + let long_name = std::iter::once("test_named_thread_truncation") + .chain(std::iter::repeat(" yada").take(100)) + .collect::(); + + fn set_thread_name(name: &CStr) -> i32 { + unsafe { libc::prctl(libc::PR_SET_NAME, name.as_ptr().cast::()) } + } + + fn get_thread_name(name: &mut [u8]) -> i32 { + assert!(name.len() >= MAX_THREAD_NAME_LEN); + unsafe { libc::prctl(libc::PR_GET_NAME, name.as_mut_ptr().cast::()) } + } + + // Set name via Rust API, get it via prctl. + let long_name2 = long_name.clone(); + thread::Builder::new() + .name(long_name.clone()) + .spawn(move || { + let mut buf = vec![0u8; MAX_THREAD_NAME_LEN]; + assert_eq!(get_thread_name(&mut buf), 0); + let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); + let truncated_name = &long_name2[..long_name2.len().min(MAX_THREAD_NAME_LEN - 1)]; + assert_eq!(cstr.to_bytes(), truncated_name.as_bytes()); + }) + .unwrap() + .join() + .unwrap(); + + // Set name via prctl and get it again (short name). + thread::Builder::new() + .spawn(move || { + // Set short thread name. + let cstr = CString::new(short_name.clone()).unwrap(); + assert!(cstr.to_bytes_with_nul().len() <= MAX_THREAD_NAME_LEN); // this should fit + assert_eq!(set_thread_name(&cstr), 0); + + let mut buf = vec![0u8; MAX_THREAD_NAME_LEN]; + assert_eq!(get_thread_name(&mut buf), 0); + let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); + assert_eq!(cstr.to_bytes(), short_name.as_bytes()); + }) + .unwrap() + .join() + .unwrap(); + + // Set name via prctl and get it again (long name). + thread::Builder::new() + .spawn(move || { + // Set full thread name. + let cstr = CString::new(long_name.clone()).unwrap(); + assert!(cstr.to_bytes_with_nul().len() > MAX_THREAD_NAME_LEN); + // Names are truncated by the Linux kernel. + assert_eq!(set_thread_name(&cstr), 0); + + let mut buf = vec![0u8; MAX_THREAD_NAME_LEN]; + assert_eq!(get_thread_name(&mut buf), 0); + let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); + assert_eq!(cstr.to_bytes(), &long_name.as_bytes()[..(MAX_THREAD_NAME_LEN - 1)]); + }) + .unwrap() + .join() + .unwrap(); +} diff --git a/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs b/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs index 404ef7cc42d61..0e5b501bbccdc 100644 --- a/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs +++ b/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs @@ -1,4 +1,5 @@ //@ignore-target: windows # No pthreads on Windows +//@ignore-target: android # No pthread_{get,set}_name on Android use std::ffi::{CStr, CString}; use std::thread; @@ -65,6 +66,22 @@ fn main() { } } + // Set name via Rust API, get it via pthreads. + let long_name2 = long_name.clone(); + thread::Builder::new() + .name(long_name.clone()) + .spawn(move || { + let mut buf = vec![0u8; long_name2.len() + 1]; + assert_eq!(get_thread_name(&mut buf), 0); + let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); + let truncated_name = &long_name2[..long_name2.len().min(MAX_THREAD_NAME_LEN - 1)]; + assert_eq!(cstr.to_bytes(), truncated_name.as_bytes()); + }) + .unwrap() + .join() + .unwrap(); + + // Set name via pthread and get it again (short name). thread::Builder::new() .spawn(move || { // Set short thread name. @@ -130,6 +147,7 @@ fn main() { .join() .unwrap(); + // Set name via pthread and get it again (long name). thread::Builder::new() .spawn(move || { // Set full thread name. diff --git a/src/tools/miri/tests/pass/dyn-upcast.rs b/src/tools/miri/tests/pass/dyn-upcast.rs index 306e9ab9c6715..61410f7c4e0b5 100644 --- a/src/tools/miri/tests/pass/dyn-upcast.rs +++ b/src/tools/miri/tests/pass/dyn-upcast.rs @@ -433,7 +433,8 @@ fn replace_vptr() { } fn drop_principal() { - use std::{alloc::Layout, any::Any}; + use std::alloc::Layout; + use std::any::Any; const fn yeet_principal(x: Box) -> Box { x diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs index 853d3e80517da..66843ca584b8d 100644 --- a/src/tools/miri/tests/pass/float.rs +++ b/src/tools/miri/tests/pass/float.rs @@ -157,13 +157,18 @@ fn basic() { assert_eq(-{ 5.0_f128 }, -5.0_f128); // infinities, NaN - // FIXME(f16_f128): add when constants and `is_infinite` are available + assert!((5.0_f16 / 0.0).is_infinite()); + assert_ne!({ 5.0_f16 / 0.0 }, { -5.0_f16 / 0.0 }); assert!((5.0_f32 / 0.0).is_infinite()); assert_ne!({ 5.0_f32 / 0.0 }, { -5.0_f32 / 0.0 }); assert!((5.0_f64 / 0.0).is_infinite()); assert_ne!({ 5.0_f64 / 0.0 }, { 5.0_f64 / -0.0 }); + assert!((5.0_f128 / 0.0).is_infinite()); + assert_ne!({ 5.0_f128 / 0.0 }, { 5.0_f128 / -0.0 }); + assert_ne!(f16::NAN, f16::NAN); assert_ne!(f32::NAN, f32::NAN); assert_ne!(f64::NAN, f64::NAN); + assert_ne!(f128::NAN, f128::NAN); // negative zero let posz = 0.0f16; @@ -215,9 +220,14 @@ fn basic() { assert!((black_box(-1.0f128) % 1.0).is_sign_negative()); assert!((black_box(-1.0f128) % -1.0).is_sign_negative()); - // FIXME(f16_f128): add when `abs` is available + assert_eq!((-1.0f16).abs(), 1.0f16); + assert_eq!(34.2f16.abs(), 34.2f16); assert_eq!((-1.0f32).abs(), 1.0f32); + assert_eq!(34.2f32.abs(), 34.2f32); + assert_eq!((-1.0f64).abs(), 1.0f64); assert_eq!(34.2f64.abs(), 34.2f64); + assert_eq!((-1.0f128).abs(), 1.0f128); + assert_eq!(34.2f128.abs(), 34.2f128); } /// Test casts from floats to ints and back @@ -654,6 +664,14 @@ fn casts() { } fn ops() { + // f16 min/max + assert_eq((1.0_f16).max(-1.0), 1.0); + assert_eq((1.0_f16).min(-1.0), -1.0); + assert_eq(f16::NAN.min(9.0), 9.0); + assert_eq(f16::NAN.max(-9.0), -9.0); + assert_eq((9.0_f16).min(f16::NAN), 9.0); + assert_eq((-9.0_f16).max(f16::NAN), -9.0); + // f32 min/max assert_eq((1.0 as f32).max(-1.0), 1.0); assert_eq((1.0 as f32).min(-1.0), -1.0); @@ -670,6 +688,21 @@ fn ops() { assert_eq((9.0 as f64).min(f64::NAN), 9.0); assert_eq((-9.0 as f64).max(f64::NAN), -9.0); + // f128 min/max + assert_eq((1.0_f128).max(-1.0), 1.0); + assert_eq((1.0_f128).min(-1.0), -1.0); + assert_eq(f128::NAN.min(9.0), 9.0); + assert_eq(f128::NAN.max(-9.0), -9.0); + assert_eq((9.0_f128).min(f128::NAN), 9.0); + assert_eq((-9.0_f128).max(f128::NAN), -9.0); + + // f16 copysign + assert_eq(3.5_f16.copysign(0.42), 3.5_f16); + assert_eq(3.5_f16.copysign(-0.42), -3.5_f16); + assert_eq((-3.5_f16).copysign(0.42), 3.5_f16); + assert_eq((-3.5_f16).copysign(-0.42), -3.5_f16); + assert!(f16::NAN.copysign(1.0).is_nan()); + // f32 copysign assert_eq(3.5_f32.copysign(0.42), 3.5_f32); assert_eq(3.5_f32.copysign(-0.42), -3.5_f32); @@ -683,6 +716,13 @@ fn ops() { assert_eq((-3.5_f64).copysign(0.42), 3.5_f64); assert_eq((-3.5_f64).copysign(-0.42), -3.5_f64); assert!(f64::NAN.copysign(1.0).is_nan()); + + // f128 copysign + assert_eq(3.5_f128.copysign(0.42), 3.5_f128); + assert_eq(3.5_f128.copysign(-0.42), -3.5_f128); + assert_eq((-3.5_f128).copysign(0.42), 3.5_f128); + assert_eq((-3.5_f128).copysign(-0.42), -3.5_f128); + assert!(f128::NAN.copysign(1.0).is_nan()); } /// Tests taken from rustc test suite. @@ -807,6 +847,18 @@ fn nan_casts() { fn rounding() { // Test cases taken from the library's tests for this feature + // f16 + assert_eq(2.5f16.round_ties_even(), 2.0f16); + assert_eq(1.0f16.round_ties_even(), 1.0f16); + assert_eq(1.3f16.round_ties_even(), 1.0f16); + assert_eq(1.5f16.round_ties_even(), 2.0f16); + assert_eq(1.7f16.round_ties_even(), 2.0f16); + assert_eq(0.0f16.round_ties_even(), 0.0f16); + assert_eq((-0.0f16).round_ties_even(), -0.0f16); + assert_eq((-1.0f16).round_ties_even(), -1.0f16); + assert_eq((-1.3f16).round_ties_even(), -1.0f16); + assert_eq((-1.5f16).round_ties_even(), -2.0f16); + assert_eq((-1.7f16).round_ties_even(), -2.0f16); // f32 assert_eq(2.5f32.round_ties_even(), 2.0f32); assert_eq(1.0f32.round_ties_even(), 1.0f32); @@ -831,23 +883,59 @@ fn rounding() { assert_eq((-1.3f64).round_ties_even(), -1.0f64); assert_eq((-1.5f64).round_ties_even(), -2.0f64); assert_eq((-1.7f64).round_ties_even(), -2.0f64); - + // f128 + assert_eq(2.5f128.round_ties_even(), 2.0f128); + assert_eq(1.0f128.round_ties_even(), 1.0f128); + assert_eq(1.3f128.round_ties_even(), 1.0f128); + assert_eq(1.5f128.round_ties_even(), 2.0f128); + assert_eq(1.7f128.round_ties_even(), 2.0f128); + assert_eq(0.0f128.round_ties_even(), 0.0f128); + assert_eq((-0.0f128).round_ties_even(), -0.0f128); + assert_eq((-1.0f128).round_ties_even(), -1.0f128); + assert_eq((-1.3f128).round_ties_even(), -1.0f128); + assert_eq((-1.5f128).round_ties_even(), -2.0f128); + assert_eq((-1.7f128).round_ties_even(), -2.0f128); + + assert_eq!(3.8f16.floor(), 3.0f16); + assert_eq!((-1.1f16).floor(), -2.0f16); assert_eq!(3.8f32.floor(), 3.0f32); + assert_eq!((-1.1f32).floor(), -2.0f32); + assert_eq!(3.8f64.floor(), 3.0f64); assert_eq!((-1.1f64).floor(), -2.0f64); + assert_eq!(3.8f128.floor(), 3.0f128); + assert_eq!((-1.1f128).floor(), -2.0f128); + assert_eq!(3.8f16.ceil(), 4.0f16); + assert_eq!((-2.3f16).ceil(), -2.0f16); + assert_eq!(3.8f32.ceil(), 4.0f32); assert_eq!((-2.3f32).ceil(), -2.0f32); assert_eq!(3.8f64.ceil(), 4.0f64); + assert_eq!((-2.3f64).ceil(), -2.0f64); + assert_eq!(3.8f128.ceil(), 4.0f128); + assert_eq!((-2.3f128).ceil(), -2.0f128); + assert_eq!(0.1f16.trunc(), 0.0f16); + assert_eq!((-0.1f16).trunc(), 0.0f16); assert_eq!(0.1f32.trunc(), 0.0f32); + assert_eq!((-0.1f32).trunc(), 0.0f32); + assert_eq!(0.1f64.trunc(), 0.0f64); assert_eq!((-0.1f64).trunc(), 0.0f64); + assert_eq!(0.1f128.trunc(), 0.0f128); + assert_eq!((-0.1f128).trunc(), 0.0f128); + assert_eq!(3.3_f16.round(), 3.0); + assert_eq!(2.5_f16.round(), 3.0); assert_eq!(3.3_f32.round(), 3.0); assert_eq!(2.5_f32.round(), 3.0); assert_eq!(3.9_f64.round(), 4.0); assert_eq!(2.5_f64.round(), 3.0); + assert_eq!(3.9_f128.round(), 4.0); + assert_eq!(2.5_f128.round(), 3.0); } fn mul_add() { + // FIXME(f16_f128): add when supported + assert_eq!(3.0f32.mul_add(2.0f32, 5.0f32), 11.0); assert_eq!(0.0f32.mul_add(-2.0, f32::consts::E), f32::consts::E); assert_eq!(3.0f64.mul_add(2.0, 5.0), 11.0); @@ -983,7 +1071,7 @@ fn test_fast() { use std::intrinsics::{fadd_fast, fdiv_fast, fmul_fast, frem_fast, fsub_fast}; #[inline(never)] - pub fn test_operations_f64(a: f64, b: f64) { + pub fn test_operations_f16(a: f16, b: f16) { // make sure they all map to the correct operation unsafe { assert_eq!(fadd_fast(a, b), a + b); @@ -1006,10 +1094,38 @@ fn test_fast() { } } - test_operations_f64(1., 2.); - test_operations_f64(10., 5.); + #[inline(never)] + pub fn test_operations_f64(a: f64, b: f64) { + // make sure they all map to the correct operation + unsafe { + assert_eq!(fadd_fast(a, b), a + b); + assert_eq!(fsub_fast(a, b), a - b); + assert_eq!(fmul_fast(a, b), a * b); + assert_eq!(fdiv_fast(a, b), a / b); + assert_eq!(frem_fast(a, b), a % b); + } + } + + #[inline(never)] + pub fn test_operations_f128(a: f128, b: f128) { + // make sure they all map to the correct operation + unsafe { + assert_eq!(fadd_fast(a, b), a + b); + assert_eq!(fsub_fast(a, b), a - b); + assert_eq!(fmul_fast(a, b), a * b); + assert_eq!(fdiv_fast(a, b), a / b); + assert_eq!(frem_fast(a, b), a % b); + } + } + + test_operations_f16(11., 2.); + test_operations_f16(10., 15.); test_operations_f32(11., 2.); test_operations_f32(10., 15.); + test_operations_f64(1., 2.); + test_operations_f64(10., 5.); + test_operations_f128(1., 2.); + test_operations_f128(10., 5.); } fn test_algebraic() { @@ -1018,7 +1134,7 @@ fn test_algebraic() { }; #[inline(never)] - pub fn test_operations_f64(a: f64, b: f64) { + pub fn test_operations_f16(a: f16, b: f16) { // make sure they all map to the correct operation assert_eq!(fadd_algebraic(a, b), a + b); assert_eq!(fsub_algebraic(a, b), a - b); @@ -1037,15 +1153,41 @@ fn test_algebraic() { assert_eq!(frem_algebraic(a, b), a % b); } - test_operations_f64(1., 2.); - test_operations_f64(10., 5.); + #[inline(never)] + pub fn test_operations_f64(a: f64, b: f64) { + // make sure they all map to the correct operation + assert_eq!(fadd_algebraic(a, b), a + b); + assert_eq!(fsub_algebraic(a, b), a - b); + assert_eq!(fmul_algebraic(a, b), a * b); + assert_eq!(fdiv_algebraic(a, b), a / b); + assert_eq!(frem_algebraic(a, b), a % b); + } + + #[inline(never)] + pub fn test_operations_f128(a: f128, b: f128) { + // make sure they all map to the correct operation + assert_eq!(fadd_algebraic(a, b), a + b); + assert_eq!(fsub_algebraic(a, b), a - b); + assert_eq!(fmul_algebraic(a, b), a * b); + assert_eq!(fdiv_algebraic(a, b), a / b); + assert_eq!(frem_algebraic(a, b), a % b); + } + + test_operations_f16(11., 2.); + test_operations_f16(10., 15.); test_operations_f32(11., 2.); test_operations_f32(10., 15.); + test_operations_f64(1., 2.); + test_operations_f64(10., 5.); + test_operations_f128(1., 2.); + test_operations_f128(10., 5.); } fn test_fmuladd() { use std::intrinsics::{fmuladdf32, fmuladdf64}; + // FIXME(f16_f128): add when supported + #[inline(never)] pub fn test_operations_f32(a: f32, b: f32, c: f32) { assert_approx_eq!(unsafe { fmuladdf32(a, b, c) }, a * b + c);