diff --git a/src/helpers.rs b/src/helpers.rs index 5b6e616400b4..b052463bb022 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -21,6 +21,27 @@ use crate::*; impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {} +const UNIX_IO_ERROR_TABLE: &[(std::io::ErrorKind, &str)] = { + use std::io::ErrorKind::*; + &[ + (ConnectionRefused, "ECONNREFUSED"), + (ConnectionReset, "ECONNRESET"), + (PermissionDenied, "EPERM"), + (BrokenPipe, "EPIPE"), + (NotConnected, "ENOTCONN"), + (ConnectionAborted, "ECONNABORTED"), + (AddrNotAvailable, "EADDRNOTAVAIL"), + (AddrInUse, "EADDRINUSE"), + (NotFound, "ENOENT"), + (Interrupted, "EINTR"), + (InvalidInput, "EINVAL"), + (TimedOut, "ETIMEDOUT"), + (AlreadyExists, "EEXIST"), + (WouldBlock, "EWOULDBLOCK"), + (DirectoryNotEmpty, "ENOTEMPTY"), + ] +}; + /// Gets an instance for a path. fn try_resolve_did<'mir, 'tcx>(tcx: TyCtxt<'tcx>, path: &[&str]) -> Option { tcx.crates(()).iter().find(|&&krate| tcx.crate_name(krate).as_str() == path[0]).and_then( @@ -502,39 +523,21 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx this.read_scalar(&errno_place.into())?.check_init() } - /// Sets the last OS error using a `std::io::ErrorKind`. This function tries to produce the most - /// similar OS error from the `std::io::ErrorKind` and sets it as the last OS error. - fn set_last_error_from_io_error(&mut self, err_kind: std::io::ErrorKind) -> InterpResult<'tcx> { - use std::io::ErrorKind::*; - let this = self.eval_context_mut(); + /// This function tries to produce the most similar OS error from the `std::io::ErrorKind` + /// as a platform-specific errnum. + fn io_error_to_errnum(&self, err_kind: std::io::ErrorKind) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_ref(); let target = &this.tcx.sess.target; - let target_os = &target.os; - let last_error = if target.families.iter().any(|f| f == "unix") { - this.eval_libc(match err_kind { - ConnectionRefused => "ECONNREFUSED", - ConnectionReset => "ECONNRESET", - PermissionDenied => "EPERM", - BrokenPipe => "EPIPE", - NotConnected => "ENOTCONN", - ConnectionAborted => "ECONNABORTED", - AddrNotAvailable => "EADDRNOTAVAIL", - AddrInUse => "EADDRINUSE", - NotFound => "ENOENT", - Interrupted => "EINTR", - InvalidInput => "EINVAL", - TimedOut => "ETIMEDOUT", - AlreadyExists => "EEXIST", - WouldBlock => "EWOULDBLOCK", - DirectoryNotEmpty => "ENOTEMPTY", - _ => { - throw_unsup_format!( - "io error {:?} cannot be translated into a raw os error", - err_kind - ) + if target.families.iter().any(|f| f == "unix") { + for &(kind, name) in UNIX_IO_ERROR_TABLE { + if err_kind == kind { + return this.eval_libc(name); } - })? + } + throw_unsup_format!("io error {:?} cannot be translated into a raw os error", err_kind) } else if target.families.iter().any(|f| f == "windows") { // FIXME: we have to finish implementing the Windows equivalent of this. + use std::io::ErrorKind::*; this.eval_windows( "c", match err_kind { @@ -546,14 +549,38 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx err_kind ), }, - )? + ) } else { throw_unsup_format!( - "setting the last OS error from an io::Error is unsupported for {}.", - target_os + "converting io::Error into errnum is unsupported for OS {}", + target.os ) - }; - this.set_last_error(last_error) + } + } + + /// The inverse of `io_error_to_errnum`. + fn errnum_to_io_error(&self, errnum: Scalar) -> InterpResult<'tcx, std::io::ErrorKind> { + 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 &(kind, name) in UNIX_IO_ERROR_TABLE { + if errnum == this.eval_libc_i32(name)? { + return Ok(kind); + } + } + throw_unsup_format!("raw errnum {:?} cannot be translated into io::Error", errnum) + } 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_kind: std::io::ErrorKind) -> InterpResult<'tcx> { + self.set_last_error(self.io_error_to_errnum(err_kind)?) } /// Helper function that consumes an `std::io::Result` and returns an diff --git a/src/shims/posix/foreign_items.rs b/src/shims/posix/foreign_items.rs index 4bf0bbc26212..16619d4aeb77 100644 --- a/src/shims/posix/foreign_items.rs +++ b/src/shims/posix/foreign_items.rs @@ -1,3 +1,5 @@ +use std::ffi::OsStr; + use log::trace; use rustc_middle::mir; @@ -421,6 +423,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx // We do not support forking, so there is nothing to do here. this.write_null(dest)?; } + "strerror_r" | "__xpg_strerror_r" => { + let &[ref errnum, ref buf, ref buflen] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let errnum = this.read_scalar(errnum)?.check_init()?; + let buf = this.read_pointer(buf)?; + let buflen = this.read_scalar(buflen)?.to_machine_usize(this)?; + + let error = this.errnum_to_io_error(errnum)?; + let formatted = error.to_string(); + let (complete, _) = this.write_os_str_to_c_str(OsStr::new(&formatted), buf, buflen)?; + let ret = if complete { 0 } else { this.eval_libc_i32("ERANGE")? }; + this.write_int(ret, dest)?; + } // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. diff --git a/tests/run-pass/fs.rs b/tests/run-pass/fs.rs index 67817e3e2c85..bc78cb560f00 100644 --- a/tests/run-pass/fs.rs +++ b/tests/run-pass/fs.rs @@ -335,6 +335,8 @@ fn test_errors() { // The following tests also check that the `__errno_location()` shim is working properly. // Opening a non-existing file should fail with a "not found" error. assert_eq!(ErrorKind::NotFound, File::open(&path).unwrap_err().kind()); + // Make sure we can also format this. + format!("{0:?}: {0}", File::open(&path).unwrap_err()); // Removing a non-existing file should fail with a "not found" error. assert_eq!(ErrorKind::NotFound, remove_file(&path).unwrap_err().kind()); // Reading the metadata of a non-existing file should fail with a "not found" error.