Integrate OSError creations into OSErrorBuilder (#6443)

This commit is contained in:
Jeong, YunWon
2025-12-16 23:11:35 +09:00
committed by GitHub
parent 272b36daa5
commit 65bdfc3d4e
6 changed files with 189 additions and 135 deletions

View File

@@ -1193,6 +1193,8 @@ pub(crate) fn errno_to_exc_type(_errno: i32, _vm: &VirtualMachine) -> Option<&'s
None
}
pub(crate) use types::{OSErrorBuilder, ToOSErrorBuilder};
pub(super) mod types {
use crate::common::lock::PyRwLock;
use crate::object::{MaybeTraverse, Traverse, TraverseFn};
@@ -1204,6 +1206,7 @@ pub(super) mod types {
PyInt, PyStrRef, PyTupleRef, PyType, PyTypeRef, traceback::PyTracebackRef,
tuple::IntoPyTuple,
},
convert::ToPyObject,
convert::ToPyResult,
function::{ArgBytesLike, FuncArgs},
types::{Constructor, Initializer},
@@ -1212,6 +1215,117 @@ pub(super) mod types {
use itertools::Itertools;
use rustpython_common::str::UnicodeEscapeCodepoint;
pub(crate) trait ToOSErrorBuilder {
fn to_os_error_builder(&self, vm: &VirtualMachine) -> OSErrorBuilder;
}
pub struct OSErrorBuilder {
exc_type: PyTypeRef,
errno: Option<i32>,
strerror: Option<PyObjectRef>,
filename: Option<PyObjectRef>,
#[cfg(windows)]
winerror: Option<PyObjectRef>,
filename2: Option<PyObjectRef>,
}
impl OSErrorBuilder {
#[must_use]
pub fn with_subtype(
exc_type: PyTypeRef,
errno: Option<i32>,
strerror: impl ToPyObject,
vm: &VirtualMachine,
) -> Self {
let strerror = strerror.to_pyobject(vm);
Self {
exc_type,
errno,
strerror: Some(strerror),
filename: None,
#[cfg(windows)]
winerror: None,
filename2: None,
}
}
#[must_use]
pub fn with_errno(errno: i32, strerror: impl ToPyObject, vm: &VirtualMachine) -> Self {
let exc_type = crate::exceptions::errno_to_exc_type(errno, vm)
.unwrap_or(vm.ctx.exceptions.os_error)
.to_owned();
Self::with_subtype(exc_type, Some(errno), strerror, vm)
}
// #[must_use]
// pub(crate) fn errno(mut self, errno: i32) -> Self {
// self.errno.replace(errno);
// self
// }
#[must_use]
pub(crate) fn filename(mut self, filename: PyObjectRef) -> Self {
self.filename.replace(filename);
self
}
#[must_use]
pub(crate) fn filename2(mut self, filename: PyObjectRef) -> Self {
self.filename2.replace(filename);
self
}
#[must_use]
#[cfg(windows)]
pub(crate) fn winerror(mut self, winerror: PyObjectRef) -> Self {
self.winerror.replace(winerror);
self
}
pub fn build(self, vm: &VirtualMachine) -> PyRef<PyOSError> {
let OSErrorBuilder {
exc_type,
errno,
strerror,
filename,
#[cfg(windows)]
winerror,
filename2,
} = self;
let args = if let Some(errno) = errno {
#[cfg(windows)]
let winerror = winerror.to_pyobject(vm);
#[cfg(not(windows))]
let winerror = vm.ctx.none();
vec![
errno.to_pyobject(vm),
strerror.to_pyobject(vm),
filename.to_pyobject(vm),
winerror,
filename2.to_pyobject(vm),
]
} else {
vec![strerror.to_pyobject(vm)]
};
let payload = PyOSError::py_new(&exc_type, args.clone().into(), vm)
.expect("new_os_error usage error");
let os_error = payload
.into_ref_with_type(vm, exc_type)
.expect("new_os_error usage error");
PyOSError::slot_init(os_error.as_object().to_owned(), args.into(), vm)
.expect("new_os_error usage error");
os_error
}
}
impl crate::convert::IntoPyException for OSErrorBuilder {
fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef {
self.build(vm).upcast()
}
}
// Re-export exception group types from dedicated module
pub use crate::exception_group::types::PyBaseExceptionGroup;

View File

@@ -2,10 +2,8 @@ use rustpython_common::crt_fd;
use crate::{
PyObjectRef, PyResult, VirtualMachine,
builtins::PyBaseExceptionRef,
convert::{IntoPyException, ToPyException, ToPyObject, TryFromObject},
function::FsPath,
object::AsObject,
};
use std::path::{Path, PathBuf};
@@ -144,62 +142,17 @@ impl OsPathOrFd<'_> {
}
}
// TODO: preserve the input `PyObjectRef` of filename and filename2 (Failing check `self.assertIs(err.filename, name, str(func)`)
pub struct IOErrorBuilder<'a> {
error: &'a std::io::Error,
filename: Option<OsPathOrFd<'a>>,
filename2: Option<OsPathOrFd<'a>>,
}
impl<'a> IOErrorBuilder<'a> {
pub const fn new(error: &'a std::io::Error) -> Self {
Self {
error,
filename: None,
filename2: None,
}
}
pub(crate) fn filename(mut self, filename: impl Into<OsPathOrFd<'a>>) -> Self {
let filename = filename.into();
self.filename.replace(filename);
self
}
pub(crate) fn filename2(mut self, filename: impl Into<OsPathOrFd<'a>>) -> Self {
let filename = filename.into();
self.filename2.replace(filename);
self
}
pub(crate) fn with_filename(
error: &'a std::io::Error,
impl crate::exceptions::OSErrorBuilder {
#[must_use]
pub(crate) fn with_filename<'a>(
error: &std::io::Error,
filename: impl Into<OsPathOrFd<'a>>,
vm: &VirtualMachine,
) -> PyBaseExceptionRef {
let zelf = IOErrorBuilder {
error,
filename: Some(filename.into()),
filename2: None,
};
zelf.to_pyexception(vm)
}
}
impl ToPyException for IOErrorBuilder<'_> {
fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
let exc = self.error.to_pyexception(vm);
if let Some(filename) = &self.filename {
exc.as_object()
.set_attr("filename", filename.filename(vm), vm)
.unwrap();
}
if let Some(filename2) = &self.filename2 {
exc.as_object()
.set_attr("filename2", filename2.filename(vm), vm)
.unwrap();
}
exc
) -> crate::builtins::PyBaseExceptionRef {
// TODO: return type to PyRef<PyOSError>
use crate::exceptions::ToOSErrorBuilder;
let builder = error.to_os_error_builder(vm);
let builder = builder.filename(filename.into().filename(vm));
builder.build(vm).upcast()
}
}

