diff --git a/Cargo.lock b/Cargo.lock index aa01ced91..67b1b5a1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -598,7 +598,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "itertools" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -633,7 +633,7 @@ dependencies = [ "diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "ena 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "lalrpop-util 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)", "petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1255,7 +1255,7 @@ version = "0.1.1" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "num-complex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython-bytecode 0.1.1", @@ -1316,7 +1316,7 @@ dependencies = [ "hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "hexf-parse 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lexical 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1368,8 +1368,6 @@ dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "js-sys 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", - "num-bigint 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython-compiler 0.1.1", "rustpython-parser 0.1.1", "rustpython-vm 0.1.1", @@ -2226,7 +2224,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum hexf-parse 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "79296f72d53a89096cbc9a88c9547ee8dfe793388674620e2207593d370550ac" "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" "checksum indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a61202fbe46c4a951e9404a720a0180bcf3212c750d735cb5c4ba4dc551299f3" -"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" +"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum js-sys 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)" = "2cc9a97d7cec30128fd8b28a7c1f9df1c001ceb9b441e2b755e24130a6b43c79" "checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" diff --git a/src/main.rs b/src/main.rs index 5aaf2aadd..dab0d2f9b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,9 @@ extern crate log; use clap::{App, AppSettings, Arg, ArgMatches}; use rustpython_compiler::compile; use rustpython_vm::{ + exceptions::print_exception, match_class, - obj::{objint::PyInt, objtuple::PyTuple, objtype}, - print_exception, + obj::{objint::PyInt, objtype}, pyobject::{ItemProtocol, PyResult}, scope::Scope, util, InitParameter, PySettings, VirtualMachine, @@ -51,8 +51,7 @@ fn main() { // See if any exception leaked out: if let Err(err) = res { if objtype::isinstance(&err, &vm.ctx.exceptions.system_exit) { - let args = vm.get_attribute(err.clone(), "args").unwrap(); - let args = args.downcast::().expect("'args' must be a tuple"); + let args = err.args(); match args.elements.len() { 0 => return, 1 => match_class!(match args.elements[0].clone() { diff --git a/src/shell.rs b/src/shell.rs index 0458465ab..af4f3c6cb 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -5,9 +5,9 @@ mod rustyline_helper; use rustpython_compiler::{compile, error::CompileError, error::CompileErrorType}; use rustpython_parser::error::ParseErrorType; use rustpython_vm::{ + exceptions::{print_exception, PyBaseExceptionRef}, obj::objtype, - print_exception, - pyobject::{ItemProtocol, PyObjectRef, PyResult}, + pyobject::{ItemProtocol, PyResult}, scope::Scope, VirtualMachine, }; @@ -16,7 +16,7 @@ use readline::{Readline, ReadlineResult}; enum ShellExecResult { Ok, - PyErr(PyObjectRef), + PyErr(PyBaseExceptionRef), Continue, } @@ -118,9 +118,8 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> { ReadlineResult::Interrupt => { continuing = false; full_input.clear(); - let keyboard_interrupt = vm - .new_empty_exception(vm.ctx.exceptions.keyboard_interrupt.clone()) - .unwrap(); + let keyboard_interrupt = + vm.new_exception_empty(vm.ctx.exceptions.keyboard_interrupt.clone()); Err(keyboard_interrupt) } ReadlineResult::EOF => { diff --git a/tests/snippets/try_exceptions.py b/tests/snippets/try_exceptions.py index c646e0937..1f46caae3 100644 --- a/tests/snippets/try_exceptions.py +++ b/tests/snippets/try_exceptions.py @@ -250,7 +250,23 @@ try: except ZeroDivisionError as ex: raise NameError from ex except NameError as ex2: - pass + assert isinstance(ex2.__cause__, ZeroDivisionError) +else: + assert False, "no raise" + + +try: + try: + try: + raise ZeroDivisionError + except ZeroDivisionError as ex: + raise NameError from ex + except NameError: + raise +except NameError as ex2: + assert isinstance(ex2.__cause__, ZeroDivisionError) +else: + assert False, "no raise" # the else clause requires at least one except clause: diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 9b8bb76f8..ccd0cc89b 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -49,7 +49,7 @@ chrono = { version = "=0.4.9", features = ["wasmbind"] } unicode-xid = "0.2.0" lazy_static = "^1.0.1" lexical = "4" -itertools = "^0.8.0" +itertools = "0.8" hex = "0.4.0" hexf-parse = "0.1.0" indexmap = "1.0.2" diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 3ff72416e..73f237a71 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -12,6 +12,7 @@ use num_traits::{Signed, ToPrimitive, Zero}; #[cfg(feature = "rustpython-compiler")] use rustpython_compiler::compile; +use crate::exceptions::PyBaseExceptionRef; use crate::function::{single_or_tuple_any, Args, KwArgs, OptionalArg, PyFuncArgs}; use crate::obj::objbool::{self, IntoPyBool}; use crate::obj::objbyteinner::PyByteInner; @@ -290,7 +291,7 @@ fn builtin_format( }) } -fn catch_attr_exception(ex: PyObjectRef, default: T, vm: &VirtualMachine) -> PyResult { +fn catch_attr_exception(ex: PyBaseExceptionRef, default: T, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&ex, &vm.ctx.exceptions.attribute_error) { Ok(default) } else { @@ -628,7 +629,7 @@ impl Printer for std::io::StdoutLock<'_> { pub fn builtin_exit(exit_code_arg: OptionalArg, vm: &VirtualMachine) -> PyResult { let code = exit_code_arg.unwrap_or_else(|| vm.new_int(0)); - Err(vm.new_exception_obj(vm.ctx.exceptions.system_exit.clone(), vec![code])?) + Err(vm.new_exception(vm.ctx.exceptions.system_exit.clone(), vec![code])) } pub fn builtin_print(objects: Args, options: PrintOptions, vm: &VirtualMachine) -> PyResult<()> { diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 2a66e555c..5552eb154 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1,11 +1,11 @@ use crate::function::PyFuncArgs; -use crate::obj::objiter; +use crate::obj::objnone::PyNone; +use crate::obj::objstr::{PyString, PyStringRef}; use crate::obj::objtraceback::PyTracebackRef; use crate::obj::objtuple::{PyTuple, PyTupleRef}; -use crate::obj::objtype; -use crate::obj::objtype::PyClassRef; +use crate::obj::objtype::{self, PyClass, PyClassRef}; use crate::pyobject::{ - IdProtocol, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, + PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::types::create_type; @@ -19,8 +19,8 @@ use std::io::{self, BufRead, BufReader, Write}; #[pyclass] pub struct PyBaseException { traceback: RefCell>, - cause: RefCell>, - context: RefCell>, + cause: RefCell>, + context: RefCell>, suppress_context: Cell, args: RefCell, } @@ -44,20 +44,19 @@ impl PyValue for PyBaseException { #[pyimpl] impl PyBaseException { - #[pyslot(new)] - fn tp_new( - cls: PyClassRef, - _args: PyFuncArgs, - vm: &VirtualMachine, - ) -> PyResult { + pub(crate) fn new(args: Vec, vm: &VirtualMachine) -> PyBaseException { PyBaseException { traceback: RefCell::new(None), cause: RefCell::new(None), context: RefCell::new(None), suppress_context: Cell::new(false), - args: RefCell::new(PyTuple::from(vec![]).into_ref(vm)), + args: RefCell::new(PyTuple::from(args).into_ref(vm)), } - .into_ref_with_type(vm, cls) + } + + #[pyslot(new)] + fn tp_new(cls: PyClassRef, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult> { + PyBaseException::new(args.args, vm).into_ref_with_type(vm, cls) } #[pymethod(name = "__init__")] @@ -66,8 +65,8 @@ impl PyBaseException { Ok(()) } - #[pyproperty] - fn args(&self, _vm: &VirtualMachine) -> PyTupleRef { + #[pyproperty(name = "args")] + fn get_args(&self, _vm: &VirtualMachine) -> PyTupleRef { self.args.borrow().clone() } @@ -84,27 +83,27 @@ impl PyBaseException { } #[pyproperty(name = "__traceback__", setter)] - fn set_traceback(&self, traceback: Option, _vm: &VirtualMachine) { + fn setter_traceback(&self, traceback: Option, _vm: &VirtualMachine) { self.traceback.replace(traceback); } #[pyproperty(name = "__cause__")] - fn get_cause(&self, _vm: &VirtualMachine) -> Option { + fn get_cause(&self, _vm: &VirtualMachine) -> Option { self.cause.borrow().clone() } #[pyproperty(name = "__cause__", setter)] - fn set_cause(&self, cause: Option, _vm: &VirtualMachine) { + fn setter_cause(&self, cause: Option, _vm: &VirtualMachine) { self.cause.replace(cause); } #[pyproperty(name = "__context__")] - fn get_context(&self, _vm: &VirtualMachine) -> Option { + fn get_context(&self, _vm: &VirtualMachine) -> Option { self.context.borrow().clone() } #[pyproperty(name = "__context__", setter)] - fn set_context(&self, context: Option, _vm: &VirtualMachine) { + fn setter_context(&self, context: Option, _vm: &VirtualMachine) { self.context.replace(context); } @@ -127,51 +126,88 @@ impl PyBaseException { zelf.traceback.replace(tb); Ok(zelf.as_object().clone()) } + + #[pymethod(name = "__str__")] + fn str(&self, vm: &VirtualMachine) -> PyStringRef { + let str_args = exception_args_as_string(vm, self.args(), false); + match str_args.into_iter().exactly_one() { + Err(i) if i.len() == 0 => PyString::from("").into_ref(vm), + Ok(s) => s, + Err(i) => PyString::from(format!("({})", i.format(", "))).into_ref(vm), + } + } + + #[pymethod(name = "__repr__")] + fn repr(zelf: PyRef, vm: &VirtualMachine) -> String { + let repr_args = exception_args_as_string(vm, zelf.args(), false); + let cls = zelf.class(); + match repr_args.into_iter().exactly_one() { + Ok(one) => format!("{}({},)", cls.name, one), + Err(i) => format!("{}({})", cls.name, i.format(", ")), + } + } + + pub fn args(&self) -> PyTupleRef { + self.args.borrow().clone() + } + + pub fn traceback(&self) -> Option { + self.traceback.borrow().clone() + } + pub fn set_traceback(&self, tb: Option) { + self.traceback.replace(tb); + } + + pub fn cause(&self) -> Option { + self.cause.borrow().clone() + } + pub fn set_cause(&self, cause: Option) { + self.cause.replace(cause); + } + + pub fn context(&self) -> Option { + self.context.borrow().clone() + } + pub fn set_context(&self, context: Option) { + self.context.replace(context); + } } /// Print exception chain -pub fn print_exception(vm: &VirtualMachine, exc: &PyObjectRef) { - let _ = write_exception(io::stdout(), vm, exc); +pub fn print_exception(vm: &VirtualMachine, exc: &PyBaseExceptionRef) { + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + let _ = write_exception(&mut stdout, vm, exc); } pub fn write_exception( - mut output: W, + output: &mut W, vm: &VirtualMachine, - exc: &PyObjectRef, + exc: &PyBaseExceptionRef, ) -> io::Result<()> { - let mut had_cause = false; - if let Ok(cause) = vm.get_attribute(exc.clone(), "__cause__") { - if !vm.get_none().is(&cause) { - had_cause = true; - print_exception(vm, &cause); - writeln!( - output, - "\nThe above exception was the direct cause of the following exception:\n" - )?; - } + if let Some(cause) = exc.cause() { + write_exception(output, vm, &cause)?; + writeln!( + output, + "\nThe above exception was the direct cause of the following exception:\n" + )?; + } else if let Some(context) = exc.context() { + write_exception(output, vm, &context)?; + writeln!( + output, + "\nDuring handling of the above exception, another exception occurred:\n" + )?; } - if !had_cause { - if let Ok(context) = vm.get_attribute(exc.clone(), "__context__") { - if !vm.get_none().is(&context) { - print_exception(vm, &context); - writeln!( - output, - "\nDuring handling of the above exception, another exception occurred:\n" - )?; - } - } - } - print_exception_inner(output, vm, exc) + + write_exception_inner(output, vm, exc) } -fn print_source_line(mut output: W, filename: &str, lineno: usize) -> io::Result<()> { +fn print_source_line(output: &mut W, filename: &str, lineno: usize) -> io::Result<()> { // 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(()); - } + Err(_) => return Ok(()), }; let file = BufReader::new(file); @@ -189,7 +225,7 @@ fn print_source_line(mut output: W, filename: &str, lineno: usize) -> } /// Print exception occurrence location from traceback element -fn print_traceback_entry(mut output: W, tb_entry: &PyTracebackRef) -> io::Result<()> { +fn write_traceback_entry(output: &mut W, tb_entry: &PyTracebackRef) -> io::Result<()> { let filename = tb_entry.frame.code.source_path.to_string(); writeln!( output, @@ -202,32 +238,23 @@ fn print_traceback_entry(mut output: W, tb_entry: &PyTracebackRef) -> } /// Print exception with traceback -pub fn print_exception_inner( - mut output: W, +pub fn write_exception_inner( + output: &mut W, vm: &VirtualMachine, - exc: &PyObjectRef, + exc: &PyBaseExceptionRef, ) -> io::Result<()> { - if let Ok(tb) = vm.get_attribute(exc.clone(), "__traceback__") { - if objtype::isinstance(&tb, &vm.ctx.traceback_type()) { - writeln!(output, "Traceback (most recent call last):")?; - let mut tb: PyTracebackRef = tb.downcast().expect(" must be a traceback object"); - loop { - print_traceback_entry(&mut output, &tb)?; - tb = match &tb.next { - Some(tb) => tb.clone(), - None => break, - }; - } + if let Some(tb) = exc.traceback.borrow().clone() { + writeln!(output, "Traceback (most recent call last):")?; + let mut tb = Some(&tb); + while let Some(traceback) = tb { + write_traceback_entry(output, traceback)?; + tb = traceback.next.as_ref(); } } else { writeln!(output, "No traceback set on exception")?; } - let varargs = vm - .get_attribute(exc.clone(), "args") - .unwrap() - .downcast::() - .expect("'args' must be a tuple"); + let varargs = exc.args(); let args_repr = exception_args_as_string(vm, varargs, true); let exc_name = exc.class().name.clone(); @@ -247,71 +274,117 @@ fn exception_args_as_string( vm: &VirtualMachine, varargs: PyTupleRef, str_single: bool, -) -> Vec { +) -> Vec { match varargs.elements.len() { 0 => vec![], 1 => { let args0_repr = if str_single { - vm.to_pystr(&varargs.elements[0]) - .unwrap_or_else(|_| "".to_string()) + vm.to_str(&varargs.elements[0]) + .unwrap_or_else(|_| PyString::from("").into_ref(vm)) } else { vm.to_repr(&varargs.elements[0]) - .map(|s| s.as_str().to_owned()) - .unwrap_or_else(|_| "".to_string()) + .unwrap_or_else(|_| PyString::from("").into_ref(vm)) }; vec![args0_repr] } _ => varargs .elements .iter() - .map(|vararg| match vm.to_repr(vararg) { - Ok(arg_repr) => arg_repr.as_str().to_string(), - Err(_) => "".to_string(), + .map(|vararg| { + vm.to_repr(vararg) + .unwrap_or_else(|_| PyString::from("").into_ref(vm)) }) .collect(), } } -fn exception_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(exc, Some(vm.ctx.exceptions.exception_type.clone()))] - ); - let args = vm - .get_attribute(exc.clone(), "args") - .unwrap() - .downcast::() - .expect("'args' must be a tuple"); - let args_str = exception_args_as_string(vm, args, false); - let joined_str = match args_str.len() { - 0 => "".to_string(), - 1 => args_str.into_iter().next().unwrap(), - _ => format!("({})", args_str.into_iter().format(", ")), - }; - Ok(vm.new_str(joined_str)) +#[derive(Clone)] +pub enum ExceptionCtor { + Class(PyClassRef), + Instance(PyBaseExceptionRef), } -fn exception_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(exc, Some(vm.ctx.exceptions.exception_type.clone()))] - ); - let args = vm - .get_attribute(exc.clone(), "args") - .unwrap() - .downcast::() - .expect("'args' must be a tuple"); - let args_repr = exception_args_as_string(vm, args, false); +impl TryFromObject for ExceptionCtor { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + obj.downcast::() + .and_then(|cls| { + if objtype::issubclass(&cls, &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 + )) + }) + } +} - let exc_name = exc.class().name.clone(); - let joined_str = match args_repr.len() { - 0 => format!("{}()", exc_name), - 1 => format!("{}({},)", exc_name, args_repr[0]), - _ => format!("{}({})", exc_name, args_repr.join(", ")), - }; - Ok(vm.new_str(joined_str)) +pub fn invoke( + cls: PyClassRef, + 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_string())) + } + // 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 objtype::isinstance(&exc, &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.elements.clone(), + exc @ PyBaseException => exc.args().elements.clone(), + obj => vec![obj], + }); + invoke(cls, args, vm) + } + } + } +} + +/// 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)] @@ -508,10 +581,7 @@ impl ExceptionZoo { } } -fn import_error_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let exc_self = args.args[0].clone(); - - vm.set_attr(&exc_self, "args", vm.ctx.new_tuple(args.args[1..].to_vec()))?; +fn import_error_init(exc_self: PyObjectRef, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult<()> { vm.set_attr( &exc_self, "name", @@ -528,26 +598,21 @@ fn import_error_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { .cloned() .unwrap_or_else(|| vm.get_none()), )?; - vm.set_attr( - &exc_self, - "msg", - args.args.get(1).cloned().unwrap_or_else(|| vm.get_none()), - )?; - Ok(vm.get_none()) + Ok(()) } fn none_getter(_obj: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { vm.get_none() } -fn make_arg_getter(idx: usize) -> impl Fn(PyBaseExceptionRef, &VirtualMachine) -> PyResult { +fn make_arg_getter(idx: usize) -> impl Fn(PyBaseExceptionRef, &VirtualMachine) -> PyObjectRef { move |exc, vm| { exc.args .borrow() .elements .get(idx) .cloned() - .ok_or_else(|| vm.new_value_error(format!("couldn't get arg {} of exception", idx))) + .unwrap_or_else(|| vm.get_none()) } } @@ -556,11 +621,6 @@ pub fn init(ctx: &PyContext) { PyBaseException::extend_class(ctx, &excs.base_exception_type); - extend_class!(ctx, &excs.exception_type, { - "__str__" => ctx.new_rustfunc(exception_str), - "__repr__" => ctx.new_rustfunc(exception_repr), - }); - extend_class!(ctx, &excs.syntax_error, { "msg" => ctx.new_property(make_arg_getter(0)), "filename" => ctx.new_property(make_arg_getter(1)), @@ -570,13 +630,12 @@ pub fn init(ctx: &PyContext) { }); extend_class!(ctx, &excs.import_error, { - "__init__" => ctx.new_rustfunc(import_error_init) + "__init__" => ctx.new_rustfunc(import_error_init), + "msg" => ctx.new_property(make_arg_getter(0)), }); extend_class!(ctx, &excs.stop_iteration, { - "value" => ctx.new_rustfunc(|obj: PyObjectRef, vm: &VirtualMachine| { - objiter::stop_iter_value(vm, &obj) - }), + "value" => ctx.new_property(make_arg_getter(0)), }); extend_class!(ctx, &excs.unicode_decode_error, { diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 0aaa079e0..d3590260a 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -5,6 +5,7 @@ use indexmap::IndexMap; use itertools::Itertools; use crate::bytecode; +use crate::exceptions::{self, ExceptionCtor, PyBaseExceptionRef}; use crate::function::{single_or_tuple_any, PyFuncArgs}; use crate::obj::objbool; use crate::obj::objcode::PyCodeRef; @@ -15,7 +16,7 @@ use crate::obj::objiter; use crate::obj::objlist; use crate::obj::objslice::PySlice; use crate::obj::objstr::{self, PyString}; -use crate::obj::objtraceback::{PyTraceback, PyTracebackRef}; +use crate::obj::objtraceback::PyTraceback; use crate::obj::objtuple::PyTuple; use crate::obj::objtype::{self, PyClassRef}; use crate::pyobject::{ @@ -63,7 +64,7 @@ enum UnwindReason { Returning { value: PyObjectRef }, /// We hit an exception, so unwind any try-except and finally blocks. - Raising { exception: PyObjectRef }, + Raising { exception: PyBaseExceptionRef }, // NoWorries, /// We are unwinding blocks, since we hit break @@ -126,7 +127,7 @@ impl ExecutionResult { } else { vec![value] }; - Err(vm.new_exception_obj(stop_iteration, args).unwrap()) + Err(vm.new_exception(stop_iteration, args)) } } } @@ -176,23 +177,8 @@ impl Frame { // 1. Extract traceback from exception's '__traceback__' attr. // 2. Add new entry with current execution position (filename, lineno, code_object) to traceback. // 3. Unwind block stack till appropriate handler is found. - assert!(objtype::isinstance( - &exception, - &vm.ctx.exceptions.base_exception_type - )); - let traceback = vm - .get_attribute(exception.clone(), "__traceback__") - .unwrap(); - - let next = if vm.is_none(&traceback) { - None - } else { - let traceback: PyTracebackRef = traceback - .downcast() - .expect("next must be a traceback object"); - Some(traceback) - }; + let next = exception.traceback(); let new_traceback = PyTraceback::new( next, @@ -200,8 +186,7 @@ impl Frame { self.lasti.get(), lineno.row(), ); - vm.set_attr(&exception, "__traceback__", new_traceback.into_ref(vm)) - .unwrap(); + exception.set_traceback(Some(new_traceback.into_ref(vm))); vm_trace!("Adding to traceback: {:?} {:?}", new_traceback, lineno); match self.unwind_blocks(vm, UnwindReason::Raising { exception }) { @@ -223,26 +208,22 @@ impl Frame { pub(crate) fn gen_throw( &self, vm: &VirtualMachine, - exc_type: PyClassRef, + exc_type: PyObjectRef, exc_val: PyObjectRef, exc_tb: PyObjectRef, ) -> PyResult { if let bytecode::Instruction::YieldFrom = self.code.instructions[self.lasti.get()] { let coro = self.last_value(); - vm.call_method( - &coro, - "throw", - vec![exc_type.into_object(), exc_val, exc_tb], - ) - .or_else(|err| { - self.pop_value(); - self.lasti.set(self.lasti.get() + 1); - let val = objiter::stop_iter_value(vm, &err)?; - self._send(coro, val, vm) - }) - .map(ExecutionResult::Yield) + vm.call_method(&coro, "throw", vec![exc_type, exc_val, exc_tb]) + .or_else(|err| { + self.pop_value(); + self.lasti.set(self.lasti.get() + 1); + let val = objiter::stop_iter_value(vm, &err)?; + self._send(coro, val, vm) + }) + .map(ExecutionResult::Yield) } else { - let exception = vm.new_exception_obj(exc_type, vec![exc_val])?; + let exception = exceptions::normalize(exc_type, exc_val, exc_tb, vm)?; match self.unwind_blocks(vm, UnwindReason::Raising { exception }) { Ok(None) => self.run(vm), Ok(Some(result)) => Ok(result), @@ -464,8 +445,8 @@ impl Frame { let args = if let Some(exc) = exc { let exc_type = exc.class().into_object(); let exc_val = exc.clone(); - let exc_tb = vm.ctx.none(); // TODO: retrieve traceback? - vec![exc_type, exc_val, exc_tb] + let exc_tb = exc.traceback().map_or(vm.get_none(), |tb| tb.into_object()); + vec![exc_type, exc_val.into_object(), exc_tb] } else { vec![vm.ctx.none(), vm.ctx.none(), vm.ctx.none()] }; @@ -745,7 +726,7 @@ impl Frame { self.pop_block(); if let UnwindReason::Raising { exception } = &reason { self.push_block(BlockType::ExceptHandler {}); - self.push_value(exception.clone()); + self.push_value(exception.clone().into_object()); vm.push_exception(exception.clone()); self.jump(handler); return Ok(None); @@ -978,36 +959,47 @@ impl Frame { fn execute_raise(&self, vm: &VirtualMachine, argc: usize) -> FrameResult { let cause = match argc { - 2 => self.get_exception(vm, true)?, - _ => vm.get_none(), + 2 => { + let val = self.pop_value(); + if vm.is_none(&val) { + // if the cause arg is none, we clear the cause + Some(None) + } else { + // if the cause arg is an exception, we overwrite it + Some(Some( + ExceptionCtor::try_from_object(vm, val)?.instantiate(vm)?, + )) + } + } + // if there's no cause arg, we keep the cause as is + _ => None, }; let exception = match argc { 0 => match vm.current_exception() { Some(exc) => exc, None => { - return Err(vm.new_exception( + return Err(vm.new_exception_msg( vm.ctx.exceptions.runtime_error.clone(), "No active exception to reraise".to_string(), - )); + )) } }, - 1 | 2 => self.get_exception(vm, false)?, + 1 | 2 => ExceptionCtor::try_from_object(vm, self.pop_value())?.instantiate(vm)?, 3 => panic!("Not implemented!"), _ => panic!("Invalid parameter for RAISE_VARARGS, must be between 0 to 3"), }; let context = match argc { - 0 => vm.get_none(), // We have already got the exception, - _ => match vm.current_exception() { - Some(exc) => exc, - None => vm.get_none(), - }, + 0 => None, // We have already got the exception, + _ => vm.current_exception(), }; info!( "Exception raised: {:?} with cause: {:?} and context: {:?}", exception, cause, context ); - vm.set_attr(&exception, vm.new_str("__cause__".to_string()), cause)?; - vm.set_attr(&exception, vm.new_str("__context__".to_string()), context)?; + if let Some(cause) = cause { + exception.set_cause(cause); + } + exception.set_context(context); Err(exception) } @@ -1383,29 +1375,6 @@ impl Frame { let stack = self.stack.borrow(); stack[stack.len() - depth - 1].clone() } - - #[cfg_attr(feature = "flame-it", flame("Frame"))] - fn get_exception(&self, vm: &VirtualMachine, none_allowed: bool) -> PyResult { - let exception = self.pop_value(); - if none_allowed && vm.get_none().is(&exception) - || objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) - { - Ok(exception) - } else if let Ok(exc_type) = PyClassRef::try_from_object(vm, exception) { - if objtype::issubclass(&exc_type, &vm.ctx.exceptions.base_exception_type) { - let exception = vm.new_empty_exception(exc_type)?; - Ok(exception) - } else { - let msg = format!( - "Can only raise BaseException derived types, not {}", - exc_type - ); - Err(vm.new_type_error(msg)) - } - } else { - Err(vm.new_type_error("exceptions must derive from BaseException".to_string())) - } - } } impl fmt::Debug for Frame { diff --git a/vm/src/function.rs b/vm/src/function.rs index f189b5b87..41552de72 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -4,6 +4,7 @@ use std::ops::RangeInclusive; use indexmap::IndexMap; +use crate::exceptions::PyBaseExceptionRef; use crate::obj::objtuple::PyTuple; use crate::obj::objtype::{isinstance, PyClassRef}; use crate::pyobject::{ @@ -207,11 +208,11 @@ pub enum ArgumentError { RequiredKeywordArgument(String), /// An exception was raised while binding arguments to the function /// parameters. - Exception(PyObjectRef), + Exception(PyBaseExceptionRef), } -impl From for ArgumentError { - fn from(ex: PyObjectRef) -> Self { +impl From for ArgumentError { + fn from(ex: PyBaseExceptionRef) -> Self { ArgumentError::Exception(ex) } } diff --git a/vm/src/import.rs b/vm/src/import.rs index 6d4e2da1a..866d8c04f 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -4,9 +4,10 @@ use rand::Rng; use crate::bytecode::CodeObject; +use crate::exceptions::PyBaseExceptionRef; use crate::obj::objtraceback::{PyTraceback, PyTracebackRef}; use crate::obj::{objcode, objtype}; -use crate::pyobject::{ItemProtocol, PyObjectRef, PyResult, PyValue}; +use crate::pyobject::{ItemProtocol, PyResult, PyValue}; use crate::scope::Scope; use crate::version::get_git_revision; use crate::vm::{InitParameter, VirtualMachine}; @@ -150,15 +151,15 @@ fn remove_importlib_frames_inner( // TODO: This function should do nothing on verbose mode. // TODO: Fix this function after making PyTraceback.next mutable -pub fn remove_importlib_frames(vm: &VirtualMachine, exc: &PyObjectRef) -> PyObjectRef { +pub fn remove_importlib_frames( + vm: &VirtualMachine, + exc: &PyBaseExceptionRef, +) -> PyBaseExceptionRef { let always_trim = objtype::isinstance(exc, &vm.ctx.exceptions.import_error); - if let Ok(tb) = vm.get_attribute(exc.clone(), "__traceback__") { - let base_tb: PyTracebackRef = tb.downcast().expect("must be a traceback object"); - let trimed_tb = remove_importlib_frames_inner(vm, Some(base_tb), always_trim) - .0 - .map_or(vm.get_none(), |x| x.into_object()); - vm.set_attr(exc, "__traceback__", trimed_tb).unwrap(); + if let Some(tb) = exc.traceback() { + let trimmed_tb = remove_importlib_frames_inner(vm, Some(tb), always_trim).0; + exc.set_traceback(trimmed_tb); } exc.clone() } diff --git a/vm/src/lib.rs b/vm/src/lib.rs index d40c2a1f2..1f8fe9be4 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -53,7 +53,7 @@ pub mod cformat; mod dictdatatype; #[cfg(feature = "rustpython-compiler")] pub mod eval; -mod exceptions; +pub mod exceptions; pub mod format; mod frame; mod frozen; @@ -72,7 +72,6 @@ mod version; mod vm; // pub use self::pyobject::Executor; -pub use self::exceptions::{print_exception, write_exception}; pub use self::vm::{InitParameter, PySettings, VirtualMachine}; pub use rustpython_bytecode::*; diff --git a/vm/src/obj/objcoroutine.rs b/vm/src/obj/objcoroutine.rs index 6d36f81a7..4dad7d71d 100644 --- a/vm/src/obj/objcoroutine.rs +++ b/vm/src/obj/objcoroutine.rs @@ -1,5 +1,6 @@ use super::objiter::new_stop_iteration; -use super::objtype::{isinstance, issubclass, PyClassRef}; +use super::objtype::{isinstance, PyClassRef}; +use crate::exceptions; use crate::frame::{ExecutionResult, FrameRef}; use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; @@ -56,26 +57,18 @@ impl PyCoroutine { #[pymethod] fn throw( &self, - exc_type: PyClassRef, + exc_type: PyObjectRef, exc_val: OptionalArg, exc_tb: OptionalArg, vm: &VirtualMachine, ) -> PyResult { + let exc_val = exc_val.unwrap_or_else(|| vm.get_none()); + let exc_tb = exc_tb.unwrap_or_else(|| vm.get_none()); if self.closed.get() { - return Err(vm.invoke(exc_type.as_object(), vec![])?); - } - // TODO what should we do with the other parameters? CPython normalises them with - // PyErr_NormalizeException, do we want to do the same. - if !issubclass(&exc_type, &vm.ctx.exceptions.base_exception_type) { - return Err(vm.new_type_error("Can't throw non exception".to_string())); + return Err(exceptions::normalize(exc_type, exc_val, exc_tb, vm)?); } vm.frames.borrow_mut().push(self.frame.clone()); - let result = self.frame.gen_throw( - vm, - exc_type, - exc_val.unwrap_or(vm.get_none()), - exc_tb.unwrap_or(vm.get_none()), - ); + let result = self.frame.gen_throw(vm, exc_type, exc_val, exc_tb); self.maybe_close(&result); vm.frames.borrow_mut().pop(); result?.into_result(vm) @@ -89,14 +82,14 @@ impl PyCoroutine { vm.frames.borrow_mut().push(self.frame.clone()); let result = self.frame.gen_throw( vm, - vm.ctx.exceptions.generator_exit.clone(), + vm.ctx.exceptions.generator_exit.clone().into_object(), vm.get_none(), vm.get_none(), ); vm.frames.borrow_mut().pop(); self.closed.set(true); match result { - Ok(ExecutionResult::Yield(_)) => Err(vm.new_exception( + Ok(ExecutionResult::Yield(_)) => Err(vm.new_exception_msg( vm.ctx.exceptions.runtime_error.clone(), "generator ignored GeneratorExit".to_string(), )), @@ -149,7 +142,7 @@ impl PyCoroutineWrapper { #[pymethod] fn throw( &self, - exc_type: PyClassRef, + exc_type: PyObjectRef, exc_val: OptionalArg, exc_tb: OptionalArg, vm: &VirtualMachine, diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 619df1f94..c25c68b71 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -5,6 +5,7 @@ use super::objiter; use super::objstr; use super::objtype::{self, PyClassRef}; use crate::dictdatatype::{self, DictKey}; +use crate::exceptions::PyBaseExceptionRef; use crate::function::{KwArgs, OptionalArg}; use crate::pyobject::{ IdProtocol, IntoPyObject, ItemProtocol, PyAttributes, PyClassImpl, PyContext, PyIterable, @@ -60,7 +61,7 @@ impl PyDictRef { vm: &VirtualMachine, ) -> PyResult<()> { if let OptionalArg::Present(dict_obj) = dict_obj { - let dicted: PyResult = dict_obj.clone().downcast(); + let dicted: Result = dict_obj.clone().downcast(); if let Ok(dict_obj) = dicted { for (key, value) in dict_obj { dict.borrow_mut().insert(vm, &key, value)?; @@ -74,7 +75,7 @@ impl PyDictRef { } else { let iter = objiter::get_iter(vm, &dict_obj)?; loop { - fn err(vm: &VirtualMachine) -> PyObjectRef { + fn err(vm: &VirtualMachine) -> PyBaseExceptionRef { vm.new_type_error("Iterator must have exactly two elements".to_string()) } let element = match objiter::get_next_object(vm, &iter)? { @@ -526,7 +527,7 @@ macro_rules! dict_iterator { let mut position = self.position.get(); let dict = self.dict.entries.borrow(); if dict.has_changed_size(&self.size) { - return Err(vm.new_exception( + return Err(vm.new_exception_msg( vm.ctx.exceptions.runtime_error.clone(), "dictionary changed size during iteration".to_string(), )); diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 0610a2302..fe1dc9851 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -7,6 +7,7 @@ use super::objbytes; use super::objint::{self, PyInt, PyIntRef}; use super::objstr::{PyString, PyStringRef}; use super::objtype::{self, PyClassRef}; +use crate::exceptions::PyBaseExceptionRef; use crate::function::{OptionalArg, OptionalOption}; use crate::pyhash; use crate::pyobject::{ @@ -619,7 +620,7 @@ fn str_to_float(vm: &VirtualMachine, literal: &str) -> PyResult { } } -fn invalid_convert(vm: &VirtualMachine, literal: &str) -> PyObjectRef { +fn invalid_convert(vm: &VirtualMachine, literal: &str) -> PyBaseExceptionRef { vm.new_value_error(format!("could not convert string to float: '{}'", literal)) } diff --git a/vm/src/obj/objgenerator.rs b/vm/src/obj/objgenerator.rs index e98500e20..56927bd19 100644 --- a/vm/src/obj/objgenerator.rs +++ b/vm/src/obj/objgenerator.rs @@ -3,7 +3,8 @@ */ use super::objiter::new_stop_iteration; -use super::objtype::{isinstance, issubclass, PyClassRef}; +use super::objtype::{isinstance, PyClassRef}; +use crate::exceptions; use crate::frame::{ExecutionResult, FrameRef}; use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; @@ -69,26 +70,18 @@ impl PyGenerator { #[pymethod] fn throw( &self, - exc_type: PyClassRef, + exc_type: PyObjectRef, exc_val: OptionalArg, exc_tb: OptionalArg, vm: &VirtualMachine, ) -> PyResult { + let exc_val = exc_val.unwrap_or_else(|| vm.get_none()); + let exc_tb = exc_tb.unwrap_or_else(|| vm.get_none()); if self.closed.get() { - return Err(vm.invoke(exc_type.as_object(), vec![])?); - } - // TODO what should we do with the other parameters? CPython normalises them with - // PyErr_NormalizeException, do we want to do the same. - if !issubclass(&exc_type, &vm.ctx.exceptions.base_exception_type) { - return Err(vm.new_type_error("Can't throw non exception".to_string())); + return Err(exceptions::normalize(exc_type, exc_val, exc_tb, vm)?); } vm.frames.borrow_mut().push(self.frame.clone()); - let result = self.frame.gen_throw( - vm, - exc_type, - exc_val.unwrap_or(vm.get_none()), - exc_tb.unwrap_or(vm.get_none()), - ); + let result = self.frame.gen_throw(vm, exc_type, exc_val, exc_tb); self.maybe_close(&result); vm.frames.borrow_mut().pop(); result?.into_result(vm) @@ -102,14 +95,14 @@ impl PyGenerator { vm.frames.borrow_mut().push(self.frame.clone()); let result = self.frame.gen_throw( vm, - vm.ctx.exceptions.generator_exit.clone(), + vm.ctx.exceptions.generator_exit.clone().into_object(), vm.get_none(), vm.get_none(), ); vm.frames.borrow_mut().pop(); self.closed.set(true); match result { - Ok(ExecutionResult::Yield(_)) => Err(vm.new_exception( + Ok(ExecutionResult::Yield(_)) => Err(vm.new_exception_msg( vm.ctx.exceptions.runtime_error.clone(), "generator ignored GeneratorExit".to_string(), )), diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 748ccf7b4..01f630cf0 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -12,6 +12,7 @@ use super::objbytes::PyBytes; use super::objfloat; use super::objstr::{PyString, PyStringRef}; use super::objtype::{self, PyClassRef}; +use crate::exceptions::PyBaseExceptionRef; use crate::format::FormatSpec; use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyhash; @@ -817,7 +818,7 @@ fn detect_base(literal: &str) -> Option { } } -fn invalid_literal(vm: &VirtualMachine, literal: &str, base: &BigInt) -> PyObjectRef { +fn invalid_literal(vm: &VirtualMachine, literal: &str, base: &BigInt) -> PyBaseExceptionRef { vm.new_value_error(format!( "invalid literal for int() with base {}: '{}'", base, literal diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 5f6df5049..b770e1e33 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -4,8 +4,8 @@ use std::cell::Cell; -use super::objtuple::PyTuple; use super::objtype::{self, PyClassRef}; +use crate::exceptions::PyBaseExceptionRef; use crate::pyobject::{ PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; @@ -68,14 +68,13 @@ pub fn get_all(vm: &VirtualMachine, iter_obj: &PyObjectRef) -> Ok(elements) } -pub fn new_stop_iteration(vm: &VirtualMachine) -> PyObjectRef { +pub fn new_stop_iteration(vm: &VirtualMachine) -> PyBaseExceptionRef { let stop_iteration_type = vm.ctx.exceptions.stop_iteration.clone(); - vm.new_empty_exception(stop_iteration_type).unwrap() + vm.new_exception_empty(stop_iteration_type) } -pub fn stop_iter_value(vm: &VirtualMachine, exc: &PyObjectRef) -> PyResult { - let args = vm.get_attribute(exc.clone(), "args")?; - let args: &PyTuple = args.payload().unwrap(); +pub fn stop_iter_value(vm: &VirtualMachine, exc: &PyBaseExceptionRef) -> PyResult { + let args = exc.args(); let val = args .elements .first() diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index fdab9efa7..d00d3fd14 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -287,8 +287,8 @@ fn _mro(cls: &PyClassRef) -> Vec { /// Determines if `obj` actually an instance of `cls`, this doesn't call __instancecheck__, so only /// use this if `cls` is known to have not overridden the base __instancecheck__ magic method. -#[cfg_attr(feature = "flame-it", flame("objtype"))] -pub fn isinstance(obj: &PyObjectRef, cls: &PyClassRef) -> bool { +#[inline] +pub fn isinstance(obj: &T, cls: &PyClassRef) -> bool { issubclass(&obj.class(), &cls) } diff --git a/vm/src/obj/objweakproxy.rs b/vm/src/obj/objweakproxy.rs index 25d7cc01e..befc30f1d 100644 --- a/vm/src/obj/objweakproxy.rs +++ b/vm/src/obj/objweakproxy.rs @@ -41,7 +41,7 @@ impl PyWeakProxy { fn getattr(&self, attr_name: PyObjectRef, vm: &VirtualMachine) -> PyResult { match self.weak.upgrade() { Some(obj) => vm.get_attribute(obj, attr_name), - None => Err(vm.new_exception( + None => Err(vm.new_exception_msg( vm.ctx.exceptions.reference_error.clone(), "weakly-referenced object no longer exists".to_string(), )), @@ -52,7 +52,7 @@ impl PyWeakProxy { fn setattr(&self, attr_name: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { match self.weak.upgrade() { Some(obj) => vm.set_attr(&obj, attr_name, value), - None => Err(vm.new_exception( + None => Err(vm.new_exception_msg( vm.ctx.exceptions.reference_error.clone(), "weakly-referenced object no longer exists".to_string(), )), diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 633b69f59..0fe1cdd17 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -13,7 +13,7 @@ use num_traits::{One, ToPrimitive, Zero}; use crate::bytecode; use crate::dictdatatype::DictKey; -use crate::exceptions; +use crate::exceptions::{self, PyBaseExceptionRef}; use crate::function::{IntoPyNativeFunc, PyFuncArgs}; use crate::obj::objbuiltinfunc::PyBuiltinFunction; use crate::obj::objbytearray; @@ -64,7 +64,7 @@ pub type PyObjectRef = Rc>; /// Use this type for functions which return a python object or an exception. /// Both the python object and the python exception are `PyObjectRef` types /// since exceptions are also python objects. -pub type PyResult = Result; // A valid value, or an exception +pub type PyResult = Result; // A valid value, or an exception /// For attributes we do not use a dict, but a hashmap. This is probably /// faster, unordered, and only supports strings as keys. @@ -575,9 +575,6 @@ impl PyObject { /// /// If the downcast fails, the original ref is returned in as `Err` so /// another downcast can be attempted without unnecessary cloning. - /// - /// Note: The returned `Result` is _not_ a `PyResult`, even though the - /// types are compatible. pub fn downcast(self: Rc) -> Result, PyObjectRef> { if self.payload_is::() { Ok({ @@ -622,7 +619,7 @@ impl PyRef { if obj.payload_is::() { Ok(Self::new_ref_unchecked(obj)) } else { - Err(vm.new_exception( + Err(vm.new_exception_msg( vm.ctx.exceptions.runtime_error.clone(), format!("Unexpected payload for type {:?}", obj.class().name), )) @@ -802,6 +799,12 @@ impl TypeProtocol for PyRef { } } +impl TypeProtocol for &'_ T { + fn class(&self) -> PyClassRef { + (&**self).class() + } +} + /// The python item protocol. Mostly applies to dictionaries. /// Allows getting, setting and deletion of keys-value pairs. pub trait ItemProtocol { @@ -1082,7 +1085,7 @@ pub trait PyValue: fmt::Debug + Sized + 'static { fn class(vm: &VirtualMachine) -> PyClassRef; fn into_ref(self, vm: &VirtualMachine) -> PyRef { - PyRef::new_ref_unchecked(PyObject::new(self, Self::class(vm), None)) + self.into_ref_with_type_unchecked(Self::class(vm), None) } fn into_ref_with_type(self, vm: &VirtualMachine, cls: PyClassRef) -> PyResult> { @@ -1100,6 +1103,10 @@ pub trait PyValue: fmt::Debug + Sized + 'static { Err(vm.new_type_error(format!("{} is not a subtype of {}", subtype, basetype))) } } + + fn into_ref_with_type_unchecked(self, cls: PyClassRef, dict: Option) -> PyRef { + PyRef::new_ref_unchecked(PyObject::new(self, cls, dict)) + } } pub trait PyObjectPayload: Any + fmt::Debug + 'static { diff --git a/vm/src/stdlib/functools.rs b/vm/src/stdlib/functools.rs index a35bede73..320c57142 100644 --- a/vm/src/stdlib/functools.rs +++ b/vm/src/stdlib/functools.rs @@ -26,7 +26,7 @@ fn functools_reduce( objiter::call_next(vm, &iterator).map_err(|err| { if objtype::isinstance(&err, &vm.ctx.exceptions.stop_iteration) { let exc_type = vm.ctx.exceptions.type_error.clone(); - vm.new_exception( + vm.new_exception_msg( exc_type, "reduce() of empty sequence with no initial value".to_string(), ) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index ecf1c9d3b..dc53d053c 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -386,7 +386,7 @@ fn io_base_checkclosed( let msg = msg .flat_option() .unwrap_or_else(|| vm.new_str("I/O operation on closed file.".to_string())); - Err(vm.new_exception_obj(vm.ctx.exceptions.value_error.clone(), vec![msg])?) + Err(vm.new_exception(vm.ctx.exceptions.value_error.clone(), vec![msg])) } else { Ok(()) } @@ -401,7 +401,7 @@ fn io_base_checkreadable( let msg = msg .flat_option() .unwrap_or_else(|| vm.new_str("File or stream is not readable.".to_string())); - Err(vm.new_exception_obj(vm.ctx.exceptions.value_error.clone(), vec![msg])?) + Err(vm.new_exception(vm.ctx.exceptions.value_error.clone(), vec![msg])) } else { Ok(()) } @@ -416,7 +416,7 @@ fn io_base_checkwritable( let msg = msg .flat_option() .unwrap_or_else(|| vm.new_str("File or stream is not writable.".to_string())); - Err(vm.new_exception_obj(vm.ctx.exceptions.value_error.clone(), vec![msg])?) + Err(vm.new_exception(vm.ctx.exceptions.value_error.clone(), vec![msg])) } else { Ok(()) } @@ -431,7 +431,7 @@ fn io_base_checkseekable( let msg = msg .flat_option() .unwrap_or_else(|| vm.new_str("File or stream is not seekable.".to_string())); - Err(vm.new_exception_obj(vm.ctx.exceptions.value_error.clone(), vec![msg])?) + Err(vm.new_exception(vm.ctx.exceptions.value_error.clone(), vec![msg])) } else { Ok(()) } diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index 17a9c2d4f..88216acc3 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -46,10 +46,10 @@ pub fn json_loads(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { .unwrap(); let json_decode_error = vm.get_attribute(module, "JSONDecodeError").unwrap(); let json_decode_error = json_decode_error.downcast().unwrap(); - let exc = vm.new_exception(json_decode_error, format!("{}", err)); - vm.set_attr(&exc, "lineno", vm.ctx.new_int(err.line())) + let exc = vm.new_exception_msg(json_decode_error, format!("{}", err)); + vm.set_attr(exc.as_object(), "lineno", vm.ctx.new_int(err.line())) .unwrap(); - vm.set_attr(&exc, "colno", vm.ctx.new_int(err.column())) + vm.set_attr(exc.as_object(), "colno", vm.ctx.new_int(err.column())) .unwrap(); exc }) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 10230feaa..2aef879f4 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -24,6 +24,7 @@ use nix::unistd::{self, Gid, Pid, Uid, Whence}; use std::os::unix::io::RawFd; use super::errno::errors; +use crate::exceptions::PyBaseExceptionRef; use crate::function::{IntoPyNativeFunc, OptionalArg, PyFuncArgs}; use crate::obj::objbyteinner::PyBytesLike; use crate::obj::objbytes::PyBytesRef; @@ -155,7 +156,7 @@ pub fn os_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { unimplemented!() } -pub fn convert_io_error(vm: &VirtualMachine, err: io::Error) -> PyObjectRef { +pub fn convert_io_error(vm: &VirtualMachine, err: io::Error) -> PyBaseExceptionRef { #[allow(unreachable_patterns)] // some errors are just aliases of each other let exc_type = match err.kind() { ErrorKind::NotFound => vm.ctx.exceptions.file_not_found_error.clone(), @@ -170,38 +171,38 @@ pub fn convert_io_error(vm: &VirtualMachine, err: io::Error) -> PyObjectRef { _ => vm.ctx.exceptions.os_error.clone(), }, }; - let os_error = vm.new_exception(exc_type, err.to_string()); + let os_error = vm.new_exception_msg(exc_type, err.to_string()); let errno = match err.raw_os_error() { Some(errno) => vm.new_int(errno), None => vm.get_none(), }; - vm.set_attr(&os_error, "errno", errno).unwrap(); + vm.set_attr(os_error.as_object(), "errno", errno).unwrap(); os_error } #[cfg(unix)] -pub fn convert_nix_error(vm: &VirtualMachine, err: nix::Error) -> PyObjectRef { +pub fn convert_nix_error(vm: &VirtualMachine, err: nix::Error) -> PyBaseExceptionRef { let nix_error = match err { nix::Error::InvalidPath => { let exc_type = vm.ctx.exceptions.file_not_found_error.clone(); - vm.new_exception(exc_type, err.to_string()) + vm.new_exception_msg(exc_type, err.to_string()) } nix::Error::InvalidUtf8 => { let exc_type = vm.ctx.exceptions.unicode_error.clone(); - vm.new_exception(exc_type, err.to_string()) + vm.new_exception_msg(exc_type, err.to_string()) } nix::Error::UnsupportedOperation => { let exc_type = vm.ctx.exceptions.runtime_error.clone(); - vm.new_exception(exc_type, err.to_string()) + vm.new_exception_msg(exc_type, err.to_string()) } nix::Error::Sys(errno) => { let exc_type = convert_nix_errno(vm, errno); - vm.new_exception(exc_type, err.to_string()) + vm.new_exception_msg(exc_type, err.to_string()) } }; if let nix::Error::Sys(errno) = err { - vm.set_attr(&nix_error, "errno", vm.ctx.new_int(errno as i32)) + vm.set_attr(nix_error.as_object(), "errno", vm.ctx.new_int(errno as i32)) .unwrap(); } @@ -218,7 +219,7 @@ fn convert_nix_errno(vm: &VirtualMachine, errno: Errno) -> PyClassRef { /// Convert the error stored in the `errno` variable into an Exception #[inline] -pub fn errno_err(vm: &VirtualMachine) -> PyObjectRef { +pub fn errno_err(vm: &VirtualMachine) -> PyBaseExceptionRef { convert_io_error(vm, io::Error::last_os_error()) } @@ -1055,18 +1056,18 @@ fn os_getegid(vm: &VirtualMachine) -> PyObjectRef { } #[cfg(unix)] -fn os_getpgid(pid: u32, vm: &VirtualMachine) -> PyObjectRef { +fn os_getpgid(pid: u32, vm: &VirtualMachine) -> PyResult { match unistd::getpgid(Some(Pid::from_raw(pid as i32))) { - Ok(pgid) => vm.new_int(pgid.as_raw()), - Err(err) => convert_nix_error(vm, err), + Ok(pgid) => Ok(vm.new_int(pgid.as_raw())), + Err(err) => Err(convert_nix_error(vm, err)), } } #[cfg(all(unix, not(target_os = "redox")))] -fn os_getsid(pid: u32, vm: &VirtualMachine) -> PyObjectRef { +fn os_getsid(pid: u32, vm: &VirtualMachine) -> PyResult { match unistd::getsid(Some(Pid::from_raw(pid as i32))) { - Ok(sid) => vm.new_int(sid.as_raw()), - Err(err) => convert_nix_error(vm, err), + Ok(sid) => Ok(vm.new_int(sid.as_raw())), + Err(err) => Err(convert_nix_error(vm, err)), } } diff --git a/vm/src/stdlib/random.rs b/vm/src/stdlib/random.rs index 398ce749b..f175423f2 100644 --- a/vm/src/stdlib/random.rs +++ b/vm/src/stdlib/random.rs @@ -19,7 +19,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { fn random_normalvariate(mu: f64, sigma: f64, vm: &VirtualMachine) -> PyResult { let normal = Normal::new(mu, sigma).map_err(|rand_err| { - vm.new_exception( + vm.new_exception_msg( vm.ctx.exceptions.arithmetic_error.clone(), format!("invalid normal distribution: {:?}", rand_err), ) diff --git a/vm/src/stdlib/signal.rs b/vm/src/stdlib/signal.rs index f20679b51..3cdb91341 100644 --- a/vm/src/stdlib/signal.rs +++ b/vm/src/stdlib/signal.rs @@ -108,7 +108,7 @@ pub fn check_signals(vm: &VirtualMachine) -> PyResult<()> { } fn default_int_handler(_signum: PyObjectRef, _arg: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm.new_empty_exception(vm.ctx.exceptions.keyboard_interrupt.clone())?) + Err(vm.new_exception_empty(vm.ctx.exceptions.keyboard_interrupt.clone())) } pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index e1fc6b693..c6cbfc8a8 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -12,6 +12,7 @@ use socket2::{Domain, Protocol, Socket, Type as SocketType}; use super::os::convert_io_error; #[cfg(unix)] use super::os::convert_nix_error; +use crate::exceptions::PyBaseExceptionRef; use crate::function::{OptionalArg, PyFuncArgs}; use crate::obj::objbytearray::PyByteArrayRef; use crate::obj::objbyteinner::PyBytesLike; @@ -484,7 +485,7 @@ fn socket_getaddrinfo(opts: GAIOptions, vm: &VirtualMachine) -> PyResult { let addrs = dns_lookup::getaddrinfo(host, port, Some(hints)).map_err(|err| { let error_type = vm.class("_socket", "gaierror"); - vm.new_exception(error_type, io::Error::from(err).to_string()) + vm.new_exception_msg(error_type, io::Error::from(err).to_string()) })?; let list = addrs @@ -536,7 +537,7 @@ where Ok(mut sock_addrs) => { if sock_addrs.len() == 0 { let error_type = vm.class("_socket", "gaierror"); - Err(vm.new_exception( + Err(vm.new_exception_msg( error_type, "nodename nor servname provided, or not known".to_string(), )) @@ -546,7 +547,7 @@ where } Err(e) => { let error_type = vm.class("_socket", "gaierror"); - Err(vm.new_exception(error_type, e.to_string())) + Err(vm.new_exception_msg(error_type, e.to_string())) } } } @@ -589,10 +590,10 @@ fn invalid_sock() -> Socket { } } -fn convert_sock_error(vm: &VirtualMachine, err: io::Error) -> PyObjectRef { +fn convert_sock_error(vm: &VirtualMachine, err: io::Error) -> PyBaseExceptionRef { if err.kind() == io::ErrorKind::TimedOut { let socket_timeout = vm.class("_socket", "timeout"); - vm.new_exception(socket_timeout, "Timed out".to_string()) + vm.new_exception_msg(socket_timeout, "Timed out".to_string()) } else { convert_io_error(vm, err) } diff --git a/vm/src/stdlib/subprocess.rs b/vm/src/stdlib/subprocess.rs index b2c538d75..bf6738694 100644 --- a/vm/src/stdlib/subprocess.rs +++ b/vm/src/stdlib/subprocess.rs @@ -152,7 +152,7 @@ impl PopenRef { .map_err(|s| vm.new_os_error(format!("Could not start program: {}", s)))?; if timeout.is_none() { let timeout_expired = vm.try_class("_subprocess", "TimeoutExpired")?; - Err(vm.new_exception(timeout_expired, "Timeout".to_string())) + Err(vm.new_exception_msg(timeout_expired, "Timeout".to_string())) } else { Ok(()) } diff --git a/vm/src/stdlib/symtable.rs b/vm/src/stdlib/symtable.rs index 51cee7ab2..386fa1441 100644 --- a/vm/src/stdlib/symtable.rs +++ b/vm/src/stdlib/symtable.rs @@ -110,7 +110,7 @@ impl PySymbolTable { } .into_ref(vm)) } else { - Err(vm.ctx.new_str(name.to_string())) + Err(vm.new_lookup_error(name.to_string())) } } diff --git a/vm/src/stdlib/zlib.rs b/vm/src/stdlib/zlib.rs index 816c4b641..62ef5df4c 100644 --- a/vm/src/stdlib/zlib.rs +++ b/vm/src/stdlib/zlib.rs @@ -1,3 +1,4 @@ +use crate::exceptions::PyBaseExceptionRef; use crate::function::OptionalArg; use crate::obj::objbytes::PyBytesRef; use crate::pyobject::{ItemProtocol, PyObjectRef, PyResult}; @@ -110,7 +111,7 @@ fn zlib_decompress( } } -fn zlib_error(message: &str, vm: &VirtualMachine) -> PyObjectRef { +fn zlib_error(message: &str, vm: &VirtualMachine) -> PyBaseExceptionRef { let module = vm .get_attribute(vm.sys_module.clone(), "modules") .unwrap() @@ -120,5 +121,5 @@ fn zlib_error(message: &str, vm: &VirtualMachine) -> PyObjectRef { let zlib_error = vm.get_attribute(module, "error").unwrap(); let zlib_error = zlib_error.downcast().unwrap(); - vm.new_exception(zlib_error, message.to_string()) + vm.new_exception_msg(zlib_error, message.to_string()) } diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index e49fcd7af..2b943f801 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -174,15 +174,18 @@ fn sys_intern(value: PyStringRef, _vm: &VirtualMachine) -> PyStringRef { value } -fn sys_exc_info(vm: &VirtualMachine) -> PyResult { - Ok(vm.ctx.new_tuple(match vm.current_exception() { +fn sys_exc_info(vm: &VirtualMachine) -> PyObjectRef { + let exc_info = match vm.current_exception() { Some(exception) => vec![ exception.class().into_object(), - exception.clone(), - vm.get_none(), + exception.clone().into_object(), + exception + .traceback() + .map_or(vm.get_none(), |tb| tb.into_object()), ], None => vec![vm.get_none(), vm.get_none(), vm.get_none()], - })) + }; + vm.ctx.new_tuple(exc_info) } fn sys_git_info(vm: &VirtualMachine) -> PyObjectRef { @@ -195,7 +198,7 @@ fn sys_git_info(vm: &VirtualMachine) -> PyObjectRef { fn sys_exit(code: OptionalArg, vm: &VirtualMachine) -> PyResult { let code = code.unwrap_or_else(|| vm.new_int(0)); - Err(vm.new_exception_obj(vm.ctx.exceptions.system_exit.clone(), vec![code])?) + Err(vm.new_exception(vm.ctx.exceptions.system_exit.clone(), vec![code])) } pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectRef) { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index b4c738605..488f168fb 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -19,6 +19,7 @@ use rustpython_compiler::{compile, error::CompileError}; use crate::builtins::{self, to_ascii}; use crate::bytecode; +use crate::exceptions::{PyBaseException, PyBaseExceptionRef}; use crate::frame::{ExecutionResult, Frame, FrameRef}; use crate::frozen; use crate::function::PyFuncArgs; @@ -59,7 +60,7 @@ pub struct VirtualMachine { pub ctx: PyContext, pub frames: RefCell>, pub wasm_id: Option, - pub exceptions: RefCell>, + pub exceptions: RefCell>, pub frozen: RefCell>, pub import_func: RefCell, pub profile_func: RefCell, @@ -328,41 +329,64 @@ impl VirtualMachine { PyObject::new(PyModule {}, self.ctx.types.module_type.clone(), Some(dict)) } - #[cfg_attr(feature = "flame-it", flame("VirtualMachine"))] - pub fn new_exception_obj(&self, exc_type: PyClassRef, args: Vec) -> PyResult { + /// Instantiate an exception with arguments. + /// This function should only be used with builtin exception types; if a user-defined exception + /// type is passed in, it may not be fully initialized; try using [`exceptions::invoke`](invoke) + /// or [`exceptions::ExceptionCtor`](ctor) instead. + /// + /// [invoke]: rustpython_vm::exceptions::invoke + /// [ctor]: rustpython_vm::exceptions::ExceptionCtor + pub fn new_exception( + &self, + exc_type: PyClassRef, + args: Vec, + ) -> PyBaseExceptionRef { // TODO: add repr of args into logging? vm_trace!("New exception created: {}", exc_type.name); - self.invoke(&exc_type.into_object(), args) + PyBaseException::new(args, self) + .into_ref_with_type_unchecked(exc_type, Some(self.ctx.new_dict())) } - pub fn new_empty_exception(&self, exc_type: PyClassRef) -> PyResult { - self.new_exception_obj(exc_type, vec![]) + /// Instantiate an exception with no arguments. + /// This function should only be used with builtin exception types; if a user-defined exception + /// type is passed in, it may not be fully initialized; try using [`exceptions::invoke`](invoke) + /// or [`exceptions::ExceptionCtor`](ctor) instead. + /// + /// [invoke]: rustpython_vm::exceptions::invoke + /// [ctor]: rustpython_vm::exceptions::ExceptionCtor + pub fn new_exception_empty(&self, exc_type: PyClassRef) -> PyBaseExceptionRef { + self.new_exception(exc_type, vec![]) } - /// Create Python instance of `exc_type` with message as first element of `args` tuple - pub fn new_exception(&self, exc_type: PyClassRef, msg: String) -> PyObjectRef { - let pystr_msg = self.new_str(msg); - self.new_exception_obj(exc_type, vec![pystr_msg]).unwrap() + /// Instantiate an exception with `msg` as the only argument. + /// This function should only be used with builtin exception types; if a user-defined exception + /// type is passed in, it may not be fully initialized; try using [`exceptions::invoke`](invoke) + /// or [`exceptions::ExceptionCtor`](ctor) instead. + /// + /// [invoke]: rustpython_vm::exceptions::invoke + /// [ctor]: rustpython_vm::exceptions::ExceptionCtor + pub fn new_exception_msg(&self, exc_type: PyClassRef, msg: String) -> PyBaseExceptionRef { + self.new_exception(exc_type, vec![self.new_str(msg)]) } - pub fn new_lookup_error(&self, msg: String) -> PyObjectRef { + pub fn new_lookup_error(&self, msg: String) -> PyBaseExceptionRef { let lookup_error = self.ctx.exceptions.lookup_error.clone(); - self.new_exception(lookup_error, msg) + self.new_exception_msg(lookup_error, msg) } - pub fn new_attribute_error(&self, msg: String) -> PyObjectRef { + pub fn new_attribute_error(&self, msg: String) -> PyBaseExceptionRef { let attribute_error = self.ctx.exceptions.attribute_error.clone(); - self.new_exception(attribute_error, msg) + self.new_exception_msg(attribute_error, msg) } - pub fn new_type_error(&self, msg: String) -> PyObjectRef { + pub fn new_type_error(&self, msg: String) -> PyBaseExceptionRef { let type_error = self.ctx.exceptions.type_error.clone(); - self.new_exception(type_error, msg) + self.new_exception_msg(type_error, msg) } - pub fn new_name_error(&self, msg: String) -> PyObjectRef { + pub fn new_name_error(&self, msg: String) -> PyBaseExceptionRef { let name_error = self.ctx.exceptions.name_error.clone(); - self.new_exception(name_error, msg) + self.new_exception_msg(name_error, msg) } pub fn new_unsupported_operand_error( @@ -370,7 +394,7 @@ impl VirtualMachine { a: PyObjectRef, b: PyObjectRef, op: &str, - ) -> PyObjectRef { + ) -> PyBaseExceptionRef { self.new_type_error(format!( "Unsupported operand types for '{}': '{}' and '{}'", op, @@ -379,60 +403,60 @@ impl VirtualMachine { )) } - pub fn new_os_error(&self, msg: String) -> PyObjectRef { + pub fn new_os_error(&self, msg: String) -> PyBaseExceptionRef { let os_error = self.ctx.exceptions.os_error.clone(); - self.new_exception(os_error, msg) + self.new_exception_msg(os_error, msg) } - pub fn new_unicode_decode_error(&self, msg: String) -> PyObjectRef { + pub fn new_unicode_decode_error(&self, msg: String) -> PyBaseExceptionRef { let unicode_decode_error = self.ctx.exceptions.unicode_decode_error.clone(); - self.new_exception(unicode_decode_error, msg) + self.new_exception_msg(unicode_decode_error, msg) } - pub fn new_unicode_encode_error(&self, msg: String) -> PyObjectRef { + pub fn new_unicode_encode_error(&self, msg: String) -> PyBaseExceptionRef { let unicode_encode_error = self.ctx.exceptions.unicode_encode_error.clone(); - self.new_exception(unicode_encode_error, msg) + self.new_exception_msg(unicode_encode_error, msg) } /// Create a new python ValueError object. Useful for raising errors from /// python functions implemented in rust. - pub fn new_value_error(&self, msg: String) -> PyObjectRef { + pub fn new_value_error(&self, msg: String) -> PyBaseExceptionRef { let value_error = self.ctx.exceptions.value_error.clone(); - self.new_exception(value_error, msg) + self.new_exception_msg(value_error, msg) } - pub fn new_key_error(&self, obj: PyObjectRef) -> PyObjectRef { + pub fn new_key_error(&self, obj: PyObjectRef) -> PyBaseExceptionRef { let key_error = self.ctx.exceptions.key_error.clone(); - self.new_exception_obj(key_error, vec![obj]).unwrap() + self.new_exception(key_error, vec![obj]) } - pub fn new_index_error(&self, msg: String) -> PyObjectRef { + pub fn new_index_error(&self, msg: String) -> PyBaseExceptionRef { let index_error = self.ctx.exceptions.index_error.clone(); - self.new_exception(index_error, msg) + self.new_exception_msg(index_error, msg) } - pub fn new_not_implemented_error(&self, msg: String) -> PyObjectRef { + pub fn new_not_implemented_error(&self, msg: String) -> PyBaseExceptionRef { let not_implemented_error = self.ctx.exceptions.not_implemented_error.clone(); - self.new_exception(not_implemented_error, msg) + self.new_exception_msg(not_implemented_error, msg) } - pub fn new_recursion_error(&self, msg: String) -> PyObjectRef { + pub fn new_recursion_error(&self, msg: String) -> PyBaseExceptionRef { let recursion_error = self.ctx.exceptions.recursion_error.clone(); - self.new_exception(recursion_error, msg) + self.new_exception_msg(recursion_error, msg) } - pub fn new_zero_division_error(&self, msg: String) -> PyObjectRef { + pub fn new_zero_division_error(&self, msg: String) -> PyBaseExceptionRef { let zero_division_error = self.ctx.exceptions.zero_division_error.clone(); - self.new_exception(zero_division_error, msg) + self.new_exception_msg(zero_division_error, msg) } - pub fn new_overflow_error(&self, msg: String) -> PyObjectRef { + pub fn new_overflow_error(&self, msg: String) -> PyBaseExceptionRef { let overflow_error = self.ctx.exceptions.overflow_error.clone(); - self.new_exception(overflow_error, msg) + self.new_exception_msg(overflow_error, msg) } #[cfg(feature = "rustpython-compiler")] - pub fn new_syntax_error(&self, error: &CompileError) -> PyObjectRef { + pub fn new_syntax_error(&self, error: &CompileError) -> PyBaseExceptionRef { let syntax_error_type = if error.is_indentation_error() { self.ctx.exceptions.indentation_error.clone() } else if error.is_tab_error() { @@ -440,25 +464,31 @@ impl VirtualMachine { } else { self.ctx.exceptions.syntax_error.clone() }; - let syntax_error = self.new_exception(syntax_error_type, error.to_string()); + let syntax_error = self.new_exception_msg(syntax_error_type, error.to_string()); let lineno = self.new_int(error.location.row()); let offset = self.new_int(error.location.column()); - self.set_attr(&syntax_error, "lineno", lineno).unwrap(); - self.set_attr(&syntax_error, "offset", offset).unwrap(); + self.set_attr(syntax_error.as_object(), "lineno", lineno) + .unwrap(); + self.set_attr(syntax_error.as_object(), "offset", offset) + .unwrap(); if let Some(v) = error.statement.as_ref() { - self.set_attr(&syntax_error, "text", self.new_str(v.to_owned())) + self.set_attr(syntax_error.as_object(), "text", self.new_str(v.to_owned())) .unwrap(); } if let Some(path) = error.source_path.as_ref() { - self.set_attr(&syntax_error, "filename", self.new_str(path.to_owned())) - .unwrap(); + self.set_attr( + syntax_error.as_object(), + "filename", + self.new_str(path.to_owned()), + ) + .unwrap(); } syntax_error } - pub fn new_import_error(&self, msg: String) -> PyObjectRef { + pub fn new_import_error(&self, msg: String) -> PyBaseExceptionRef { let import_error = self.ctx.exceptions.import_error.clone(); - self.new_exception(import_error, msg) + self.new_exception_msg(import_error, msg) } pub fn new_scope_with_builtins(&self) -> Scope { @@ -1358,15 +1388,15 @@ impl VirtualMachine { } } - pub fn push_exception(&self, exc: PyObjectRef) { + pub fn push_exception(&self, exc: PyBaseExceptionRef) { self.exceptions.borrow_mut().push(exc) } - pub fn pop_exception(&self) -> Option { + pub fn pop_exception(&self) -> Option { self.exceptions.borrow_mut().pop() } - pub fn current_exception(&self) -> Option { + pub fn current_exception(&self) -> Option { self.exceptions.borrow().last().cloned() } diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index 48a9b0738..c96b2a58c 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -25,8 +25,6 @@ serde-wasm-bindgen = "0.1" serde = "1.0" js-sys = "0.3" futures = "0.1" -num-traits = "0.2" -num-bigint = { version = "0.2.3", features = ["serde"] } [dependencies.web-sys] version = "0.3" diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index 86c0d93f9..84aeb3c8c 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -1,15 +1,12 @@ use futures::Future; use js_sys::Promise; -use num_traits::cast::ToPrimitive; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::{future_to_promise, JsFuture}; use rustpython_vm::function::{OptionalArg, PyFuncArgs}; use rustpython_vm::import::import_file; -use rustpython_vm::obj::{ - objdict::PyDictRef, objint::PyIntRef, objstr::PyStringRef, objtype::PyClassRef, -}; +use rustpython_vm::obj::{objdict::PyDictRef, objstr::PyStringRef, objtype::PyClassRef}; use rustpython_vm::pyobject::{ PyCallable, PyClassImpl, PyObject, PyObjectRef, PyRef, PyResult, PyValue, }; @@ -148,14 +145,7 @@ fn browser_request_animation_frame(func: PyCallable, vm: &VirtualMachine) -> PyR Ok(vm.ctx.new_int(id)) } -fn browser_cancel_animation_frame(id: PyIntRef, vm: &VirtualMachine) -> PyResult { - let id = id.as_bigint().to_i32().ok_or_else(|| { - vm.new_exception( - vm.ctx.exceptions.value_error.clone(), - "Integer too large to convert to i32 for animationFrame id".into(), - ) - })?; - +fn browser_cancel_animation_frame(id: i32, vm: &VirtualMachine) -> PyResult { window() .cancel_animation_frame(id) .map_err(|err| convert::js_py_typeerror(vm, err))?; @@ -308,7 +298,7 @@ impl Element { fn set_attr(&self, attr: PyStringRef, value: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { self.elem .set_attribute(attr.as_str(), value.as_str()) - .map_err(|err| convert::js_to_py(vm, err)) + .map_err(|err| convert::js_py_typeerror(vm, err)) } } diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 1bba54629..ffb324018 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -1,20 +1,18 @@ use js_sys::{Array, ArrayBuffer, Object, Promise, Reflect, Uint8Array}; -use num_traits::cast::ToPrimitive; use serde_wasm_bindgen; use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; +use rustpython_vm::exceptions::PyBaseExceptionRef; use rustpython_vm::function::PyFuncArgs; -use rustpython_vm::obj::{objbytes, objint, objsequence, objtype}; +use rustpython_vm::obj::{objbytes, objtype}; use rustpython_vm::py_serde; use rustpython_vm::pyobject::{ItemProtocol, PyObjectRef, PyResult, PyValue}; use rustpython_vm::VirtualMachine; -use num_bigint::BigInt; - use crate::browser_module; use crate::vm_class::{stored_vm_from_wasm, WASMVirtualMachine}; -pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyObjectRef) -> JsValue { +pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyBaseExceptionRef) -> JsValue { macro_rules! map_exceptions { ($py_exc:ident, $msg:expr, { $($py_exc_ty:expr => $js_err_new:expr),*$(,)? }) => { $(if objtype::isinstance($py_exc, $py_exc_ty) { @@ -24,11 +22,11 @@ pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyObjectRef) -> JsValue { } }; } - let msg = match vm.to_pystr(py_err) { + let msg = match vm.to_str(py_err.as_object()) { Ok(msg) => msg, Err(_) => return js_sys::Error::new("error getting error").into(), }; - let js_err = map_exceptions!(py_err,& msg, { + let js_err = map_exceptions!(py_err, msg.as_str(), { // TypeError is sort of a catch-all for "this value isn't what I thought it was like" &vm.ctx.exceptions.type_error => js_sys::TypeError::new, &vm.ctx.exceptions.value_error => js_sys::TypeError::new, @@ -38,31 +36,36 @@ pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyObjectRef) -> JsValue { &vm.ctx.exceptions.name_error => js_sys::ReferenceError::new, &vm.ctx.exceptions.syntax_error => js_sys::SyntaxError::new, }); - if let Ok(tb) = vm.get_attribute(py_err.clone(), "__traceback__") { - if objtype::isinstance(&tb, &vm.ctx.list_type()) { - let elements = objsequence::get_elements_list(&tb).to_vec(); - if let Some(top) = elements.get(0) { - if objtype::isinstance(&top, &vm.ctx.tuple_type()) { - let element = objsequence::get_elements_tuple(&top); - - if let Some(lineno) = objint::to_int(vm, &element[1], &BigInt::from(10)) - .ok() - .and_then(|lineno| lineno.to_u32()) - { - let _ = Reflect::set(&js_err, &"row".into(), &lineno.into()); - } - } - } - } + if let Some(tb) = py_err.traceback() { + let _ = Reflect::set(&js_err, &"row".into(), &(tb.lineno as u32).into()); } js_err } -pub fn js_py_typeerror(vm: &VirtualMachine, js_err: JsValue) -> PyObjectRef { +pub fn js_py_typeerror(vm: &VirtualMachine, js_err: JsValue) -> PyBaseExceptionRef { let msg = js_err.unchecked_into::().to_string(); vm.new_type_error(msg.into()) } +pub fn js_err_to_py_err(vm: &VirtualMachine, js_err: &JsValue) -> PyBaseExceptionRef { + match js_err.dyn_ref::() { + Some(err) => { + let exc_type = match String::from(err.name()).as_str() { + "TypeError" => &vm.ctx.exceptions.type_error, + "ReferenceError" => &vm.ctx.exceptions.name_error, + "SyntaxError" => &vm.ctx.exceptions.syntax_error, + _ => &vm.ctx.exceptions.exception_type, + } + .clone(); + vm.new_exception_msg(exc_type, err.message().into()) + } + None => vm.new_exception_msg( + vm.ctx.exceptions.exception_type.clone(), + format!("{:?}", js_err), + ), + } +} + pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue { if let Some(ref wasm_id) = vm.wasm_id { if objtype::isinstance(&py_obj, &vm.ctx.function_type()) { @@ -207,17 +210,10 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef { } func.apply(&this, &js_args) .map(|val| js_to_py(vm, val)) - .map_err(|err| js_to_py(vm, err)) + .map_err(|err| js_err_to_py_err(vm, &err)) }) } else if let Some(err) = js_val.dyn_ref::() { - let exc_type = match String::from(err.name()).as_str() { - "TypeError" => &vm.ctx.exceptions.type_error, - "ReferenceError" => &vm.ctx.exceptions.name_error, - "SyntaxError" => &vm.ctx.exceptions.syntax_error, - _ => &vm.ctx.exceptions.exception_type, - } - .clone(); - vm.new_exception(exc_type, err.message().into()) + js_err_to_py_err(vm, err).into_object() } else if js_val.is_undefined() { // Because `JSON.stringify(undefined)` returns undefined vm.get_none() diff --git a/wasm/lib/src/js_module.rs b/wasm/lib/src/js_module.rs index 9622613f7..f640b1d14 100644 --- a/wasm/lib/src/js_module.rs +++ b/wasm/lib/src/js_module.rs @@ -1,4 +1,5 @@ use js_sys::{Array, Object, Reflect}; +use rustpython_vm::exceptions::PyBaseExceptionRef; use rustpython_vm::function::Args; use rustpython_vm::obj::{objfloat::PyFloatRef, objstr::PyStringRef, objtype::PyClassRef}; use rustpython_vm::pyobject::{PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject}; @@ -233,10 +234,14 @@ struct NewObjectOptions { prototype: Option, } -fn new_js_error(vm: &VirtualMachine, err: JsValue) -> PyObjectRef { - let exc = vm.new_exception(vm.class("_js", "JsError"), format!("{:?}", err)); - vm.set_attr(&exc, "js_value", PyJsValue::new(err).into_ref(vm)) - .unwrap(); +fn new_js_error(vm: &VirtualMachine, err: JsValue) -> PyBaseExceptionRef { + let exc = vm.new_exception_msg(vm.class("_js", "JsError"), format!("{:?}", err)); + vm.set_attr( + exc.as_object(), + "js_value", + PyJsValue::new(err).into_ref(vm), + ) + .unwrap(); exc } diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 3f9a318fa..57c9e0c7c 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -232,7 +232,7 @@ impl WASMVirtualMachine { &JsValue::UNDEFINED, &wasm_builtins::format_print_args(vm, args)?.into(), ) - .map_err(|err| convert::js_to_py(vm, err))?; + .map_err(|err| convert::js_py_typeerror(vm, err))?; Ok(vm.get_none()) }) } else if stdout.is_null() {