Move free functions in vm::exceptions to vm methods

This commit is contained in:
Jeong YunWon
2021-09-25 20:35:11 +09:00
parent 6f902d2705
commit 256de1eb46
12 changed files with 211 additions and 213 deletions

View File

@@ -13,8 +13,8 @@ fn run(vm: &vm::VirtualMachine) -> vm::PyResult<()> {
let res = vm.run_code_obj(vm.new_code_object(module), scope);
if let Err(err) = res {
vm::exceptions::print_exception(&vm, err);
if let Err(exc) = res {
vm.print_exception(exc);
}
Ok(())

View File

@@ -76,8 +76,8 @@ def fib(n):
scope.globals.set_item("last", output, vm)?;
}
}
Err(e) => {
vm::exceptions::print_exception(vm, e);
Err(exc) => {
vm.print_exception(exc);
}
}
}

View File

@@ -45,9 +45,9 @@ extern crate log;
use clap::{App, AppSettings, Arg, ArgMatches};
use rustpython_vm::{
builtins::PyDictRef, builtins::PyInt, compile, exceptions::print_exception, match_class,
scope::Scope, stdlib::sys, InitParameter, Interpreter, ItemProtocol, PyObjectRef, PyResult,
PySettings, TryFromObject, TypeProtocol, VirtualMachine,
builtins::PyDictRef, builtins::PyInt, compile, match_class, scope::Scope, stdlib::sys,
InitParameter, Interpreter, ItemProtocol, PyObjectRef, PyResult, PySettings, TryFromObject,
TypeProtocol, VirtualMachine,
};
use std::convert::TryInto;
@@ -136,8 +136,8 @@ where
}
}
}
Err(err) => {
print_exception(vm, err);
Err(exc) => {
vm.print_exception(exc);
1
}
};

View File

@@ -5,7 +5,6 @@ use rustpython_vm::readline::{Readline, ReadlineResult};
use rustpython_vm::{
builtins::PyBaseExceptionRef,
compile::{self, CompileError, CompileErrorType},
exceptions::print_exception,
scope::Scope,
PyResult, TypeProtocol, VirtualMachine,
};
@@ -130,7 +129,7 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
repl.save_history(&repl_history_path).unwrap();
return Err(exc);
}
print_exception(vm, exc);
vm.print_exception(exc);
}
}
repl.save_history(&repl_history_path).unwrap();

View File

