Auto merge of #2067 - RalfJung:strerror_r, r=RalfJung

implement strerror_r

This isn't perfect; we end up using [this match](72a25d05bf/library/std/src/io/error.rs (L380)) rather than the platform-specific messages, but at least we show something -- this is mostly informational anyway.

Cc https://github.com/rust-lang/miri/issues/2057
This commit is contained in:
bors 2022-04-17 02:54:53 +00:00
commit 35aeba78db
3 changed files with 77 additions and 34 deletions

View file

@ -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<DefId> {
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<Tag>> {
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<Tag>) -> 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<T>` and returns an

View file

@ -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.

View file

@ -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.