diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 5324e4bec5b..da0581ea862 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -2,7 +2,7 @@ use std::collections::BTreeSet; use std::num::NonZero; use std::sync::Mutex; use std::time::Duration; -use std::{cmp, io, iter}; +use std::{cmp, iter}; use rand::RngCore; use rustc_apfloat::Float; @@ -31,65 +31,6 @@ pub enum AccessKind { Write, } -// This mapping should match `decode_error_kind` in -// . -const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { - use std::io::ErrorKind::*; - &[ - ("E2BIG", ArgumentListTooLong), - ("EADDRINUSE", AddrInUse), - ("EADDRNOTAVAIL", AddrNotAvailable), - ("EBUSY", ResourceBusy), - ("ECONNABORTED", ConnectionAborted), - ("ECONNREFUSED", ConnectionRefused), - ("ECONNRESET", ConnectionReset), - ("EDEADLK", Deadlock), - ("EDQUOT", FilesystemQuotaExceeded), - ("EEXIST", AlreadyExists), - ("EFBIG", FileTooLarge), - ("EHOSTUNREACH", HostUnreachable), - ("EINTR", Interrupted), - ("EINVAL", InvalidInput), - ("EISDIR", IsADirectory), - ("ELOOP", FilesystemLoop), - ("ENOENT", NotFound), - ("ENOMEM", OutOfMemory), - ("ENOSPC", StorageFull), - ("ENOSYS", Unsupported), - ("EMLINK", TooManyLinks), - ("ENAMETOOLONG", InvalidFilename), - ("ENETDOWN", NetworkDown), - ("ENETUNREACH", NetworkUnreachable), - ("ENOTCONN", NotConnected), - ("ENOTDIR", NotADirectory), - ("ENOTEMPTY", DirectoryNotEmpty), - ("EPIPE", BrokenPipe), - ("EROFS", ReadOnlyFilesystem), - ("ESPIPE", NotSeekable), - ("ESTALE", StaleNetworkFileHandle), - ("ETIMEDOUT", TimedOut), - ("ETXTBSY", ExecutableFileBusy), - ("EXDEV", CrossesDevices), - // The following have two valid options. We have both for the forwards mapping; only the - // first one will be used for the backwards mapping. - ("EPERM", PermissionDenied), - ("EACCES", PermissionDenied), - ("EWOULDBLOCK", WouldBlock), - ("EAGAIN", WouldBlock), - ] -}; -// This mapping should match `decode_error_kind` in -// . -const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { - use std::io::ErrorKind::*; - // FIXME: this is still incomplete. - &[ - ("ERROR_ACCESS_DENIED", PermissionDenied), - ("ERROR_FILE_NOT_FOUND", NotFound), - ("ERROR_INVALID_PARAMETER", InvalidInput), - ] -}; - /// Gets an instance for a path. /// /// A `None` namespace indicates we are looking for a module. @@ -745,130 +686,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { self.eval_context_ref().tcx.sess.target.families.iter().any(|f| f == "unix") } - /// Get last error variable as a place, lazily allocating thread-local storage for it if - /// necessary. - fn last_error_place(&mut self) -> InterpResult<'tcx, MPlaceTy<'tcx>> { - let this = self.eval_context_mut(); - if let Some(errno_place) = this.active_thread_ref().last_error.as_ref() { - Ok(errno_place.clone()) - } else { - // Allocate new place, set initial value to 0. - let errno_layout = this.machine.layouts.u32; - let errno_place = this.allocate(errno_layout, MiriMemoryKind::Machine.into())?; - this.write_scalar(Scalar::from_u32(0), &errno_place)?; - this.active_thread_mut().last_error = Some(errno_place.clone()); - Ok(errno_place) - } - } - - /// Sets the last error variable. - fn set_last_error(&mut self, scalar: Scalar) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); - let errno_place = this.last_error_place()?; - this.write_scalar(scalar, &errno_place) - } - - /// Gets the last error variable. - fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); - let errno_place = this.last_error_place()?; - this.read_scalar(&errno_place) - } - - /// 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: std::io::Error) -> InterpResult<'tcx, Scalar> { - 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 { - return Ok(this.eval_libc(name)); - } - } - 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 { - return Ok(this.eval_windows("c", name)); - } - } - throw_unsup_format!("unsupported io error: {err}"); - } else { - throw_unsup_format!( - "converting io::Error into errnum is unsupported for OS {}", - target.os - ) - } - } - - /// The inverse of `io_error_to_errnum`. - #[allow(clippy::needless_return)] - fn try_errnum_to_io_error( - &self, - errnum: Scalar, - ) -> InterpResult<'tcx, Option> { - let this = self.eval_context_ref(); - let target = &this.tcx.sess.target; - if target.families.iter().any(|f| f == "unix") { - let errnum = errnum.to_i32()?; - for &(name, kind) in UNIX_IO_ERROR_TABLE { - if errnum == this.eval_libc_i32(name) { - return Ok(Some(kind)); - } - } - return Ok(None); - } else if target.families.iter().any(|f| f == "windows") { - let errnum = errnum.to_u32()?; - for &(name, kind) in WINDOWS_IO_ERROR_TABLE { - if errnum == this.eval_windows("c", name).to_u32()? { - return Ok(Some(kind)); - } - } - return Ok(None); - } else { - throw_unsup_format!( - "converting errnum into io::Error is unsupported for OS {}", - target.os - ) - } - } - - /// Sets the last OS error using a `std::io::ErrorKind`. - 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)?) - } - - /// Sets the last OS error using a `std::io::ErrorKind` and writes -1 to dest place. - fn set_last_error_and_return( - &mut self, - err: impl Into, - dest: &MPlaceTy<'tcx>, - ) -> InterpResult<'tcx> { - self.set_last_error(self.io_error_to_errnum(err.into())?)?; - self.write_int(-1, dest)?; - Ok(()) - } - - /// Helper function that consumes an `std::io::Result` and returns an - /// `InterpResult<'tcx,T>::Ok` instead. In case the result is an error, this function returns - /// `Ok(-1)` and sets the last OS error accordingly. - /// - /// This function uses `T: From` instead of `i32` directly because some IO related - /// functions return different integer types (like `read`, that returns an `i64`). - fn try_unwrap_io_result>( - &mut self, - result: std::io::Result, - ) -> InterpResult<'tcx, T> { - match result { - Ok(ok) => Ok(ok), - Err(e) => { - self.eval_context_mut().set_last_error_from_io_error(e)?; - Ok((-1).into()) - } - } - } - /// Dereference a pointer operand to a place using `layout` instead of the pointer's declared type fn deref_pointer_as( &self, diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 78e7bf70455..c4adf38299c 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -150,6 +150,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 _; 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/io_error.rs b/src/tools/miri/src/shims/io_error.rs new file mode 100644 index 00000000000..8c727f4c894 --- /dev/null +++ b/src/tools/miri/src/shims/io_error.rs @@ -0,0 +1,190 @@ +use std::io; + +use crate::*; + +// This mapping should match `decode_error_kind` in +// . +const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { + use std::io::ErrorKind::*; + &[ + ("E2BIG", ArgumentListTooLong), + ("EADDRINUSE", AddrInUse), + ("EADDRNOTAVAIL", AddrNotAvailable), + ("EBUSY", ResourceBusy), + ("ECONNABORTED", ConnectionAborted), + ("ECONNREFUSED", ConnectionRefused), + ("ECONNRESET", ConnectionReset), + ("EDEADLK", Deadlock), + ("EDQUOT", FilesystemQuotaExceeded), + ("EEXIST", AlreadyExists), + ("EFBIG", FileTooLarge), + ("EHOSTUNREACH", HostUnreachable), + ("EINTR", Interrupted), + ("EINVAL", InvalidInput), + ("EISDIR", IsADirectory), + ("ELOOP", FilesystemLoop), + ("ENOENT", NotFound), + ("ENOMEM", OutOfMemory), + ("ENOSPC", StorageFull), + ("ENOSYS", Unsupported), + ("EMLINK", TooManyLinks), + ("ENAMETOOLONG", InvalidFilename), + ("ENETDOWN", NetworkDown), + ("ENETUNREACH", NetworkUnreachable), + ("ENOTCONN", NotConnected), + ("ENOTDIR", NotADirectory), + ("ENOTEMPTY", DirectoryNotEmpty), + ("EPIPE", BrokenPipe), + ("EROFS", ReadOnlyFilesystem), + ("ESPIPE", NotSeekable), + ("ESTALE", StaleNetworkFileHandle), + ("ETIMEDOUT", TimedOut), + ("ETXTBSY", ExecutableFileBusy), + ("EXDEV", CrossesDevices), + // The following have two valid options. We have both for the forwards mapping; only the + // first one will be used for the backwards mapping. + ("EPERM", PermissionDenied), + ("EACCES", PermissionDenied), + ("EWOULDBLOCK", WouldBlock), + ("EAGAIN", WouldBlock), + ] +}; +// This mapping should match `decode_error_kind` in +// . +const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { + use std::io::ErrorKind::*; + // FIXME: this is still incomplete. + &[ + ("ERROR_ACCESS_DENIED", PermissionDenied), + ("ERROR_FILE_NOT_FOUND", NotFound), + ("ERROR_INVALID_PARAMETER", InvalidInput), + ] +}; + +impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} +pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Get last error variable as a place, lazily allocating thread-local storage for it if + /// necessary. + fn last_error_place(&mut self) -> InterpResult<'tcx, MPlaceTy<'tcx>> { + let this = self.eval_context_mut(); + if let Some(errno_place) = this.active_thread_ref().last_error.as_ref() { + Ok(errno_place.clone()) + } else { + // Allocate new place, set initial value to 0. + let errno_layout = this.machine.layouts.u32; + let errno_place = this.allocate(errno_layout, MiriMemoryKind::Machine.into())?; + this.write_scalar(Scalar::from_u32(0), &errno_place)?; + this.active_thread_mut().last_error = Some(errno_place.clone()); + Ok(errno_place) + } + } + + /// Sets the last error variable. + fn set_last_error(&mut self, scalar: Scalar) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let errno_place = this.last_error_place()?; + this.write_scalar(scalar, &errno_place) + } + + /// Gets the last error variable. + fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + let errno_place = this.last_error_place()?; + this.read_scalar(&errno_place) + } + + /// 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: std::io::Error) -> InterpResult<'tcx, Scalar> { + 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 { + return Ok(this.eval_libc(name)); + } + } + 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 { + return Ok(this.eval_windows("c", name)); + } + } + throw_unsup_format!("unsupported io error: {err}"); + } else { + throw_unsup_format!( + "converting io::Error into errnum is unsupported for OS {}", + target.os + ) + } + } + + /// The inverse of `io_error_to_errnum`. + #[allow(clippy::needless_return)] + fn try_errnum_to_io_error( + &self, + errnum: Scalar, + ) -> InterpResult<'tcx, Option> { + let this = self.eval_context_ref(); + let target = &this.tcx.sess.target; + if target.families.iter().any(|f| f == "unix") { + let errnum = errnum.to_i32()?; + for &(name, kind) in UNIX_IO_ERROR_TABLE { + if errnum == this.eval_libc_i32(name) { + return Ok(Some(kind)); + } + } + return Ok(None); + } else if target.families.iter().any(|f| f == "windows") { + let errnum = errnum.to_u32()?; + for &(name, kind) in WINDOWS_IO_ERROR_TABLE { + if errnum == this.eval_windows("c", name).to_u32()? { + return Ok(Some(kind)); + } + } + return Ok(None); + } else { + throw_unsup_format!( + "converting errnum into io::Error is unsupported for OS {}", + target.os + ) + } + } + + /// Sets the last OS error using a `std::io::ErrorKind`. + 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)?) + } + + /// Sets the last OS error using a `std::io::ErrorKind` and writes -1 to dest place. + fn set_last_error_and_return( + &mut self, + err: impl Into, + dest: &MPlaceTy<'tcx>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + this.set_last_error(this.io_error_to_errnum(err.into())?)?; + this.write_int(-1, dest)?; + Ok(()) + } + + /// Helper function that consumes an `std::io::Result` and returns an + /// `InterpResult<'tcx,T>::Ok` instead. In case the result is an error, this function returns + /// `Ok(-1)` and sets the last OS error accordingly. + /// + /// This function uses `T: From` instead of `i32` directly because some IO related + /// functions return different integer types (like `read`, that returns an `i64`). + fn try_unwrap_io_result>( + &mut self, + result: std::io::Result, + ) -> InterpResult<'tcx, T> { + match result { + Ok(ok) => Ok(ok), + Err(e) => { + self.eval_context_mut().set_last_error_from_io_error(e)?; + Ok((-1).into()) + } + } + } +} diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs index a689ac2b378..b9317ac1a15 100644 --- a/src/tools/miri/src/shims/mod.rs +++ b/src/tools/miri/src/shims/mod.rs @@ -12,6 +12,7 @@ mod x86; pub mod env; pub mod extern_static; pub mod foreign_items; +pub mod io_error; pub mod os_str; pub mod panic; pub mod time;