Skip to content

Make returning io errors more uniform and convenient #3929

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 1 addition & 184 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -31,65 +31,6 @@ pub enum AccessKind {
Write,
}

// This mapping should match `decode_error_kind` in
// <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/unix/mod.rs>.
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
// <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/windows/mod.rs>.
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.
Expand Down Expand Up @@ -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<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 &(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<io::Error>,
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<T>` 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<i32>` instead of `i32` directly because some IO related
/// functions return different integer types (like `read`, that returns an `i64`).
fn try_unwrap_io_result<T: From<i32>>(
&mut self,
result: std::io::Result<T>,
) -> 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,
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _, LibcError};
pub use crate::shims::os_str::EvalContextExt as _;
pub use crate::shims::panic::{CatchUnwindData, EvalContextExt as _};
pub use crate::shims::time::EvalContextExt as _;
Expand Down
Loading