View File

@@ -14,11 +14,12 @@ use crate::{
builtins::{PyBaseExceptionRef, PyModule},
common::os::ErrorExt,
convert::{IntoPyException, ToPyException},
exceptions::{OSErrorBuilder, ToOSErrorBuilder},
};
pub use _io::{OpenArgs, io_open as open};
impl ToPyException for std::io::Error {
fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
impl ToOSErrorBuilder for std::io::Error {
fn to_os_error_builder(&self, vm: &VirtualMachine) -> OSErrorBuilder {
let errno = self.posix_errno();
#[cfg(windows)]
let msg = 'msg: {
@@ -53,23 +54,23 @@ impl ToPyException for std::io::Error {
#[cfg(not(any(windows, unix)))]
let msg = self.to_string();
#[allow(clippy::let_and_return)]
let exc = vm.new_errno_error(errno, msg);
#[cfg(windows)]
{
use crate::object::AsObject;
let winerror = if let Some(winerror) = self.raw_os_error() {
vm.new_pyobj(winerror)
} else {
vm.ctx.none()
};
#[allow(unused_mut)]
let mut builder = OSErrorBuilder::with_errno(errno, msg, vm);
// FIXME: manual setup winerror due to lack of OSError.__init__ support
exc.as_object()
.set_attr("winerror", vm.new_pyobj(winerror), vm)
.unwrap();
#[cfg(windows)]
if let Some(winerror) = self.raw_os_error() {
use crate::convert::ToPyObject;
builder = builder.winerror(winerror.to_pyobject(vm));
}
exc.upcast()
builder
}
}
impl ToPyException for std::io::Error {
fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
let builder = self.to_os_error_builder(vm);
builder.into_pyexception(vm)
}
}
@@ -4328,8 +4329,9 @@ mod fileio {
builtins::{PyBaseExceptionRef, PyUtf8Str, PyUtf8StrRef},
common::crt_fd,
convert::{IntoPyException, ToPyException},
exceptions::OSErrorBuilder,
function::{ArgBytesLike, ArgMemoryBuffer, OptionalArg, OptionalOption},
ospath::{IOErrorBuilder, OsPath, OsPathOrFd},
ospath::{OsPath, OsPathOrFd},
stdlib::os,
types::{Constructor, DefaultConstructor, Destructor, Initializer, Representable},
};
@@ -4526,7 +4528,7 @@ mod fileio {
let filename = OsPathOrFd::Path(path);
match fd {
Ok(fd) => (fd.into_raw(), Some(filename)),
Err(e) => return Err(IOErrorBuilder::with_filename(&e, filename, vm)),
Err(e) => return Err(OSErrorBuilder::with_filename(&e, filename, vm)),
}
}
};
@@ -4541,7 +4543,7 @@ mod fileio {
#[cfg(windows)]
{
if let Err(err) = fd_fstat {
return Err(IOErrorBuilder::with_filename(&err, filename, vm));
return Err(OSErrorBuilder::with_filename(&err, filename, vm));
}
}
#[cfg(any(unix, target_os = "wasi"))]
@@ -4550,12 +4552,12 @@ mod fileio {
Ok(status) => {
if (status.st_mode & libc::S_IFMT) == libc::S_IFDIR {
let err = std::io::Error::from_raw_os_error(libc::EISDIR);
return Err(IOErrorBuilder::with_filename(&err, filename, vm));
return Err(OSErrorBuilder::with_filename(&err, filename, vm));
}
}
Err(err) => {
if err.raw_os_error() == Some(libc::EBADF) {
return Err(IOErrorBuilder::with_filename(&err, filename, vm));
return Err(OSErrorBuilder::with_filename(&err, filename, vm));
}
}
}

View File

@@ -153,6 +153,7 @@ pub(super) mod _os {
AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
builtins::{
PyBytesRef, PyGenericAlias, PyIntRef, PyStrRef, PyTuple, PyTupleRef, PyTypeRef,
ToOSErrorBuilder,
},
common::{
crt_fd,
@@ -161,8 +162,9 @@ pub(super) mod _os {
suppress_iph,
},
convert::{IntoPyException, ToPyObject},
exceptions::OSErrorBuilder,
function::{ArgBytesLike, FsPath, FuncArgs, OptionalArg},
ospath::{IOErrorBuilder, OsPath, OsPathOrFd, OutputMode},
ospath::{OsPath, OsPathOrFd, OutputMode},
protocol::PyIterReturn,
recursion::ReprGuard,
types::{IterNext, Iterable, PyStructSequence, Representable, SelfIter, Unconstructible},
@@ -263,7 +265,7 @@ pub(super) mod _os {
crt_fd::open(&name, flags, mode)
}
};
fd.map_err(|err| IOErrorBuilder::with_filename(&err, name, vm))
fd.map_err(|err| OSErrorBuilder::with_filename(&err, name, vm))
}
#[pyfunction]
@@ -316,7 +318,7 @@ pub(super) mod _os {
} else {
fs::remove_file(&path)
};
res.map_err(|err| IOErrorBuilder::with_filename(&err, path, vm))
res.map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))
}
#[cfg(not(windows))]
@@ -334,7 +336,7 @@ pub(super) mod _os {
let res = unsafe { libc::mkdirat(fd, c_path.as_ptr(), mode as _) };
return if res < 0 {
let err = crate::common::os::errno_io_error();
Err(IOErrorBuilder::with_filename(&err, path, vm))
Err(OSErrorBuilder::with_filename(&err, path, vm))
} else {
Ok(())
};
@@ -344,7 +346,7 @@ pub(super) mod _os {
let res = unsafe { libc::mkdir(c_path.as_ptr(), mode as _) };
if res < 0 {
let err = crate::common::os::errno_io_error();
return Err(IOErrorBuilder::with_filename(&err, path, vm));
return Err(OSErrorBuilder::with_filename(&err, path, vm));
}
Ok(())
}
@@ -357,7 +359,7 @@ pub(super) mod _os {
#[pyfunction]
fn rmdir(path: OsPath, dir_fd: DirFd<'_, 0>, vm: &VirtualMachine) -> PyResult<()> {
let [] = dir_fd.0;
fs::remove_dir(&path).map_err(|err| IOErrorBuilder::with_filename(&err, path, vm))
fs::remove_dir(&path).map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))
}
const LISTDIR_FD: bool = cfg!(all(unix, not(target_os = "redox")));
@@ -373,13 +375,13 @@ pub(super) mod _os {
let dir_iter = match fs::read_dir(&path) {
Ok(iter) => iter,
Err(err) => {
return Err(IOErrorBuilder::with_filename(&err, path, vm));
return Err(OSErrorBuilder::with_filename(&err, path, vm));
}
};
dir_iter
.map(|entry| match entry {
Ok(entry_path) => Ok(path.mode.process_path(entry_path.file_name(), vm)),
Err(err) => Err(IOErrorBuilder::with_filename(&err, path.clone(), vm)),
Err(err) => Err(OSErrorBuilder::with_filename(&err, path.clone(), vm)),
})
.collect::<PyResult<_>>()?
}
@@ -546,7 +548,7 @@ pub(super) mod _os {
let mode = path.mode;
let [] = dir_fd.0;
let path =
fs::read_link(&path).map_err(|err| IOErrorBuilder::with_filename(&err, path, vm))?;
fs::read_link(&path).map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))?;
Ok(mode.process_path(path, vm))
}
@@ -859,7 +861,7 @@ pub(super) mod _os {
fn scandir(path: OptionalArg<OsPath>, vm: &VirtualMachine) -> PyResult {
let path = path.unwrap_or_else(|| OsPath::new_str("."));
let entries = fs::read_dir(&path.path)
.map_err(|err| IOErrorBuilder::with_filename(&err, path.clone(), vm))?;
.map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?;
Ok(ScandirIterator {
entries: PyRwLock::new(Some(entries)),
mode: path.mode,
@@ -1084,7 +1086,7 @@ pub(super) mod _os {
vm: &VirtualMachine,
) -> PyResult {
let stat = stat_inner(file.clone(), dir_fd, follow_symlinks)
.map_err(|err| IOErrorBuilder::with_filename(&err, file, vm))?
.map_err(|err| OSErrorBuilder::with_filename(&err, file, vm))?
.ok_or_else(|| crate::exceptions::cstring_error(vm))?;
Ok(StatResultData::from_stat(&stat, vm).to_pyobject(vm))
}
@@ -1115,7 +1117,7 @@ pub(super) mod _os {
#[pyfunction]
fn chdir(path: OsPath, vm: &VirtualMachine) -> PyResult<()> {
env::set_current_dir(&path.path)
.map_err(|err| IOErrorBuilder::with_filename(&err, path, vm))
.map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))
}
#[pyfunction]
@@ -1127,10 +1129,10 @@ pub(super) mod _os {
#[pyfunction(name = "replace")]
fn rename(src: OsPath, dst: OsPath, vm: &VirtualMachine) -> PyResult<()> {
fs::rename(&src.path, &dst.path).map_err(|err| {
IOErrorBuilder::new(&err)
.filename(src)
.filename2(dst)
.into_pyexception(vm)
let builder = err.to_os_error_builder(vm);
let builder = builder.filename(src.filename(vm));
let builder = builder.filename2(dst.filename(vm));
builder.build(vm).upcast()
})
}
@@ -1219,10 +1221,10 @@ pub(super) mod _os {
#[pyfunction]
fn link(src: OsPath, dst: OsPath, vm: &VirtualMachine) -> PyResult<()> {
fs::hard_link(&src.path, &dst.path).map_err(|err| {
IOErrorBuilder::new(&err)
.filename(src)
.filename2(dst)
.into_pyexception(vm)
let builder = err.to_os_error_builder(vm);
let builder = builder.filename(src.filename(vm));
let builder = builder.filename2(dst.filename(vm));
builder.build(vm).upcast()
})
}
@@ -1334,7 +1336,7 @@ pub(super) mod _os {
)
};
if ret < 0 {
Err(IOErrorBuilder::with_filename(
Err(OSErrorBuilder::with_filename(
&io::Error::last_os_error(),
path_for_err,
vm,
@@ -1385,14 +1387,14 @@ pub(super) mod _os {
.write(true)
.custom_flags(windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS)
.open(&path)
.map_err(|err| IOErrorBuilder::with_filename(&err, path.clone(), vm))?;
.map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?;
let ret = unsafe {
FileSystem::SetFileTime(f.as_raw_handle() as _, std::ptr::null(), &acc, &modif)
};
if ret == 0 {
Err(IOErrorBuilder::with_filename(
Err(OSErrorBuilder::with_filename(
&io::Error::last_os_error(),
path,
vm,
@@ -1565,7 +1567,7 @@ pub(super) mod _os {
error: std::io::Error,
path: OsPath,
) -> crate::builtins::PyBaseExceptionRef {
IOErrorBuilder::with_filename(&error, path, vm)
OSErrorBuilder::with_filename(&error, path, vm)
}
let path = OsPath::try_from_object(vm, path)?;

View File

@@ -26,8 +26,9 @@ pub mod module {
AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine,
builtins::{PyDictRef, PyInt, PyListRef, PyStrRef, PyTupleRef, PyType, PyUtf8StrRef},
convert::{IntoPyException, ToPyObject, TryFromObject},
exceptions::OSErrorBuilder,
function::{Either, KwArgs, OptionalArg},
ospath::{IOErrorBuilder, OsPath, OsPathOrFd},
ospath::{OsPath, OsPathOrFd},
stdlib::os::{_os, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory, fs_metadata},
types::{Constructor, Representable},
utils::ToCString,
@@ -412,7 +413,7 @@ pub mod module {
}
let metadata =
metadata.map_err(|err| IOErrorBuilder::with_filename(&err, path.clone(), vm))?;
metadata.map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?;
let user_id = metadata.uid();
let group_id = metadata.gid();
@@ -482,12 +483,12 @@ pub mod module {
#[cfg(not(target_os = "redox"))]
#[pyfunction]
fn chroot(path: OsPath, vm: &VirtualMachine) -> PyResult<()> {
use crate::ospath::IOErrorBuilder;
use crate::exceptions::OSErrorBuilder;
nix::unistd::chroot(&*path.path).map_err(|err| {
// Use `From<nix::Error> for io::Error` when it is available
let err = io::Error::from_raw_os_error(err as i32);
IOErrorBuilder::with_filename(&err, path, vm)
let io_err: io::Error = err.into();
OSErrorBuilder::with_filename(&io_err, path, vm)
})
}
@@ -533,7 +534,7 @@ pub mod module {
.map_err(|err| {
// Use `From<nix::Error> for io::Error` when it is available
let err = io::Error::from_raw_os_error(err as i32);
IOErrorBuilder::with_filename(&err, path, vm)
OSErrorBuilder::with_filename(&err, path, vm)
})
}
@@ -1031,7 +1032,7 @@ pub mod module {
permissions.set_mode(mode);
fs::set_permissions(&path, permissions)
};
body().map_err(|err| IOErrorBuilder::with_filename(&err, err_path, vm))
body().map_err(|err| OSErrorBuilder::with_filename(&err, err_path, vm))
}
#[cfg(not(target_os = "redox"))]
@@ -1093,7 +1094,7 @@ pub mod module {
Ok(())
} else {
let err = std::io::Error::last_os_error();
Err(IOErrorBuilder::with_filename(&err, path, vm))
Err(OSErrorBuilder::with_filename(&err, path, vm))
}
}
@@ -1554,7 +1555,7 @@ pub mod module {
};
if let Err(err) = ret {
let err = err.into();
return Err(IOErrorBuilder::with_filename(&err, self.path, vm));
return Err(OSErrorBuilder::with_filename(&err, self.path, vm));
}
}
}
@@ -1653,7 +1654,7 @@ pub mod module {
nix::spawn::posix_spawn(&*path, &file_actions, &attrp, &args, &env)
};
ret.map(Into::into)
.map_err(|err| IOErrorBuilder::with_filename(&err.into(), self.path, vm))
.map_err(|err| OSErrorBuilder::with_filename(&err.into(), self.path, vm))
}
}
@@ -2126,7 +2127,7 @@ pub mod module {
if Errno::last_raw() == 0 {
Ok(None)
} else {
Err(IOErrorBuilder::with_filename(
Err(OSErrorBuilder::with_filename(
&io::Error::from(Errno::last()),
path,
vm,

View File

@@ -1,5 +1,5 @@
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef,
AsObject, Py, PyObject, PyObjectRef, PyRef,
builtins::{
PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef, PyModule, PyOSError, PyStrRef,
PyType, PyTypeRef,
@@ -8,9 +8,9 @@ use crate::{
tuple::{IntoPyTuple, PyTupleRef},
},
convert::{ToPyException, ToPyObject},
exceptions::OSErrorBuilder,
function::{IntoPyNativeFn, PyMethodFlags},
scope::Scope,
types::Constructor,
vm::VirtualMachine,
};
use rustpython_compiler_core::SourceLocation;
@@ -119,26 +119,8 @@ impl VirtualMachine {
msg: impl ToPyObject,
) -> PyRef<PyOSError> {
debug_assert_eq!(exc_type.slots.basicsize, std::mem::size_of::<PyOSError>());
let msg = msg.to_pyobject(self);
fn new_os_subtype_error_impl(
vm: &VirtualMachine,
exc_type: PyTypeRef,
errno: Option<i32>,
msg: PyObjectRef,
) -> PyRef<PyOSError> {
let args = match errno {
Some(e) => vec![vm.new_pyobj(e), msg],
None => vec![msg],
};
let payload =
PyOSError::py_new(&exc_type, args.into(), vm).expect("new_os_error usage error");
payload
.into_ref_with_type(vm, exc_type)
.expect("new_os_error usage error")
}
new_os_subtype_error_impl(self, exc_type, errno, msg)
OSErrorBuilder::with_subtype(exc_type, errno, msg, self).build(self)
}
/// Instantiate an exception with no arguments.