@@ -1,7 +1,6 @@
use crate::{
builtins::{PyBaseExceptionRef, PyStrRef},
common::lock::PyMutex,
exceptions,
frame::{ExecutionResult, FrameRef},
protocol::PyIterReturn,
IdProtocol, PyObjectRef, PyResult, TypeProtocol, VirtualMachine,
@@ -140,7 +139,7 @@ impl Coro {
vm: &VirtualMachine,
) -> PyResult<PyIterReturn> {
if self.closed.load() {
return Err(exceptions::normalize(exc_type, exc_val, exc_tb, vm)?);
return Err(vm.normalize_exception(exc_type, exc_val, exc_tb)?);
}
let result = self.run_with_context(gen, vm, |f| f.gen_throw(vm, exc_type, exc_val, exc_tb));
self.maybe_close(&result);

View File

@@ -31,48 +31,175 @@ impl PyValue for PyBaseException {
}
}
pub fn chain<T>(e1: PyResult<()>, e2: PyResult<T>) -> PyResult<T> {
match (e1, e2) {
(Err(e1), Err(e)) => {
e.set_context(Some(e1));
Err(e)
}
(Err(e), Ok(_)) | (Ok(()), Err(e)) => Err(e),
(Ok(()), Ok(close_res)) => Ok(close_res),
}
}
impl VirtualMachine {
// Why `impl VirtualMachine?
// These functions are natively free function in CPython - not methods of PyException
/// Print exception chain by calling sys.excepthook
pub fn print_exception(vm: &VirtualMachine, exc: PyBaseExceptionRef) {
let write_fallback = |exc, errstr| {
if let Ok(stderr) = sys::get_stderr(vm) {
let mut stderr = py_io::PyWriter(stderr, vm);
// if this fails stderr might be closed -- ignore it
let _ = writeln!(stderr, "{}", errstr);
let _ = write_exception(&mut stderr, vm, exc);
/// Print exception chain by calling sys.excepthook
pub fn print_exception(&self, exc: PyBaseExceptionRef) {
let vm = self;
let write_fallback = |exc, errstr| {
if let Ok(stderr) = sys::get_stderr(vm) {
let mut stderr = py_io::PyWriter(stderr, vm);
// if this fails stderr might be closed -- ignore it
let _ = writeln!(stderr, "{}", errstr);
let _ = self.write_exception(&mut stderr, exc);
} else {
eprintln!("{}\nlost sys.stderr", errstr);
let _ = self.write_exception(&mut py_io::IoWriter(io::stderr()), exc);
}
};
if let Ok(excepthook) = vm.get_attribute(vm.sys_module.clone(), "excepthook") {
let (exc_type, exc_val, exc_tb) = vm.split_exception(exc.clone());
if let Err(eh_exc) = vm.invoke(&excepthook, vec![exc_type, exc_val, exc_tb]) {
write_fallback(&eh_exc, "Error in sys.excepthook:");
write_fallback(&exc, "Original exception was:");
}
} else {
eprintln!("{}\nlost sys.stderr", errstr);
let _ = write_exception(&mut py_io::IoWriter(io::stderr()), vm, exc);
write_fallback(&exc, "missing sys.excepthook");
}
};
if let Ok(excepthook) = vm.get_attribute(vm.sys_module.clone(), "excepthook") {
let (exc_type, exc_val, exc_tb) = split(exc.clone(), vm);
if let Err(eh_exc) = vm.invoke(&excepthook, vec![exc_type, exc_val, exc_tb]) {
write_fallback(&eh_exc, "Error in sys.excepthook:");
write_fallback(&exc, "Original exception was:");
}
} else {
write_fallback(&exc, "missing sys.excepthook");
}
}
pub fn write_exception<W: Write>(
output: &mut W,
vm: &VirtualMachine,
exc: &PyBaseExceptionRef,
) -> Result<(), W::Error> {
let seen = &mut HashSet::<usize>::new();
write_exception_recursive(output, vm, exc, seen)
pub fn write_exception<W: Write>(
&self,
output: &mut W,
exc: &PyBaseExceptionRef,
) -> Result<(), W::Error> {
let seen = &mut HashSet::<usize>::new();
self.write_exception_recursive(output, exc, seen)
}
fn write_exception_recursive<W: Write>(
&self,
output: &mut W,
exc: &PyBaseExceptionRef,
seen: &mut HashSet<usize>,
) -> Result<(), W::Error> {
// This function should not be called directly,
// use `wite_exception` as a public interface.
// It is similar to `print_exception_recursive` from `CPython`.
seen.insert(exc.as_object().get_id());
#[allow(clippy::manual_map)]
if let Some((cause_or_context, msg)) = if let Some(cause) = exc.cause() {
// This can be a special case: `raise e from e`,
// we just ignore it and treat like `raise e` without any extra steps.
Some((
cause,
"\nThe above exception was the direct cause of the following exception:\n",
))
} else if let Some(context) = exc.context() {
// This can be a special case:
// e = ValueError('e')
// e.__context__ = e
// In this case, we just ignore
// `__context__` part from going into recursion.
Some((
context,
"\nDuring handling of the above exception, another exception occurred:\n",
))
} else {
None
} {
if !seen.contains(&cause_or_context.as_object().get_id()) {
self.write_exception_recursive(output, &cause_or_context, seen)?;
writeln!(output, "{}", msg)?;
} else {
seen.insert(cause_or_context.as_object().get_id());
}
}
self.write_exception_inner(output, exc)
}
/// Print exception with traceback
pub fn write_exception_inner<W: Write>(
&self,
output: &mut W,
exc: &PyBaseExceptionRef,
) -> Result<(), W::Error> {
let vm = self;
if let Some(tb) = exc.traceback.read().clone() {
writeln!(output, "Traceback (most recent call last):")?;
for tb in tb.iter() {
write_traceback_entry(output, &tb)?;
}
}
let varargs = exc.args();
let args_repr = vm.exception_args_as_string(varargs, true);
let exc_type = exc.class();
let exc_name = exc_type.name();
match args_repr.len() {
0 => writeln!(output, "{}", exc_name),
1 => writeln!(output, "{}: {}", exc_name, args_repr[0]),
_ => writeln!(
output,
"{}: ({})",
exc_name,
args_repr.into_iter().format(", ")
),
}
}
fn exception_args_as_string(&self, varargs: PyTupleRef, str_single: bool) -> Vec<PyStrRef> {
let vm = self;
let varargs = varargs.as_slice();
match varargs.len() {
0 => vec![],
1 => {
let args0_repr = if str_single {
vm.to_str(&varargs[0])
.unwrap_or_else(|_| PyStr::from("<element str() failed>").into_ref(vm))
} else {
vm.to_repr(&varargs[0])
.unwrap_or_else(|_| PyStr::from("<element repr() failed>").into_ref(vm))
};
vec![args0_repr]
}
_ => varargs
.iter()
.map(|vararg| {
vm.to_repr(vararg)
.unwrap_or_else(|_| PyStr::from("<element repr() failed>").into_ref(vm))
})
.collect(),
}
}
pub fn split_exception(
&self,
exc: PyBaseExceptionRef,
) -> (PyObjectRef, PyObjectRef, PyObjectRef) {
let tb = exc.traceback().into_pyobject(self);
(exc.clone_class().into(), exc.into(), tb)
}
/// Similar to PyErr_NormalizeException in CPython
pub fn normalize_exception(
&self,
exc_type: PyObjectRef,
exc_val: PyObjectRef,
exc_tb: PyObjectRef,
) -> PyResult<PyBaseExceptionRef> {
let ctor = ExceptionCtor::try_from_object(self, exc_type)?;
let exc = ctor.instantiate_value(exc_val, self)?;
if let Some(tb) = Option::<PyTracebackRef>::try_from_object(self, exc_tb)? {
exc.set_traceback(Some(tb));
}
Ok(exc)
}
pub fn invoke_exception(
&self,
cls: PyTypeRef,
args: Vec<PyObjectRef>,
) -> PyResult<PyBaseExceptionRef> {
// TODO: fast-path built-in exceptions by directly instantiating them? Is that really worth it?
let res = self.invoke(cls.as_object(), args)?;
PyBaseExceptionRef::try_from_object(self, res)
}
}
fn print_source_line<W: Write>(
@@ -117,107 +244,6 @@ fn write_traceback_entry<W: Write>(
Ok(())
}
fn write_exception_recursive<W: Write>(
output: &mut W,
vm: &VirtualMachine,
exc: &PyBaseExceptionRef,
seen: &mut HashSet<usize>,
) -> Result<(), W::Error> {
// This function should not be called directly,
// use `wite_exception` as a public interface.
// It is similar to `print_exception_recursive` from `CPython`.
seen.insert(exc.as_object().get_id());
#[allow(clippy::manual_map)]
if let Some((cause_or_context, msg)) = if let Some(cause) = exc.cause() {
// This can be a special case: `raise e from e`,
// we just ignore it and treat like `raise e` without any extra steps.
Some((
cause,
"\nThe above exception was the direct cause of the following exception:\n",
))
} else if let Some(context) = exc.context() {
// This can be a special case:
// e = ValueError('e')
// e.__context__ = e
// In this case, we just ignore
// `__context__` part from going into recursion.
Some((
context,
"\nDuring handling of the above exception, another exception occurred:\n",
))
} else {
None
} {
if !seen.contains(&cause_or_context.as_object().get_id()) {
write_exception_recursive(output, vm, &cause_or_context, seen)?;
writeln!(output, "{}", msg)?;
} else {
seen.insert(cause_or_context.as_object().get_id());
}
}
write_exception_inner(output, vm, exc)
}
/// Print exception with traceback
pub fn write_exception_inner<W: Write>(
output: &mut W,
vm: &VirtualMachine,
exc: &PyBaseExceptionRef,
) -> Result<(), W::Error> {
if let Some(tb) = exc.traceback.read().clone() {
writeln!(output, "Traceback (most recent call last):")?;
for tb in tb.iter() {
write_traceback_entry(output, &tb)?;
}
}
let varargs = exc.args();
let args_repr = exception_args_as_string(vm, varargs, true);
let exc_class = exc.class();
let exc_name = exc_class.name();
match args_repr.len() {
0 => writeln!(output, "{}", exc_name),
1 => writeln!(output, "{}: {}", exc_name, args_repr[0]),
_ => writeln!(
output,
"{}: ({})",
exc_name,
args_repr.into_iter().format(", ")
),
}
}
fn exception_args_as_string(
vm: &VirtualMachine,
varargs: PyTupleRef,
str_single: bool,
) -> Vec<PyStrRef> {
let varargs = varargs.as_slice();
match varargs.len() {
0 => vec![],
1 => {
let args0_repr = if str_single {
vm.to_str(&varargs[0])
.unwrap_or_else(|_| PyStr::from("<element str() failed>").into_ref(vm))
} else {
vm.to_repr(&varargs[0])
.unwrap_or_else(|_| PyStr::from("<element repr() failed>").into_ref(vm))
};
vec![args0_repr]
}
_ => varargs
.iter()
.map(|vararg| {
vm.to_repr(vararg)
.unwrap_or_else(|_| PyStr::from("<element repr() failed>").into_ref(vm))
})
.collect(),
}
}
#[derive(Clone)]
pub enum ExceptionCtor {
Class(PyTypeRef),
@@ -244,20 +270,10 @@ impl TryFromObject for ExceptionCtor {
}
}
pub fn invoke(
cls: PyTypeRef,
args: Vec<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PyBaseExceptionRef> {
// TODO: fast-path built-in exceptions by directly instantiating them? Is that really worth it?
let res = vm.invoke(cls.as_object(), args)?;
res.try_into_value(vm)
}
impl ExceptionCtor {
pub fn instantiate(self, vm: &VirtualMachine) -> PyResult<PyBaseExceptionRef> {
match self {
Self::Class(cls) => invoke(cls, vec![], vm),
Self::Class(cls) => vm.invoke_exception(cls, vec![]),
Self::Instance(exc) => Ok(exc),
}
}
@@ -286,35 +302,12 @@ impl ExceptionCtor {
exc @ PyBaseException => exc.args().as_slice().to_vec(),
obj => vec![obj],
});
invoke(cls, args, vm)
vm.invoke_exception(cls, args)
}
}
}
}
pub fn split(
exc: PyBaseExceptionRef,
vm: &VirtualMachine,
) -> (PyObjectRef, PyObjectRef, PyObjectRef) {
let tb = exc.traceback().into_pyobject(vm);
(exc.clone_class().into(), exc.into(), tb)
}
/// Similar to PyErr_NormalizeException in CPython
pub fn normalize(
exc_type: PyObjectRef,
exc_val: PyObjectRef,
exc_tb: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<PyBaseExceptionRef> {
let ctor = ExceptionCtor::try_from_object(vm, exc_type)?;
let exc = ctor.instantiate_value(exc_val, vm)?;
if let Some(tb) = Option::<PyTracebackRef>::try_from_object(vm, exc_tb)? {
exc.set_traceback(Some(tb));
}
Ok(exc)
}
#[derive(Debug, Clone)]
pub struct ExceptionZoo {
pub base_exception_type: PyTypeRef,
@@ -497,7 +490,7 @@ impl PyBaseException {
#[pymethod(magic)]
pub(super) fn str(&self, vm: &VirtualMachine) -> PyStrRef {
let str_args = exception_args_as_string(vm, self.args(), true);
let str_args = vm.exception_args_as_string(self.args(), true);
match str_args.into_iter().exactly_one() {
Err(i) if i.len() == 0 => PyStr::from("").into_ref(vm),
Ok(s) => s,
@@ -507,7 +500,7 @@ impl PyBaseException {
#[pymethod(magic)]
fn repr(zelf: PyRef<Self>, vm: &VirtualMachine) -> String {
let repr_args = exception_args_as_string(vm, zelf.args(), false);
let repr_args = vm.exception_args_as_string(zelf.args(), false);
let cls = zelf.class();
format!("{}({})", cls.name(), repr_args.iter().format(", "))
}
@@ -843,7 +836,7 @@ fn make_arg_getter(idx: usize) -> impl Fn(PyBaseExceptionRef) -> Option<PyObject
fn key_error_str(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyStrRef {
let args = exc.args();
if args.as_slice().len() == 1 {
exception_args_as_string(vm, args, false)
vm.exception_args_as_string(args, false)
.into_iter()
.exactly_one()
.unwrap()
@@ -923,7 +916,9 @@ impl serde::Serialize for SerializeException<'_> {
let rendered = {
let mut rendered = String::new();
write_exception(&mut rendered, self.vm, self.exc).map_err(S::Error::custom)?;
self.vm
.write_exception(&mut rendered, self.exc)
.map_err(S::Error::custom)?;
rendered
};
struc.serialize_field("rendered", &rendered)?;
@@ -953,7 +948,6 @@ pub(super) mod types {
use crate::common::lock::PyRwLock;
use crate::{
builtins::{traceback::PyTracebackRef, PyInt, PyTupleRef, PyTypeRef},
exceptions::invoke,
function::{FuncArgs, IntoPyResult},
PyObjectRef, PyRef, PyResult, VirtualMachine,
};
@@ -1192,7 +1186,7 @@ pub(super) mod types {
};
if error.is_some() {
Some(invoke(error?, args.to_vec(), vm))
Some(vm.invoke_exception(error?, args.to_vec()))
} else {
None
}

View File

@@ -9,7 +9,7 @@ use crate::{
},
bytecode,
coroutine::Coro,
exceptions::{self, ExceptionCtor},
exceptions::ExceptionCtor,
function::{FuncArgs, IntoPyResult},
protocol::{PyIter, PyIterReturn},
scope::Scope,
@@ -412,13 +412,13 @@ impl ExecutingFrame<'_> {
self.push_value(val);
self.run(vm)
} else {
let (ty, val, tb) = exceptions::split(err, vm);
let (ty, val, tb) = vm.split_exception(err);
self.gen_throw(vm, ty, val, tb)
}
});
}
}
let exception = exceptions::normalize(exc_type, exc_val, exc_tb, vm)?;
let exception = vm.normalize_exception(exc_type, exc_val, exc_tb)?;
match self.unwind_blocks(vm, UnwindReason::Raising { exception }) {
Ok(None) => self.run(vm),
Ok(Some(result)) => Ok(result),
@@ -801,7 +801,7 @@ impl ExecutingFrame<'_> {
let exit = self.pop_value();
let args = if let Some(exc) = exc {
exceptions::split(exc, vm)
vm.split_exception(exc)
} else {
(vm.ctx.none(), vm.ctx.none(), vm.ctx.none())
};

View File

@@ -81,7 +81,6 @@ mod _io {
PyBaseExceptionRef, PyByteArray, PyBytes, PyBytesRef, PyIntRef, PyMemoryView, PyStr,
PyStrRef, PyType, PyTypeRef,
},
exceptions,
function::{
ArgBytesLike, ArgIterable, ArgMemoryBuffer, FuncArgs, IntoPyObject, OptionalArg,
OptionalOption,
@@ -1539,7 +1538,7 @@ mod _io {
}
let flush_res = data.flush(vm);
let close_res = vm.call_method(data.raw.as_ref().unwrap(), "close", ());
exceptions::chain(flush_res, close_res)
exeption_chain(flush_res, close_res)
}
#[pymethod]
@@ -1555,7 +1554,7 @@ mod _io {
let data = zelf.lock(vm)?;
let raw = data.raw.as_ref().unwrap();
let close_res = vm.call_method(raw, "close", ());
exceptions::chain(flush_res, close_res)
exeption_chain(flush_res, close_res)
}
#[pymethod]
@@ -1644,6 +1643,17 @@ mod _io {
}
}
fn exeption_chain<T>(e1: PyResult<()>, e2: PyResult<T>) -> PyResult<T> {
match (e1, e2) {
(Err(e1), Err(e)) => {
e.set_context(Some(e1));
Err(e)
}
(Err(e), Ok(_)) | (Ok(()), Err(e)) => Err(e),
(Ok(()), Ok(close_res)) => Ok(close_res),
}
}
#[pyattr]
#[pyclass(name = "BufferedReader", base = "_BufferedIOBase")]
#[derive(Debug, Default, PyValue)]
@@ -1831,7 +1841,7 @@ mod _io {
fn close(&self, vm: &VirtualMachine) -> PyResult {
let write_res = self.write.close_strict(vm).map(drop);
let read_res = self.read.close_strict(vm);
exceptions::chain(write_res, read_res)
exeption_chain(write_res, read_res)
}
}
@@ -2855,7 +2865,7 @@ mod _io {
}
let flush_res = vm.call_method(zelf.as_object(), "flush", ()).map(drop);
let close_res = vm.call_method(&buffer, "close", ()).map(drop);
exceptions::chain(flush_res, close_res)
exeption_chain(flush_res, close_res)
}
#[pyproperty]
fn closed(&self, vm: &VirtualMachine) -> PyResult {

View File

@@ -12,7 +12,6 @@ mod sys {
};
use crate::{
builtins::{PyDictRef, PyListRef, PyNamespace, PyStr, PyStrRef, PyTupleRef, PyTypeRef},
exceptions,
frame::FrameRef,
function::{FuncArgs, OptionalArg, PosArgs},
stdlib::builtins,
@@ -308,15 +307,15 @@ mod sys {
exc_tb: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<()> {
let exc = exceptions::normalize(exc_type, exc_val, exc_tb, vm)?;
let exc = vm.normalize_exception(exc_type, exc_val, exc_tb)?;
let stderr = super::get_stderr(vm)?;
exceptions::write_exception(&mut crate::py_io::PyWriter(stderr, vm), vm, &exc)
vm.write_exception(&mut crate::py_io::PyWriter(stderr, vm), &exc)
}
#[pyfunction]
fn exc_info(vm: &VirtualMachine) -> (PyObjectRef, PyObjectRef, PyObjectRef) {
match vm.topmost_exception() {
Some(exception) => exceptions::split(exception, vm),
Some(exception) => vm.split_exception(exception),
None => (vm.ctx.none(), vm.ctx.none(), vm.ctx.none()),
}
}

View File

@@ -6,7 +6,6 @@ pub(crate) use _thread::{make_module, RawRMutex};
pub(crate) mod _thread {
use crate::{
builtins::{PyDictRef, PyStrRef, PyTupleRef, PyTypeRef},
exceptions,
function::{ArgCallable, FuncArgs, IntoPyException, KwArgs, OptionalArg},
py_io,
slots::{SlotConstructor, SlotGetattro, SlotSetattro},
@@ -266,7 +265,7 @@ pub(crate) mod _thread {
.as_ref()
.map_or("<object repr() failed>", |s| s.as_str());
writeln!(*stderr, "Exception ignored in thread started by: {}", repr)
.and_then(|()| exceptions::write_exception(&mut stderr, vm, &exc))
.and_then(|()| vm.write_exception(&mut stderr, &exc))
.ok();
}
}

View File

@@ -17,7 +17,6 @@ use crate::{
bytecode,
codecs::CodecsRegistry,
common::{ascii, hash::HashSecret, lock::PyMutex, rc::PyRc},
exceptions,
frame::{ExecutionResult, Frame, FrameRef},
frozen,
function::{FuncArgs, IntoFuncArgs, IntoPyObject},
@@ -478,7 +477,7 @@ impl VirtualMachine {
stdlib::sys::PyStderr(self),
"Error in atexit._run_exitfuncs:"
);
exceptions::print_exception(self, e);
self.print_exception(e);
}
}
}
@@ -819,7 +818,7 @@ impl VirtualMachine {
{
let show_backtrace = std::env::var_os("RUST_BACKTRACE").map_or(false, |v| &v != "0");
let after = if show_backtrace {
exceptions::print_exception(self, exc);
self.print_exception(exc);
"exception backtrace above"
} else {
"run with RUST_BACKTRACE=1 to see Python backtrace"
@@ -835,7 +834,7 @@ impl VirtualMachine {
fn error(s: &str);
}
let mut s = String::new();
exceptions::write_exception(&mut s, self, &exc).unwrap();
self.write_exception(&mut s, &exc).unwrap();
error(&s);
panic!("{}; exception backtrace above", msg)
}

View File

@@ -575,11 +575,10 @@ impl AwaitPromise {
exc_tb: OptionalArg,
vm: &VirtualMachine,
) -> PyResult {
let err = rustpython_vm::exceptions::normalize(
let err = vm.normalize_exception(
exc_type,
exc_val.unwrap_or_none(vm),
exc_tb.unwrap_or_none(vm),
vm,
)?;
Err(err)
}