use crate::builtins::pystr::{PyStr, PyStrRef}; use crate::builtins::pytype::{PyType, PyTypeRef}; use crate::builtins::singletons::{PyNone, PyNoneRef}; use crate::builtins::traceback::PyTracebackRef; use crate::builtins::tuple::{PyTuple, PyTupleRef}; use crate::common::lock::PyRwLock; use crate::function::FuncArgs; use crate::py_io::{self, Write}; use crate::sysmodule; use crate::types::create_type_with_slots; use crate::StaticType; use crate::VirtualMachine; use crate::{ IdProtocol, IntoPyObject, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crossbeam_utils::atomic::AtomicCell; use itertools::Itertools; use std::collections::HashSet; use std::fmt; use std::fs::File; use std::io::{self, BufRead, BufReader}; #[pyclass(module = false, name = "BaseException")] pub struct PyBaseException { traceback: PyRwLock>, cause: PyRwLock>, context: PyRwLock>, suppress_context: AtomicCell, args: PyRwLock, } impl fmt::Debug for PyBaseException { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // TODO: implement more detailed, non-recursive Debug formatter f.write_str("PyBaseException") } } pub type PyBaseExceptionRef = PyRef; pub trait IntoPyException { fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef; } impl PyValue for PyBaseException { fn class(vm: &VirtualMachine) -> &PyTypeRef { &vm.ctx.exceptions.base_exception_type } } #[pyimpl(flags(BASETYPE, HAS_DICT))] impl PyBaseException { pub(crate) fn new(args: Vec, vm: &VirtualMachine) -> PyBaseException { PyBaseException { traceback: PyRwLock::new(None), cause: PyRwLock::new(None), context: PyRwLock::new(None), suppress_context: AtomicCell::new(false), args: PyRwLock::new(PyTupleRef::with_elements(args, &vm.ctx)), } } #[pyslot] pub(crate) fn tp_new( cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine, ) -> PyResult> { PyBaseException::new(args.args, vm).into_ref_with_type(vm, cls) } #[pymethod(magic)] pub(crate) fn init(zelf: PyRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { *zelf.args.write() = PyTupleRef::with_elements(args.args, &vm.ctx); Ok(()) } pub fn get_arg(&self, idx: usize) -> Option { self.args.read().as_slice().get(idx).cloned() } #[pyproperty] pub fn args(&self) -> PyTupleRef { self.args.read().clone() } #[pyproperty(setter)] fn set_args(&self, args: PyIterable, vm: &VirtualMachine) -> PyResult<()> { let args = args.iter(vm)?.collect::>>()?; *self.args.write() = PyTupleRef::with_elements(args, &vm.ctx); Ok(()) } #[pyproperty(magic)] pub fn traceback(&self) -> Option { self.traceback.read().clone() } #[pyproperty(name = "__traceback__", setter)] pub fn set_traceback(&self, traceback: Option) { *self.traceback.write() = traceback; } #[pyproperty(magic)] pub fn cause(&self) -> Option { self.cause.read().clone() } #[pyproperty(name = "__cause__", setter)] pub fn set_cause(&self, cause: Option) { let mut c = self.cause.write(); self.set_suppress_context(true); *c = cause; } #[pyproperty(magic)] pub fn context(&self) -> Option { self.context.read().clone() } #[pyproperty(name = "__context__", setter)] pub fn set_context(&self, context: Option) { *self.context.write() = context; } #[pyproperty(name = "__suppress_context__")] fn get_suppress_context(&self) -> bool { self.suppress_context.load() } #[pyproperty(name = "__suppress_context__", setter)] fn set_suppress_context(&self, suppress_context: bool) { self.suppress_context.store(suppress_context); } #[pymethod] fn with_traceback(zelf: PyRef, tb: Option) -> PyResult { *zelf.traceback.write() = tb; Ok(zelf.as_object().clone()) } #[pymethod(magic)] fn str(&self, vm: &VirtualMachine) -> PyStrRef { let str_args = exception_args_as_string(vm, self.args(), true); match str_args.into_iter().exactly_one() { Err(i) if i.len() == 0 => PyStr::from("").into_ref(vm), Ok(s) => s, Err(i) => PyStr::from(format!("({})", i.format(", "))).into_ref(vm), } } #[pymethod(magic)] fn repr(zelf: PyRef, vm: &VirtualMachine) -> String { let repr_args = exception_args_as_string(vm, zelf.args(), false); let cls = zelf.class(); format!("{}({})", cls.name, repr_args.iter().format(", ")) } } pub fn chain(e1: PyResult<()>, e2: PyResult) -> PyResult { 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), } } /// Print exception chain by calling sys.excepthook pub fn print_exception(vm: &VirtualMachine, exc: PyBaseExceptionRef) { let write_fallback = |exc, errstr| { if let Ok(stderr) = sysmodule::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); } else { eprintln!("{}\nlost sys.stderr", errstr); let _ = write_exception(&mut py_io::IoWriter(io::stderr()), vm, exc); } }; 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( output: &mut W, vm: &VirtualMachine, exc: &PyBaseExceptionRef, ) -> Result<(), W::Error> { let seen = &mut HashSet::::new(); write_exception_recursive(output, vm, exc, seen) } fn print_source_line( output: &mut W, filename: &str, lineno: usize, ) -> Result<(), W::Error> { // TODO: use io.open() method instead, when available, according to https://github.com/python/cpython/blob/master/Python/traceback.c#L393 // TODO: support different encodings let file = match File::open(filename) { Ok(file) => file, Err(_) => return Ok(()), }; let file = BufReader::new(file); for (i, line) in file.lines().enumerate() { if i + 1 == lineno { if let Ok(line) = line { // Indented with 4 spaces writeln!(output, " {}", line.trim_start())?; } return Ok(()); } } Ok(()) } /// Print exception occurrence location from traceback element fn write_traceback_entry( output: &mut W, tb_entry: &PyTracebackRef, ) -> Result<(), W::Error> { let filename = tb_entry.frame.code.source_path.as_str(); writeln!( output, r##" File "{}", line {}, in {}"##, filename, tb_entry.lineno, tb_entry.frame.code.obj_name )?; print_source_line(output, filename, tb_entry.lineno)?; Ok(()) } fn write_exception_recursive( output: &mut W, vm: &VirtualMachine, exc: &PyBaseExceptionRef, seen: &mut HashSet, ) -> 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( 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_name = exc.class().name.clone(); 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 { 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("").into_ref(vm)) } else { vm.to_repr(&varargs[0]) .unwrap_or_else(|_| PyStr::from("").into_ref(vm)) }; vec![args0_repr] } _ => varargs .iter() .map(|vararg| { vm.to_repr(vararg) .unwrap_or_else(|_| PyStr::from("").into_ref(vm)) }) .collect(), } } #[derive(Clone)] pub enum ExceptionCtor { Class(PyTypeRef), Instance(PyBaseExceptionRef), } impl TryFromObject for ExceptionCtor { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { obj.downcast::() .and_then(|cls| { if cls.issubclass(&vm.ctx.exceptions.base_exception_type) { Ok(Self::Class(cls)) } else { Err(cls.into_object()) } }) .or_else(|obj| obj.downcast::().map(Self::Instance)) .map_err(|obj| { vm.new_type_error(format!( "exceptions must be classes or instances deriving from BaseException, not {}", obj.class().name )) }) } } pub fn invoke( cls: PyTypeRef, args: Vec, vm: &VirtualMachine, ) -> PyResult { // TODO: fast-path built-in exceptions by directly instantiating them? Is that really worth it? let res = vm.invoke(cls.as_object(), args)?; PyBaseExceptionRef::try_from_object(vm, res) } impl ExceptionCtor { pub fn instantiate(self, vm: &VirtualMachine) -> PyResult { match self { Self::Class(cls) => invoke(cls, vec![], vm), Self::Instance(exc) => Ok(exc), } } pub fn instantiate_value( self, value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { let exc_inst = value.clone().downcast::().ok(); match (self, exc_inst) { // both are instances; which would we choose? (Self::Instance(_exc_a), Some(_exc_b)) => { Err(vm .new_type_error("instance exception may not have a separate value".to_owned())) } // if the "type" is an instance and the value isn't, use the "type" (Self::Instance(exc), None) => Ok(exc), // if the value is an instance of the type, use the instance value (Self::Class(cls), Some(exc)) if exc.isinstance(&cls) => Ok(exc), // otherwise; construct an exception of the type using the value as args (Self::Class(cls), _) => { let args = match_class!(match value { PyNone => vec![], tup @ PyTuple => tup.as_slice().to_vec(), exc @ PyBaseException => exc.args().as_slice().to_vec(), obj => vec![obj], }); invoke(cls, args, vm) } } } } pub fn split( exc: PyBaseExceptionRef, vm: &VirtualMachine, ) -> (PyObjectRef, PyObjectRef, PyObjectRef) { let tb = exc.traceback().into_pyobject(vm); (exc.clone_class().into_object(), exc.into_object(), tb) } /// Similar to PyErr_NormalizeException in CPython pub fn normalize( exc_type: PyObjectRef, exc_val: PyObjectRef, exc_tb: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { let ctor = ExceptionCtor::try_from_object(vm, exc_type)?; let exc = ctor.instantiate_value(exc_val, vm)?; if let Some(tb) = Option::::try_from_object(vm, exc_tb)? { exc.set_traceback(Some(tb)); } Ok(exc) } #[derive(Debug, Clone)] pub struct ExceptionZoo { pub base_exception_type: PyTypeRef, pub system_exit: PyTypeRef, pub keyboard_interrupt: PyTypeRef, pub generator_exit: PyTypeRef, pub exception_type: PyTypeRef, pub stop_iteration: PyTypeRef, pub stop_async_iteration: PyTypeRef, pub arithmetic_error: PyTypeRef, pub floating_point_error: PyTypeRef, pub overflow_error: PyTypeRef, pub zero_division_error: PyTypeRef, pub assertion_error: PyTypeRef, pub attribute_error: PyTypeRef, pub buffer_error: PyTypeRef, pub eof_error: PyTypeRef, pub import_error: PyTypeRef, pub module_not_found_error: PyTypeRef, pub lookup_error: PyTypeRef, pub index_error: PyTypeRef, pub key_error: PyTypeRef, pub memory_error: PyTypeRef, pub name_error: PyTypeRef, pub unbound_local_error: PyTypeRef, pub os_error: PyTypeRef, pub blocking_io_error: PyTypeRef, pub child_process_error: PyTypeRef, pub connection_error: PyTypeRef, pub broken_pipe_error: PyTypeRef, pub connection_aborted_error: PyTypeRef, pub connection_refused_error: PyTypeRef, pub connection_reset_error: PyTypeRef, pub file_exists_error: PyTypeRef, pub file_not_found_error: PyTypeRef, pub interrupted_error: PyTypeRef, pub is_a_directory_error: PyTypeRef, pub not_a_directory_error: PyTypeRef, pub permission_error: PyTypeRef, pub process_lookup_error: PyTypeRef, pub timeout_error: PyTypeRef, pub reference_error: PyTypeRef, pub runtime_error: PyTypeRef, pub not_implemented_error: PyTypeRef, pub recursion_error: PyTypeRef, pub syntax_error: PyTypeRef, pub target_scope_error: PyTypeRef, pub indentation_error: PyTypeRef, pub tab_error: PyTypeRef, pub system_error: PyTypeRef, pub type_error: PyTypeRef, pub value_error: PyTypeRef, pub unicode_error: PyTypeRef, pub unicode_decode_error: PyTypeRef, pub unicode_encode_error: PyTypeRef, pub unicode_translate_error: PyTypeRef, #[cfg(feature = "jit")] pub jit_error: PyTypeRef, pub warning: PyTypeRef, pub deprecation_warning: PyTypeRef, pub pending_deprecation_warning: PyTypeRef, pub runtime_warning: PyTypeRef, pub syntax_warning: PyTypeRef, pub user_warning: PyTypeRef, pub future_warning: PyTypeRef, pub import_warning: PyTypeRef, pub unicode_warning: PyTypeRef, pub bytes_warning: PyTypeRef, pub resource_warning: PyTypeRef, } pub fn exception_slots() -> crate::slots::PyTypeSlots { let mut slots = PyBaseException::make_slots(); // make_slots produces it with a tp_name of BaseException, which is usually wrong slots.name.get_mut().take(); slots } pub fn create_exception_type(name: &str, base: &PyTypeRef) -> PyTypeRef { create_type_with_slots(name, PyType::static_type(), base, exception_slots()) } /// This macro serves a goal of generating multiple BaseException / Exception /// subtypes in a uniform and convenient manner. /// It looks like `SimpleExtendsException` in `CPython`. /// https://github.com/python/cpython/blob/main/Objects/exceptions.c /// /// We need `ctx` to be ready to add `properties` / `custom` constructors / slots / etc. /// So, we use `extend_class!` macro as the second step in exception type definition. macro_rules! extends_exception { ( $class_name: ident, $base_class: ident, $ctx_name: ident, $docs: tt ) => { #[pyexception($class_name, $base_class)] #[derive(Debug)] #[doc = $docs] struct $class_name {} // We need this to make extend mechanism work: impl PyValue for $class_name { fn class(vm: &VirtualMachine) -> &PyTypeRef { &vm.ctx.exceptions.$ctx_name } } #[pyimpl(flags(BASETYPE, HAS_DICT))] impl $class_name { #[pyslot] fn tp_new( cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine, ) -> PyResult> { // We need this method, because of how `CPython` copies `init` // from `BaseException` in `SimpleExtendsException` macro. // See: `BaseException_new` PyBaseException::tp_new(cls, args, vm) } #[pymethod(magic)] fn init( zelf: PyRef, args: FuncArgs, vm: &VirtualMachine, ) -> PyResult<()> { // We need this method, because of how `CPython` copies `init` // from `BaseException` in `SimpleExtendsException` macro. // See: `(initproc)BaseException_init` $base_class::init(zelf, args, vm) } } }; } // Exception types that extend `BaseException`, // sorted the same way CPython does. extends_exception! { PyException, PyBaseException, exception_type, "Common base class for all non-exit exceptions." } extends_exception! { PyGeneratorExit, PyBaseException, generator_exit, "Request that a generator exit." } extends_exception! { PySystemExit, PyBaseException, system_exit, "Request to exit from the interpreter." } extends_exception! { PyKeyboardInterrupt, PyBaseException, keyboard_interrupt, "Program interrupted by user." } // Exception types that extend `Exception`, // sorted the same way CPython does. extends_exception! { PyTypeError, PyException, type_error, "Inappropriate argument type." } extends_exception! { PyStopAsyncIteration, PyException, stop_async_iteration, "Signal the end from iterator.__anext__()." } extends_exception! { PyStopIteration, PyException, stop_iteration, "Signal the end from iterator.__next__()." } extends_exception! { PyOSError, PyException, os_error, "Base class for I/O related errors." } macro_rules! extend_exception { ( $ctx:expr, $class:expr, { $($name:expr => $value:expr),* $(,)* }) => { $class.set_str_attr("__new__", $ctx.new_method("__new__", $class.clone(), PyBaseException::tp_new)); $class.set_str_attr("__init__", $ctx.new_method("__init__", $class.clone(), PyBaseException::init)); extend_class!($ctx, $class, { $( $name => $value, )* }); }; } impl ExceptionZoo { pub(crate) fn init() -> Self { // The same order as definitions: let base_exception_type = PyBaseException::init_bare_type().clone(); let exception_type = PyException::init_bare_type().clone(); let system_exit = PySystemExit::init_bare_type().clone(); let keyboard_interrupt = PyKeyboardInterrupt::init_bare_type().clone(); let generator_exit = PyGeneratorExit::init_bare_type().clone(); let stop_iteration = PyStopIteration::init_bare_type().clone(); let stop_async_iteration = PyStopAsyncIteration::init_bare_type().clone(); let arithmetic_error = create_exception_type("ArithmeticError", &exception_type); let floating_point_error = create_exception_type("FloatingPointError", &arithmetic_error); let overflow_error = create_exception_type("OverflowError", &arithmetic_error); let zero_division_error = create_exception_type("ZeroDivisionError", &arithmetic_error); let assertion_error = create_exception_type("AssertionError", &exception_type); let attribute_error = create_exception_type("AttributeError", &exception_type); let buffer_error = create_exception_type("BufferError", &exception_type); let eof_error = create_exception_type("EOFError", &exception_type); let import_error = create_exception_type("ImportError", &exception_type); let module_not_found_error = create_exception_type("ModuleNotFoundError", &import_error); let lookup_error = create_exception_type("LookupError", &exception_type); let index_error = create_exception_type("IndexError", &lookup_error); let key_error = create_exception_type("KeyError", &lookup_error); let memory_error = create_exception_type("MemoryError", &exception_type); let name_error = create_exception_type("NameError", &exception_type); let unbound_local_error = create_exception_type("UnboundLocalError", &name_error); // os errors let os_error = PyOSError::init_bare_type().clone(); let blocking_io_error = create_exception_type("BlockingIOError", &os_error); let child_process_error = create_exception_type("ChildProcessError", &os_error); let connection_error = create_exception_type("ConnectionError", &os_error); let connection_aborted_error = create_exception_type("ConnectionAbortedError", &connection_error); let connection_refused_error = create_exception_type("ConnectionRefusedError", &connection_error); let connection_reset_error = create_exception_type("ConnectionResetError", &connection_error); let file_exists_error = create_exception_type("FileExistsError", &os_error); let file_not_found_error = create_exception_type("FileNotFoundError", &os_error); let interrupted_error = create_exception_type("InterruptedError", &os_error); let is_a_directory_error = create_exception_type("IsADirectoryError", &os_error); let not_a_directory_error = create_exception_type("NotADirectoryError", &os_error); let broken_pipe_error = create_exception_type("BrokenPipeError", &connection_error); let permission_error = create_exception_type("PermissionError", &os_error); let process_lookup_error = create_exception_type("ProcessLookupError", &os_error); let timeout_error = create_exception_type("TimeoutError", &os_error); let reference_error = create_exception_type("ReferenceError", &exception_type); let runtime_error = create_exception_type("RuntimeError", &exception_type); let not_implemented_error = create_exception_type("NotImplementedError", &runtime_error); let recursion_error = create_exception_type("RecursionError", &runtime_error); let syntax_error = create_exception_type("SyntaxError", &exception_type); let indentation_error = create_exception_type("IndentationError", &syntax_error); let tab_error = create_exception_type("TabError", &indentation_error); let target_scope_error = create_exception_type("TargetScopeError", &syntax_error); let system_error = create_exception_type("SystemError", &exception_type); let type_error = PyTypeError::init_bare_type().clone(); let value_error = create_exception_type("ValueError", &exception_type); let unicode_error = create_exception_type("UnicodeError", &value_error); let unicode_decode_error = create_exception_type("UnicodeDecodeError", &unicode_error); let unicode_encode_error = create_exception_type("UnicodeEncodeError", &unicode_error); let unicode_translate_error = create_exception_type("UnicodeTranslateError", &unicode_error); #[cfg(feature = "jit")] let jit_error = create_exception_type("JitError", &exception_type); let warning = create_exception_type("Warning", &exception_type); let deprecation_warning = create_exception_type("DeprecationWarning", &warning); let pending_deprecation_warning = create_exception_type("PendingDeprecationWarning", &warning); let runtime_warning = create_exception_type("RuntimeWarning", &warning); let syntax_warning = create_exception_type("SyntaxWarning", &warning); let user_warning = create_exception_type("UserWarning", &warning); let future_warning = create_exception_type("FutureWarning", &warning); let import_warning = create_exception_type("ImportWarning", &warning); let unicode_warning = create_exception_type("UnicodeWarning", &warning); let bytes_warning = create_exception_type("BytesWarning", &warning); let resource_warning = create_exception_type("ResourceWarning", &warning); Self { base_exception_type, system_exit, keyboard_interrupt, generator_exit, exception_type, stop_iteration, stop_async_iteration, arithmetic_error, floating_point_error, overflow_error, zero_division_error, assertion_error, attribute_error, buffer_error, eof_error, import_error, module_not_found_error, lookup_error, index_error, key_error, memory_error, name_error, unbound_local_error, os_error, blocking_io_error, child_process_error, connection_error, broken_pipe_error, connection_aborted_error, connection_refused_error, connection_reset_error, file_exists_error, file_not_found_error, interrupted_error, is_a_directory_error, not_a_directory_error, permission_error, process_lookup_error, timeout_error, reference_error, runtime_error, not_implemented_error, recursion_error, syntax_error, target_scope_error, indentation_error, tab_error, system_error, type_error, value_error, unicode_error, unicode_decode_error, unicode_encode_error, unicode_translate_error, #[cfg(feature = "jit")] jit_error, warning, deprecation_warning, pending_deprecation_warning, runtime_warning, syntax_warning, user_warning, future_warning, import_warning, unicode_warning, bytes_warning, resource_warning, } } pub fn extend(ctx: &PyContext) { let excs = &ctx.exceptions; PyBaseException::extend_class(ctx, &excs.base_exception_type); PyException::extend_class(ctx, &excs.exception_type); PyGeneratorExit::extend_class(ctx, &excs.generator_exit); PySystemExit::extend_class(ctx, &excs.system_exit); extend_class!(ctx, &excs.system_exit, { "code" => ctx.new_readonly_getset("code", excs.system_exit.clone(), system_exit_code), }); PyKeyboardInterrupt::extend_class(ctx, &excs.keyboard_interrupt); PyTypeError::extend_class(ctx, &excs.type_error); PyStopAsyncIteration::extend_class(ctx, &excs.stop_async_iteration); PyStopIteration::extend_class(ctx, &excs.stop_iteration); extend_class!(ctx, &excs.stop_iteration, { "value" => ctx.new_readonly_getset("value", excs.stop_iteration.clone(), make_arg_getter(0)), }); extend_exception!(ctx, &excs.arithmetic_error, {}); extend_class!(ctx, &excs.syntax_error, { "msg" => ctx.new_readonly_getset("msg", excs.syntax_error.clone(), make_arg_getter(0)), // TODO: members "filename" => ctx.none(), "lineno" => ctx.none(), "offset" => ctx.none(), "text" => ctx.none(), }); extend_exception!(ctx, &excs.import_error, { "__init__" => ctx.new_method("__init__", excs.import_error.clone(), import_error_init), "msg" => ctx.new_readonly_getset("msg", excs.import_error.clone(), make_arg_getter(0)), }); extend_class!(ctx, &excs.key_error, { "__str__" => ctx.new_method("__str__", excs.key_error.clone(), key_error_str), }); PyOSError::extend_class(ctx, &excs.os_error); let errno_getter = ctx.new_readonly_getset("errno", excs.os_error.clone(), |exc: PyBaseExceptionRef| { let args = exc.args(); let args = args.as_slice(); args.get(0).filter(|_| args.len() > 1).cloned() }); #[cfg(windows)] extend_class!(ctx, &excs.os_error, { // TODO: this isn't really accurate "winerror" => errno_getter.clone(), }); extend_class!(ctx, &excs.os_error, { "errno" => errno_getter, "strerror" => ctx.new_readonly_getset("strerror", excs.os_error.clone(), make_arg_getter(1)), }); extend_class!(ctx, &excs.unicode_decode_error, { "encoding" => ctx.new_readonly_getset("encoding", excs.unicode_decode_error.clone(), make_arg_getter(0)), "object" => ctx.new_readonly_getset("object", excs.unicode_decode_error.clone(), make_arg_getter(1)), "start" => ctx.new_readonly_getset("start", excs.unicode_decode_error.clone(), make_arg_getter(2)), "end" => ctx.new_readonly_getset("end", excs.unicode_decode_error.clone(), make_arg_getter(3)), "reason" => ctx.new_readonly_getset("reason", excs.unicode_decode_error.clone(), make_arg_getter(4)), }); extend_class!(ctx, &excs.unicode_encode_error, { "encoding" => ctx.new_readonly_getset("encoding", excs.unicode_encode_error.clone(), make_arg_getter(0)), "object" => ctx.new_readonly_getset("object", excs.unicode_encode_error.clone(), make_arg_getter(1)), "start" => ctx.new_readonly_getset("start", excs.unicode_encode_error.clone(), make_arg_getter(2), ), "end" => ctx.new_readonly_getset("end", excs.unicode_encode_error.clone(), make_arg_getter(3)), "reason" => ctx.new_readonly_getset("reason", excs.unicode_encode_error.clone(), make_arg_getter(4)), }); extend_class!(ctx, &excs.unicode_translate_error, { "encoding" => ctx.new_readonly_getset("encoding", excs.unicode_translate_error.clone(), none_getter), "object" => ctx.new_readonly_getset("object", excs.unicode_translate_error.clone(), make_arg_getter(0)), "start" => ctx.new_readonly_getset("start", excs.unicode_translate_error.clone(), make_arg_getter(1)), "end" => ctx.new_readonly_getset("end", excs.unicode_translate_error.clone(), make_arg_getter(2)), "reason" => ctx.new_readonly_getset("reason", excs.unicode_translate_error.clone(), make_arg_getter(3)), }); } } fn import_error_init(exc_self: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { vm.set_attr( &exc_self, "name", vm.unwrap_or_none(args.kwargs.get("name").cloned()), )?; vm.set_attr( &exc_self, "path", vm.unwrap_or_none(args.kwargs.get("path").cloned()), )?; Ok(()) } fn none_getter(_obj: PyObjectRef, vm: &VirtualMachine) -> PyNoneRef { vm.ctx.none.clone() } fn make_arg_getter(idx: usize) -> impl Fn(PyBaseExceptionRef) -> Option { move |exc| exc.get_arg(idx) } 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) .into_iter() .exactly_one() .unwrap() } else { exc.str(vm) } } fn system_exit_code(exc: PyBaseExceptionRef) -> Option { exc.args.read().as_slice().first().map(|code| { match_class!(match code { ref tup @ PyTuple => match tup.as_slice() { [x] => x.clone(), _ => code.clone(), }, other => other.clone(), }) }) } pub struct SerializeException<'s> { vm: &'s VirtualMachine, exc: &'s PyBaseExceptionRef, } impl<'s> SerializeException<'s> { pub fn new(vm: &'s VirtualMachine, exc: &'s PyBaseExceptionRef) -> Self { SerializeException { vm, exc } } } impl serde::Serialize for SerializeException<'_> { fn serialize(&self, s: S) -> Result { use serde::ser::*; let mut struc = s.serialize_struct("PyBaseException", 7)?; struc.serialize_field("exc_type", &self.exc.class().name)?; let tbs = { struct Tracebacks(PyTracebackRef); impl serde::Serialize for Tracebacks { fn serialize(&self, s: S) -> Result { let mut s = s.serialize_seq(None)?; for tb in self.0.iter() { s.serialize_element(&*tb)?; } s.end() } } self.exc.traceback().map(Tracebacks) }; struc.serialize_field("traceback", &tbs)?; struc.serialize_field( "cause", &self.exc.cause().as_ref().map(|e| Self::new(self.vm, e)), )?; struc.serialize_field( "context", &self.exc.context().as_ref().map(|e| Self::new(self.vm, e)), )?; struc.serialize_field("suppress_context", &self.exc.get_suppress_context())?; let args = { struct Args<'vm>(&'vm VirtualMachine, PyTupleRef); impl serde::Serialize for Args<'_> { fn serialize(&self, s: S) -> Result { s.collect_seq( self.1 .as_slice() .iter() .map(|arg| crate::py_serde::PyObjectSerializer::new(self.0, arg)), ) } } Args(self.vm, self.exc.args()) }; struc.serialize_field("args", &args)?; let rendered = { let mut rendered = String::new(); write_exception(&mut rendered, self.vm, self.exc).map_err(S::Error::custom)?; rendered }; struc.serialize_field("rendered", &rendered)?; struc.end() } } pub(crate) fn cstring_error(vm: &VirtualMachine) -> PyBaseExceptionRef { vm.new_value_error("embedded null character".to_owned()) } impl IntoPyException for std::ffi::NulError { fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef { cstring_error(vm) } } #[cfg(windows)] impl IntoPyException for widestring::NulError { fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef { cstring_error(vm) } }