Skip to content
Open
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
15 changes: 6 additions & 9 deletions crates/vm/src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1385,14 +1385,8 @@ impl OSErrorBuilder {
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_lazy_dict(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
vm.new_payload_exception::<PyOSError>(exc_type, args.into())
.expect("new_os_error usage error")
}
}

Expand Down Expand Up @@ -2144,7 +2138,10 @@ pub(super) mod types {
.downcast_ref::<PyInt>()
.and_then(|errno| errno.try_to_primitive::<i32>(vm).ok())
.and_then(|errno| super::errno_to_exc_type(errno, vm))
.and_then(|typ| vm.invoke_exception(typ, args_vec).ok())
.and_then(|typ| {
vm.new_payload_exception::<Self>(typ.to_owned(), args_vec.into())
.ok()
})
{
return error.to_pyresult(vm);
}
Expand Down
60 changes: 35 additions & 25 deletions crates/vm/src/stdlib/_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ cfg_select! {
}

use crate::{
AsObject, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine, builtins::PyModule,
AsObject, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine,
builtins::{PyModule, PyOSError},
};
pub use _io::{OpenArgs, io_open as open};
use rustpython_host_env::io as host_io;
Expand Down Expand Up @@ -943,14 +944,17 @@ mod _io {
Some(n) => n,
None => {
// BlockingIOError(errno, msg, characters_written=0)
return Err(vm.invoke_exception(
vm.ctx.exceptions.blocking_io_error,
vec![
vm.new_pyobj(EAGAIN),
vm.new_pyobj("write could not complete without blocking"),
vm.new_pyobj(0),
],
)?);
return Err(vm
.new_payload_exception::<PyOSError>(
vm.ctx.exceptions.blocking_io_error.to_owned(),
vec![
vm.new_pyobj(EAGAIN),
vm.new_pyobj("write could not complete without blocking"),
vm.new_pyobj(0),
]
.into(),
)?
.upcast());
}
};
self.write_pos += n as Offset;
Expand Down Expand Up @@ -1154,14 +1158,17 @@ mod _io {
self.buffer[self.write_end as usize..][..avail].copy_from_slice(&buf[..avail]);
self.write_end += avail as Offset;
self.pos += avail as Offset;
return Err(vm.invoke_exception(
vm.ctx.exceptions.blocking_io_error,
vec![
vm.new_pyobj(EAGAIN),
vm.new_pyobj("write could not complete without blocking"),
vm.new_pyobj(avail),
],
)?);
return Err(vm
.new_payload_exception::<PyOSError>(
vm.ctx.exceptions.blocking_io_error.to_owned(),
vec![
vm.new_pyobj(EAGAIN),
vm.new_pyobj("write could not complete without blocking"),
vm.new_pyobj(avail),
]
.into(),
)?
.upcast());
}
Err(e) => return Err(e),
}
Expand Down Expand Up @@ -1200,14 +1207,17 @@ mod _io {
self.write_end = buffer_size;
// BlockingIOError(errno, msg, characters_written)
let chars_written = written + buffer_len;
return Err(vm.invoke_exception(
vm.ctx.exceptions.blocking_io_error,
vec![
vm.new_pyobj(EAGAIN),
vm.new_pyobj("write could not complete without blocking"),
vm.new_pyobj(chars_written),
],
)?);
return Err(vm
.new_payload_exception::<PyOSError>(
vm.ctx.exceptions.blocking_io_error.to_owned(),
vec![
vm.new_pyobj(EAGAIN),
vm.new_pyobj("write could not complete without blocking"),
vm.new_pyobj(chars_written),
]
.into(),
)?
.upcast());
}
None => break,
}
Expand Down
2 changes: 1 addition & 1 deletion crates/vm/src/stdlib/_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ pub(crate) mod _thread {

#[pyfunction]
fn exit(vm: &VirtualMachine) -> PyResult {
Err(vm.invoke_exception(vm.ctx.exceptions.system_exit, vec![])?)
Err(vm.new_system_exit(vec![].into()))
}

thread_local!(static SENTINELS: RefCell<Vec<PyRef<Lock>>> = const { RefCell::new(Vec::new()) });
Expand Down
2 changes: 1 addition & 1 deletion crates/vm/src/stdlib/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,7 @@ mod builtins {
#[pyfunction]
pub(super) fn exit(exit_code_arg: OptionalArg<PyObjectRef>, vm: &VirtualMachine) -> PyResult {
let code = exit_code_arg.unwrap_or_else(|| vm.ctx.new_int(0).into());
Err(vm.invoke_exception(vm.ctx.exceptions.system_exit, vec![code])?)
Err(vm.new_system_exit(vec![code].into()))
}

#[derive(Debug, Default, FromArgs)]
Expand Down
3 changes: 1 addition & 2 deletions crates/vm/src/stdlib/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -776,8 +776,7 @@ pub mod sys {
} else {
vec![status]
};
let exc = vm.invoke_exception(vm.ctx.exceptions.system_exit, args)?;
Err(exc)
Err(vm.new_system_exit(args.into()))
}

#[pyfunction]
Expand Down
2 changes: 1 addition & 1 deletion crates/vm/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2167,7 +2167,7 @@ impl VirtualMachine {
if self.state.finalizing.load(Ordering::Acquire) && !self.is_main_thread() {
// once finalization starts,
// non-main Python threads should stop running bytecode.
return Err(self.invoke_exception(self.ctx.exceptions.system_exit, vec![])?);
return Err(self.new_system_exit(vec![].into()));
}

// Suspend this thread if stop-the-world is in progress
Expand Down
53 changes: 41 additions & 12 deletions crates/vm/src/vm/vm_new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@ use rustpython_compiler::{CompileError, ParseError};
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
builtins::{
PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef, PyModule, PyOSError, PyStrRef,
PyType, PyTypeRef,
PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef, PyModule, PyOSError,
PyStopIteration, PyStrRef, PySystemExit, PyType, PyTypeRef,
builtin_func::PyNativeFunction,
descriptor::PyMethodDescriptor,
tuple::{IntoPyTuple, PyTupleRef},
},
convert::{ToPyException, ToPyObject},
exceptions::OSErrorBuilder,
function::{IntoPyNativeFn, PyMethodFlags},
function::{FuncArgs, IntoPyNativeFn, PyMethodFlags},
scope::Scope,
set_attrs,
types::{Constructor, Initializer},
vm::VirtualMachine,
};

Expand Down Expand Up @@ -353,6 +354,26 @@ impl VirtualMachine {
.expect("vm.new_exception() called with an invalid exception type")
}

/// Construct a built-in exception type that carries a payload, directly
/// (`py_new` + `slot_init`), without routing through `PyType::call`.
/// Only valid for a built-in `T` whose exact type is known at compile time.
pub fn new_payload_exception<T>(&self, cls: PyTypeRef, args: FuncArgs) -> PyResult<PyRef<T>>
where
T: Constructor<Args = FuncArgs> + Initializer,
{
debug_assert_eq!(
cls.slots.basicsize,
size_of::<T>(),
"vm.new_payload_exception::<{}>() called with mismatched type '{}'",
core::any::type_name::<T>(),
cls.name()
);
let payload = T::py_new(&cls, args.clone(), self)?;
let exc = payload.into_ref_with_type_lazy_dict(self, cls)?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calling into_ref_with_type_lazy_dict came from OSErrorBuilder. I don't know this part well, so I'd like to ask if this is safe for every exception types or not.

T::slot_init(exc.as_object().to_owned(), args, self)?;
Ok(exc)
}

pub fn new_os_error(&self, msg: impl ToPyObject) -> PyRef<PyBaseException> {
self.new_os_subtype_error(self.ctx.exceptions.os_error.to_owned(), None, msg)
.upcast()
Expand Down Expand Up @@ -893,16 +914,24 @@ impl VirtualMachine {
exc
}

pub fn new_stop_iteration(&self, value: Option<PyObjectRef>) -> PyBaseExceptionRef {
let stop_iteration_error = self.ctx.exceptions.stop_iteration;
let args = if let Some(value) = value {
vec![value]
} else {
Vec::new()
};
let exc = self.invoke_exception(stop_iteration_error, args);
pub fn new_system_exit(&self, args: FuncArgs) -> PyBaseExceptionRef {
self.new_payload_exception::<PySystemExit>(self.ctx.exceptions.system_exit.to_owned(), args)
.expect("SystemExit construction from internal args is infallible")
.upcast()
}

exc.expect("StopIteration is a BaseException Subclass.")
pub fn new_stop_iteration(&self, value: Option<PyObjectRef>) -> PyBaseExceptionRef {
let args: FuncArgs = value.map(|v| vec![v]).unwrap_or_default().into();
self.new_payload_exception::<PyStopIteration>(
self.ctx.exceptions.stop_iteration.to_owned(),
args,
)
.expect("StopIteration construction from internal args is infallible")
// `PyStopIteration` -> `PyBaseException` needs an owned upcast here.
// `.upcast()` does a runtime downcast; the zero-cost `upcast_ref`/`to_base`
// only return `&Py<_>`, which doesn't fit the owned `PyBaseExceptionRef`
// return. Keeping the downcast for now.
.upcast()
}

fn new_downcast_error(
Expand Down
Loading