From cd726a281866d20d5ee436c8a2f70225fba21e78 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Tue, 17 Dec 2019 11:43:23 -0600 Subject: [PATCH 01/10] Cleanup exceptions a bit --- Cargo.lock | 10 +-- src/main.rs | 8 +-- src/shell.rs | 2 +- vm/Cargo.toml | 2 +- vm/src/exceptions.rs | 141 +++++++++++++++--------------------------- vm/src/lib.rs | 2 +- vm/src/obj/objiter.rs | 6 +- 7 files changed, 65 insertions(+), 106 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa01ced91..52474001f 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)", @@ -2226,7 +2226,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..8413c7471 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, PyBaseExceptionRef}, 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,8 @@ 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 err: PyBaseExceptionRef = err.downcast().unwrap(); + 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..1b6a10d5a 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -5,8 +5,8 @@ mod rustyline_helper; use rustpython_compiler::{compile, error::CompileError, error::CompileErrorType}; use rustpython_parser::error::ParseErrorType; use rustpython_vm::{ + exceptions::print_exception, obj::objtype, - print_exception, pyobject::{ItemProtocol, PyObjectRef, PyResult}, scope::Scope, VirtualMachine, 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/exceptions.rs b/vm/src/exceptions.rs index 2a66e555c..39dd3dc36 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1,8 +1,7 @@ use crate::function::PyFuncArgs; -use crate::obj::objiter; +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::pyobject::{ IdProtocol, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, @@ -47,7 +46,7 @@ impl PyBaseException { #[pyslot(new)] fn tp_new( cls: PyClassRef, - _args: PyFuncArgs, + args: PyFuncArgs, vm: &VirtualMachine, ) -> PyResult { PyBaseException { @@ -55,7 +54,7 @@ impl PyBaseException { 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.args).into_ref(vm)), } .into_ref_with_type(vm, cls) } @@ -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() } @@ -127,6 +126,30 @@ 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() + } } /// Print exception chain @@ -207,27 +230,20 @@ pub fn print_exception_inner( vm: &VirtualMachine, exc: &PyObjectRef, ) -> 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, - }; - } + let exc: PyBaseExceptionRef = exc.clone().downcast().unwrap(); + + 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 { + print_traceback_entry(&mut output, traceback)?; + tb = &traceback.next; } } 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,73 +263,30 @@ 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)) -} - -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); - - 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)) -} - #[derive(Debug)] pub struct ExceptionZoo { pub arithmetic_error: PyClassRef, @@ -508,10 +481,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,12 +498,7 @@ 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 { @@ -556,11 +521,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 +530,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/lib.rs b/vm/src/lib.rs index d40c2a1f2..71f206a09 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; diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 5f6df5049..79ade9f4f 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, }; @@ -74,8 +74,8 @@ pub fn new_stop_iteration(vm: &VirtualMachine) -> PyObjectRef { } pub fn stop_iter_value(vm: &VirtualMachine, exc: &PyObjectRef) -> PyResult { - let args = vm.get_attribute(exc.clone(), "args")?; - let args: &PyTuple = args.payload().unwrap(); + let exc = PyBaseExceptionRef::try_from_object(vm, exc.clone())?; + let args = exc.args(); let val = args .elements .first() From 97c8d110923486effe3538527a36a7051a0a104e Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Tue, 17 Dec 2019 21:48:53 +0000 Subject: [PATCH 02/10] Make PyResult = Result --- src/main.rs | 3 +- src/shell.rs | 6 +-- vm/src/builtins.rs | 3 +- vm/src/exceptions.rs | 101 +++++++++++++++++++++---------------- vm/src/frame.rs | 56 ++++++++------------ vm/src/function.rs | 7 +-- vm/src/import.rs | 17 ++++--- vm/src/obj/objcoroutine.rs | 10 +++- vm/src/obj/objdict.rs | 5 +- vm/src/obj/objfloat.rs | 3 +- vm/src/obj/objgenerator.rs | 9 +++- vm/src/obj/objint.rs | 3 +- vm/src/obj/objiter.rs | 5 +- vm/src/obj/objtype.rs | 2 +- vm/src/pyobject.rs | 10 +++- vm/src/stdlib/json.rs | 4 +- vm/src/stdlib/os.rs | 23 +++++---- vm/src/stdlib/socket.rs | 3 +- vm/src/stdlib/symtable.rs | 2 +- vm/src/stdlib/zlib.rs | 3 +- vm/src/sysmodule.rs | 13 +++-- vm/src/vm.rs | 71 +++++++++++++++----------- 22 files changed, 198 insertions(+), 161 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8413c7471..dab0d2f9b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ extern crate log; use clap::{App, AppSettings, Arg, ArgMatches}; use rustpython_compiler::compile; use rustpython_vm::{ - exceptions::{print_exception, PyBaseExceptionRef}, + exceptions::print_exception, match_class, obj::{objint::PyInt, objtype}, pyobject::{ItemProtocol, PyResult}, @@ -51,7 +51,6 @@ fn main() { // See if any exception leaked out: if let Err(err) = res { if objtype::isinstance(&err, &vm.ctx.exceptions.system_exit) { - let err: PyBaseExceptionRef = err.downcast().unwrap(); let args = err.args(); match args.elements.len() { 0 => return, diff --git a/src/shell.rs b/src/shell.rs index 1b6a10d5a..bf26af3a9 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, + exceptions::{print_exception, PyBaseExceptionRef}, obj::objtype, - 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, } diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 3ff72416e..c052ee3f9 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 { diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 39dd3dc36..f51c183dd 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -4,8 +4,7 @@ use crate::obj::objtraceback::PyTracebackRef; use crate::obj::objtuple::{PyTuple, PyTupleRef}; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - IdProtocol, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, - TypeProtocol, + PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::types::create_type; use crate::vm::VirtualMachine; @@ -18,8 +17,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, } @@ -83,12 +82,12 @@ 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() } @@ -98,7 +97,7 @@ impl PyBaseException { } #[pyproperty(name = "__context__")] - fn get_context(&self, _vm: &VirtualMachine) -> Option { + fn get_context(&self, _vm: &VirtualMachine) -> Option { self.context.borrow().clone() } @@ -150,51 +149,67 @@ impl PyBaseException { 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); + let cause = exc.cause(); + if let Some(cause) = &cause { + write_exception(output, vm, cause)?; + writeln!( + output, + "\nThe above exception was the direct cause of the following exception:\n" + )?; + } + if cause.is_none() { + if let Some(context) = exc.context() { + write_exception(output, vm, &context)?; writeln!( output, - "\nThe above exception was the direct cause of the following exception:\n" + "\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); @@ -212,7 +227,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, @@ -225,19 +240,17 @@ 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<()> { - let exc: PyBaseExceptionRef = exc.clone().downcast().unwrap(); - if let Some(tb) = exc.traceback.borrow().clone() { writeln!(output, "Traceback (most recent call last):")?; - let mut tb = &Some(tb); + let mut tb = Some(&tb); while let Some(traceback) = tb { - print_traceback_entry(&mut output, traceback)?; - tb = &traceback.next; + write_traceback_entry(output, traceback)?; + tb = traceback.next.as_ref(); } } else { writeln!(output, "No traceback set on exception")?; @@ -505,14 +518,14 @@ 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()) } } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 307f93cc5..adaa68b1a 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::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 @@ -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 }) { @@ -464,8 +449,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 +730,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,8 +963,8 @@ 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 => Some(self.get_exception(vm, true)?), + _ => None, }; let exception = match argc { 0 => match vm.current_exception() { @@ -996,18 +981,15 @@ impl Frame { _ => 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)?; + exception.set_cause(cause); + exception.set_context(context); Err(exception) } @@ -1385,12 +1367,16 @@ impl Frame { } #[cfg_attr(feature = "flame-it", flame("Frame"))] - fn get_exception(&self, vm: &VirtualMachine, none_allowed: bool) -> PyResult { + fn get_exception( + &self, + vm: &VirtualMachine, + none_allowed: bool, + ) -> PyResult { let exception = self.pop_value(); - if none_allowed && vm.get_none().is(&exception) + if none_allowed && vm.is_none(&exception) || objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) { - Ok(exception) + Ok(exception.downcast().unwrap()) } 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)?; 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/obj/objcoroutine.rs b/vm/src/obj/objcoroutine.rs index 6d36f81a7..d179ba743 100644 --- a/vm/src/obj/objcoroutine.rs +++ b/vm/src/obj/objcoroutine.rs @@ -2,7 +2,9 @@ use super::objiter::new_stop_iteration; use super::objtype::{isinstance, issubclass, PyClassRef}; use crate::frame::{ExecutionResult, FrameRef}; use crate::function::OptionalArg; -use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{ + PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, +}; use crate::vm::VirtualMachine; use std::cell::Cell; @@ -62,7 +64,11 @@ impl PyCoroutine { vm: &VirtualMachine, ) -> PyResult { if self.closed.get() { - return Err(vm.invoke(exc_type.as_object(), vec![])?); + // TODO: normalize exception + return Err(TryFromObject::try_from_object( + vm, + 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. diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 619df1f94..47ee14564 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)? { diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index ab8c51533..ef2b1e388 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::{ @@ -675,7 +676,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..3bb345510 100644 --- a/vm/src/obj/objgenerator.rs +++ b/vm/src/obj/objgenerator.rs @@ -6,7 +6,9 @@ use super::objiter::new_stop_iteration; use super::objtype::{isinstance, issubclass, PyClassRef}; use crate::frame::{ExecutionResult, FrameRef}; use crate::function::OptionalArg; -use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{ + PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, +}; use crate::vm::VirtualMachine; use std::cell::Cell; @@ -75,7 +77,10 @@ impl PyGenerator { vm: &VirtualMachine, ) -> PyResult { if self.closed.get() { - return Err(vm.invoke(exc_type.as_object(), vec![])?); + return Err(TryFromObject::try_from_object( + vm, + 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. diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index fafcc6980..eecc7f2b3 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 79ade9f4f..94f69c064 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -68,13 +68,12 @@ 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() } -pub fn stop_iter_value(vm: &VirtualMachine, exc: &PyObjectRef) -> PyResult { - let exc = PyBaseExceptionRef::try_from_object(vm, exc.clone())?; +pub fn stop_iter_value(vm: &VirtualMachine, exc: &PyBaseExceptionRef) -> PyResult { let args = exc.args(); let val = args .elements diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index fdab9efa7..c1dba4e31 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -288,7 +288,7 @@ 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 { +pub fn isinstance(obj: &impl TypeProtocol, cls: &PyClassRef) -> bool { issubclass(&obj.class(), &cls) } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 633b69f59..d20b5a960 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. @@ -802,6 +802,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 { diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index 17a9c2d4f..dc9f2e217 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -47,9 +47,9 @@ pub fn json_loads(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { 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())) + 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..1fdf3b978 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(), @@ -175,12 +176,12 @@ pub fn convert_io_error(vm: &VirtualMachine, err: io::Error) -> PyObjectRef { 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(); @@ -201,7 +202,7 @@ pub fn convert_nix_error(vm: &VirtualMachine, err: nix::Error) -> PyObjectRef { }; 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/socket.rs b/vm/src/stdlib/socket.rs index e1fc6b693..fdc599966 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; @@ -589,7 +590,7 @@ 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()) 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..0206d7743 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() diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index e49fcd7af..f9553e6e2 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 { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index b4c738605..d82cd012e 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::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, @@ -329,38 +330,42 @@ impl VirtualMachine { } #[cfg_attr(feature = "flame-it", flame("VirtualMachine"))] - pub fn new_exception_obj(&self, exc_type: PyClassRef, args: Vec) -> PyResult { + pub fn new_exception_obj( + &self, + exc_type: PyClassRef, + args: Vec, + ) -> PyResult { // TODO: add repr of args into logging? vm_trace!("New exception created: {}", exc_type.name); - self.invoke(&exc_type.into_object(), args) + PyBaseExceptionRef::try_from_object(self, self.invoke(&exc_type.into_object(), args)?) } - pub fn new_empty_exception(&self, exc_type: PyClassRef) -> PyResult { + pub fn new_empty_exception(&self, exc_type: PyClassRef) -> PyResult { self.new_exception_obj(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 { + pub fn new_exception(&self, exc_type: PyClassRef, msg: String) -> PyBaseExceptionRef { let pystr_msg = self.new_str(msg); self.new_exception_obj(exc_type, vec![pystr_msg]).unwrap() } - 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) } - 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) } - 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) } - 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) } @@ -370,7 +375,7 @@ impl VirtualMachine { a: PyObjectRef, b: PyObjectRef, op: &str, - ) -> PyObjectRef { + ) -> PyBaseExceptionRef { self.new_type_error(format!( "Unsupported operand types for '{}': '{}' and '{}'", op, @@ -379,60 +384,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) } - 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) } - 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) } /// 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) } - 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() } - 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) } - 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) } - 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) } - 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) } - 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) } #[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() { @@ -443,20 +448,26 @@ impl VirtualMachine { let syntax_error = self.new_exception(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) } @@ -1358,15 +1369,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() } From 420f5c3490359d793102321ff202392962cf6bf5 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Wed, 18 Dec 2019 03:09:30 +0000 Subject: [PATCH 03/10] Add vm.normalize_exception() --- vm/src/frame.rs | 54 +++++++++++++++++--------------- vm/src/obj/objcoroutine.rs | 32 ++++++------------- vm/src/obj/objgenerator.rs | 29 +++++------------ vm/src/vm.rs | 64 +++++++++++++++++++++++++++++++++++--- 4 files changed, 106 insertions(+), 73 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index adaa68b1a..9b13c0415 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -208,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 = vm.normalize_exception(exc_type, exc_val, exc_tb)?; match self.unwind_blocks(vm, UnwindReason::Raising { exception }) { Ok(None) => self.run(vm), Ok(Some(result)) => Ok(result), @@ -963,7 +959,17 @@ impl Frame { fn execute_raise(&self, vm: &VirtualMachine, argc: usize) -> FrameResult { let cause = match argc { - 2 => Some(self.get_exception(vm, true)?), + 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(self.get_exception(vm, val)?)) + } + } + // if there's no cause arg, we keep the cause as is _ => None, }; let exception = match argc { @@ -973,10 +979,10 @@ impl Frame { return Err(vm.new_exception( vm.ctx.exceptions.runtime_error.clone(), "No active exception to reraise".to_string(), - )); + )) } }, - 1 | 2 => self.get_exception(vm, false)?, + 1 | 2 => self.get_exception(vm, self.pop_value())?, 3 => panic!("Not implemented!"), _ => panic!("Invalid parameter for RAISE_VARARGS, must be between 0 to 3"), }; @@ -988,7 +994,9 @@ impl Frame { "Exception raised: {:?} with cause: {:?} and context: {:?}", exception, cause, context ); - exception.set_cause(cause); + if let Some(cause) = cause { + exception.set_cause(cause); + } exception.set_context(context); Err(exception) } @@ -1370,17 +1378,13 @@ impl Frame { fn get_exception( &self, vm: &VirtualMachine, - none_allowed: bool, + exception: PyObjectRef, ) -> PyResult { - let exception = self.pop_value(); - if none_allowed && vm.is_none(&exception) - || objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) - { + if objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) { Ok(exception.downcast().unwrap()) } 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) + vm.new_empty_exception(exc_type) } else { let msg = format!( "Can only raise BaseException derived types, not {}", diff --git a/vm/src/obj/objcoroutine.rs b/vm/src/obj/objcoroutine.rs index d179ba743..05c3fc807 100644 --- a/vm/src/obj/objcoroutine.rs +++ b/vm/src/obj/objcoroutine.rs @@ -1,10 +1,8 @@ use super::objiter::new_stop_iteration; -use super::objtype::{isinstance, issubclass, PyClassRef}; +use super::objtype::{isinstance, PyClassRef}; use crate::frame::{ExecutionResult, FrameRef}; use crate::function::OptionalArg; -use crate::pyobject::{ - PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, -}; +use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use std::cell::Cell; @@ -58,30 +56,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() { - // TODO: normalize exception - return Err(TryFromObject::try_from_object( - vm, - 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(vm.normalize_exception(exc_type, exc_val, exc_tb)?); } 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) @@ -95,7 +81,7 @@ 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(), ); @@ -155,7 +141,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/objgenerator.rs b/vm/src/obj/objgenerator.rs index 3bb345510..85ff575ee 100644 --- a/vm/src/obj/objgenerator.rs +++ b/vm/src/obj/objgenerator.rs @@ -3,12 +3,10 @@ */ use super::objiter::new_stop_iteration; -use super::objtype::{isinstance, issubclass, PyClassRef}; +use super::objtype::{isinstance, PyClassRef}; use crate::frame::{ExecutionResult, FrameRef}; use crate::function::OptionalArg; -use crate::pyobject::{ - PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, -}; +use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use std::cell::Cell; @@ -71,29 +69,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(TryFromObject::try_from_object( - vm, - 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(vm.normalize_exception(exc_type, exc_val, exc_tb)?); } 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) @@ -107,7 +94,7 @@ 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(), ); diff --git a/vm/src/vm.rs b/vm/src/vm.rs index d82cd012e..12adcc9ac 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -19,7 +19,7 @@ use rustpython_compiler::{compile, error::CompileError}; use crate::builtins::{self, to_ascii}; use crate::bytecode; -use crate::exceptions::PyBaseExceptionRef; +use crate::exceptions::{PyBaseException, PyBaseExceptionRef}; use crate::frame::{ExecutionResult, Frame, FrameRef}; use crate::frozen; use crate::function::PyFuncArgs; @@ -36,12 +36,12 @@ use crate::obj::objiter; use crate::obj::objmodule::{self, PyModule}; use crate::obj::objsequence; use crate::obj::objstr::{PyString, PyStringRef}; -use crate::obj::objtuple::PyTupleRef; +use crate::obj::objtuple::{PyTuple, PyTupleRef}; use crate::obj::objtype::{self, PyClassRef}; use crate::pyhash; use crate::pyobject::{ - IdProtocol, ItemProtocol, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TryFromObject, - TryIntoRef, TypeProtocol, + Either, IdProtocol, ItemProtocol, PyContext, PyObject, PyObjectRef, PyResult, PyValue, + TryFromObject, TryIntoRef, TypeProtocol, }; use crate::scope::Scope; use crate::stdlib; @@ -329,6 +329,62 @@ impl VirtualMachine { PyObject::new(PyModule {}, self.ctx.types.module_type.clone(), Some(dict)) } + /// Similar to PyErr_NormalizeException in CPython + pub fn normalize_exception( + &self, + exc_type: PyObjectRef, + exc_val: PyObjectRef, + exc_tb: PyObjectRef, + ) -> PyResult { + let args = || { + if self.is_none(&exc_val) { + vec![] + } else { + match_class!(match exc_val.clone() { + tup @ PyTuple => tup.elements.clone(), + exc @ PyBaseException => exc.args().elements.clone(), + obj => vec![obj], + }) + } + }; + let exc_type = if let Ok(exc) = PyBaseExceptionRef::try_from_object(self, exc_type.clone()) + { + Either::A(exc) + } else if let Ok(cls) = PyClassRef::try_from_object(self, exc_type.clone()) { + Either::B(cls) + } else { + return Err(self.new_type_error(format!( + "expected an exception instance or type, got '{}'", + exc_type.class().name + ))); + }; + let exc = if let Ok(exc) = PyBaseExceptionRef::try_from_object(self, exc_val.clone()) { + match exc_type { + Either::A(_) => { + return Err(self.new_type_error( + "instance exception may not have a separate value".to_string(), + )) + } + Either::B(cls) => { + if objtype::isinstance(&exc, &cls) { + exc + } else { + self.new_exception_obj(cls, args())? + } + } + } + } else { + match exc_type { + Either::A(exc) => exc, + Either::B(cls) => self.new_exception_obj(cls, args())?, + } + }; + if let Some(tb) = TryFromObject::try_from_object(self, exc_tb)? { + exc.set_traceback(Some(tb)); + } + Ok(exc) + } + #[cfg_attr(feature = "flame-it", flame("VirtualMachine"))] pub fn new_exception_obj( &self, From 1773025368bc6657e009fbccd28756c3f1dfc60c Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 17 Dec 2019 22:05:23 -0600 Subject: [PATCH 04/10] Convert to new PyResult in rustpython_wasm --- Cargo.lock | 2 -- wasm/lib/Cargo.toml | 2 -- wasm/lib/src/browser_module.rs | 16 ++------- wasm/lib/src/convert.rs | 62 ++++++++++++++++------------------ wasm/lib/src/js_module.rs | 11 ++++-- wasm/lib/src/vm_class.rs | 2 +- 6 files changed, 41 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52474001f..67b1b5a1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", 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..789f6cb39 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(exc_type, err.message().into()) + } + None => vm.new_exception( + 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..0ffcf2f6a 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 { +fn new_js_error(vm: &VirtualMachine, err: JsValue) -> PyBaseExceptionRef { let exc = vm.new_exception(vm.class("_js", "JsError"), format!("{:?}", err)); - vm.set_attr(&exc, "js_value", PyJsValue::new(err).into_ref(vm)) - .unwrap(); + 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() { From d4261cafa36cb9b531e939e3334ac8dab575d5a8 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 17 Dec 2019 22:44:31 -0600 Subject: [PATCH 05/10] Add new test for cause --- tests/snippets/try_exceptions.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) 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: From ec3e40297075ec9d5c81ef2c5ab36550c1288e59 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 27 Dec 2019 22:13:23 -0600 Subject: [PATCH 06/10] Move and improve normalize_exception --- vm/src/exceptions.rs | 72 +++++++++++++++++++++++++++++--------- vm/src/frame.rs | 4 +-- vm/src/obj/objcoroutine.rs | 3 +- vm/src/obj/objgenerator.rs | 3 +- vm/src/obj/objtype.rs | 4 +-- vm/src/vm.rs | 64 +++------------------------------ 6 files changed, 68 insertions(+), 82 deletions(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index f51c183dd..15d4b4b8d 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -2,9 +2,10 @@ use crate::function::PyFuncArgs; use crate::obj::objstr::{PyString, PyStringRef}; use crate::obj::objtraceback::PyTracebackRef; use crate::obj::objtuple::{PyTuple, PyTupleRef}; -use crate::obj::objtype::PyClassRef; +use crate::obj::objtype::{self, PyClassRef}; use crate::pyobject::{ - PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, + Either, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, + TryFromObject, TypeProtocol, }; use crate::types::create_type; use crate::vm::VirtualMachine; @@ -92,7 +93,7 @@ impl PyBaseException { } #[pyproperty(name = "__cause__", setter)] - fn set_cause(&self, cause: Option, _vm: &VirtualMachine) { + fn setter_cause(&self, cause: Option, _vm: &VirtualMachine) { self.cause.replace(cause); } @@ -102,7 +103,7 @@ impl PyBaseException { } #[pyproperty(name = "__context__", setter)] - fn set_context(&self, context: Option, _vm: &VirtualMachine) { + fn setter_context(&self, context: Option, _vm: &VirtualMachine) { self.context.replace(context); } @@ -184,23 +185,20 @@ pub fn write_exception( vm: &VirtualMachine, exc: &PyBaseExceptionRef, ) -> io::Result<()> { - let cause = exc.cause(); - if let Some(cause) = &cause { - write_exception(output, vm, cause)?; + 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 cause.is_none() { - if let Some(context) = exc.context() { - write_exception(output, vm, &context)?; - writeln!( - output, - "\nDuring handling of the above exception, another exception occurred:\n" - )?; - } - } + write_exception_inner(output, vm, exc) } @@ -300,6 +298,48 @@ fn exception_args_as_string( } } +/// Similar to PyErr_NormalizeException in CPython +pub fn normalize( + exc_type: PyObjectRef, + exc_val: PyObjectRef, + exc_tb: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult { + let args = || { + if vm.is_none(&exc_val) { + vec![] + } else { + match_class!(match exc_val.clone() { + tup @ PyTuple => tup.elements.clone(), + exc @ PyBaseException => exc.args().elements.clone(), + obj => vec![obj], + }) + } + }; + let exc_type = Either::::try_from_object(vm, exc_type.clone()) + .map_err(|_| { + vm.new_type_error(format!( + "expected an exception instance or type, got '{}'", + exc_type.class().name + )) + })?; + let exc_inst = exc_val.clone().downcast::().ok(); + let exc = match (exc_type, exc_inst) { + (Either::A(_exc_a), Some(_exc_b)) => { + return Err( + vm.new_type_error("instance exception may not have a separate value".to_string()) + ) + } + (Either::A(exc), None) => exc, + (Either::B(cls), Some(exc)) if objtype::isinstance(&exc, &cls) => exc, + (Either::B(cls), _) => vm.new_exception_obj(cls, args())?, + }; + if let Some(tb) = Option::::try_from_object(vm, exc_tb)? { + exc.set_traceback(Some(tb)); + } + Ok(exc) +} + #[derive(Debug)] pub struct ExceptionZoo { pub arithmetic_error: PyClassRef, diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 9b13c0415..86c9ca581 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -5,7 +5,7 @@ use indexmap::IndexMap; use itertools::Itertools; use crate::bytecode; -use crate::exceptions::PyBaseExceptionRef; +use crate::exceptions::{self, PyBaseExceptionRef}; use crate::function::{single_or_tuple_any, PyFuncArgs}; use crate::obj::objbool; use crate::obj::objcode::PyCodeRef; @@ -223,7 +223,7 @@ impl Frame { }) .map(ExecutionResult::Yield) } else { - let exception = vm.normalize_exception(exc_type, exc_val, exc_tb)?; + 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), diff --git a/vm/src/obj/objcoroutine.rs b/vm/src/obj/objcoroutine.rs index 05c3fc807..0fd686492 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, PyClassRef}; +use crate::exceptions; use crate::frame::{ExecutionResult, FrameRef}; use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; @@ -64,7 +65,7 @@ impl PyCoroutine { 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.normalize_exception(exc_type, exc_val, exc_tb)?); + 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, exc_tb); diff --git a/vm/src/obj/objgenerator.rs b/vm/src/obj/objgenerator.rs index 85ff575ee..c43fb57fd 100644 --- a/vm/src/obj/objgenerator.rs +++ b/vm/src/obj/objgenerator.rs @@ -4,6 +4,7 @@ use super::objiter::new_stop_iteration; 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}; @@ -77,7 +78,7 @@ impl PyGenerator { 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.normalize_exception(exc_type, exc_val, exc_tb)?); + 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, exc_tb); diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index c1dba4e31..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: &impl TypeProtocol, cls: &PyClassRef) -> bool { +#[inline] +pub fn isinstance(obj: &T, cls: &PyClassRef) -> bool { issubclass(&obj.class(), &cls) } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 12adcc9ac..d82cd012e 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -19,7 +19,7 @@ use rustpython_compiler::{compile, error::CompileError}; use crate::builtins::{self, to_ascii}; use crate::bytecode; -use crate::exceptions::{PyBaseException, PyBaseExceptionRef}; +use crate::exceptions::PyBaseExceptionRef; use crate::frame::{ExecutionResult, Frame, FrameRef}; use crate::frozen; use crate::function::PyFuncArgs; @@ -36,12 +36,12 @@ use crate::obj::objiter; use crate::obj::objmodule::{self, PyModule}; use crate::obj::objsequence; use crate::obj::objstr::{PyString, PyStringRef}; -use crate::obj::objtuple::{PyTuple, PyTupleRef}; +use crate::obj::objtuple::PyTupleRef; use crate::obj::objtype::{self, PyClassRef}; use crate::pyhash; use crate::pyobject::{ - Either, IdProtocol, ItemProtocol, PyContext, PyObject, PyObjectRef, PyResult, PyValue, - TryFromObject, TryIntoRef, TypeProtocol, + IdProtocol, ItemProtocol, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TryFromObject, + TryIntoRef, TypeProtocol, }; use crate::scope::Scope; use crate::stdlib; @@ -329,62 +329,6 @@ impl VirtualMachine { PyObject::new(PyModule {}, self.ctx.types.module_type.clone(), Some(dict)) } - /// Similar to PyErr_NormalizeException in CPython - pub fn normalize_exception( - &self, - exc_type: PyObjectRef, - exc_val: PyObjectRef, - exc_tb: PyObjectRef, - ) -> PyResult { - let args = || { - if self.is_none(&exc_val) { - vec![] - } else { - match_class!(match exc_val.clone() { - tup @ PyTuple => tup.elements.clone(), - exc @ PyBaseException => exc.args().elements.clone(), - obj => vec![obj], - }) - } - }; - let exc_type = if let Ok(exc) = PyBaseExceptionRef::try_from_object(self, exc_type.clone()) - { - Either::A(exc) - } else if let Ok(cls) = PyClassRef::try_from_object(self, exc_type.clone()) { - Either::B(cls) - } else { - return Err(self.new_type_error(format!( - "expected an exception instance or type, got '{}'", - exc_type.class().name - ))); - }; - let exc = if let Ok(exc) = PyBaseExceptionRef::try_from_object(self, exc_val.clone()) { - match exc_type { - Either::A(_) => { - return Err(self.new_type_error( - "instance exception may not have a separate value".to_string(), - )) - } - Either::B(cls) => { - if objtype::isinstance(&exc, &cls) { - exc - } else { - self.new_exception_obj(cls, args())? - } - } - } - } else { - match exc_type { - Either::A(exc) => exc, - Either::B(cls) => self.new_exception_obj(cls, args())?, - } - }; - if let Some(tb) = TryFromObject::try_from_object(self, exc_tb)? { - exc.set_traceback(Some(tb)); - } - Ok(exc) - } - #[cfg_attr(feature = "flame-it", flame("VirtualMachine"))] pub fn new_exception_obj( &self, From 41c8dd84b357871c8d1829a3e8b19efe14b3621b Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 28 Dec 2019 00:02:45 -0600 Subject: [PATCH 07/10] Remove frame::get_exception, use exceptions::normalize instead --- vm/src/exceptions.rs | 42 +++++++++++++++++++++++++++--------------- vm/src/frame.rs | 32 +++++++------------------------- vm/src/pyobject.rs | 3 --- 3 files changed, 34 insertions(+), 43 deletions(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 15d4b4b8d..473460aed 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1,4 +1,5 @@ use crate::function::PyFuncArgs; +use crate::obj::objnone::PyNone; use crate::obj::objstr::{PyString, PyStringRef}; use crate::obj::objtraceback::PyTracebackRef; use crate::obj::objtuple::{PyTuple, PyTupleRef}; @@ -305,34 +306,45 @@ pub fn normalize( exc_tb: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - let args = || { - if vm.is_none(&exc_val) { - vec![] - } else { - match_class!(match exc_val.clone() { - tup @ PyTuple => tup.elements.clone(), - exc @ PyBaseException => exc.args().elements.clone(), - obj => vec![obj], - }) - } - }; let exc_type = Either::::try_from_object(vm, exc_type.clone()) - .map_err(|_| { + .ok() + .and_then(|e| { + // make sure the class is actually a subclass of BaseException + if let Either::B(ref cls) = e { + if !objtype::issubclass(cls, &vm.ctx.exceptions.base_exception_type) { + return None; + } + } + Some(e) + }) + .ok_or_else(|| { vm.new_type_error(format!( - "expected an exception instance or type, got '{}'", + "exceptions must be classes or instances deriving from BaseException, not {}", exc_type.class().name )) })?; let exc_inst = exc_val.clone().downcast::().ok(); let exc = match (exc_type, exc_inst) { (Either::A(_exc_a), Some(_exc_b)) => { + // both are instances; which would we choose? return 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" (Either::A(exc), None) => exc, + // if the value is an instance of the type, use the instance value (Either::B(cls), Some(exc)) if objtype::isinstance(&exc, &cls) => exc, - (Either::B(cls), _) => vm.new_exception_obj(cls, args())?, + // otherwise; construct an exception of the type using the value as args + (Either::B(cls), _) => { + let args = match_class!(match exc_val { + PyNone => vec![], + tup @ PyTuple => tup.elements.clone(), + exc @ PyBaseException => exc.args().elements.clone(), + obj => vec![obj], + }); + vm.new_exception_obj(cls, args)? + } }; if let Some(tb) = Option::::try_from_object(vm, exc_tb)? { exc.set_traceback(Some(tb)); diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 86c9ca581..a3cff68c7 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -966,7 +966,12 @@ impl Frame { Some(None) } else { // if the cause arg is an exception, we overwrite it - Some(Some(self.get_exception(vm, val)?)) + Some(Some(exceptions::normalize( + val, + vm.get_none(), + vm.get_none(), + vm, + )?)) } } // if there's no cause arg, we keep the cause as is @@ -982,7 +987,7 @@ impl Frame { )) } }, - 1 | 2 => self.get_exception(vm, self.pop_value())?, + 1 | 2 => exceptions::normalize(self.pop_value(), vm.get_none(), vm.get_none())?, 3 => panic!("Not implemented!"), _ => panic!("Invalid parameter for RAISE_VARARGS, must be between 0 to 3"), }; @@ -1373,29 +1378,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, - exception: PyObjectRef, - ) -> PyResult { - if objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) { - Ok(exception.downcast().unwrap()) - } else if let Ok(exc_type) = PyClassRef::try_from_object(vm, exception) { - if objtype::issubclass(&exc_type, &vm.ctx.exceptions.base_exception_type) { - vm.new_empty_exception(exc_type) - } 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/pyobject.rs b/vm/src/pyobject.rs index d20b5a960..0e2c36b38 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -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({ From 42a3523f9ff72aedcba7cc0e063f3249720df680 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 28 Dec 2019 00:34:36 -0600 Subject: [PATCH 08/10] Add ExceptionCtor type, use for exception-creation-related code --- vm/src/exceptions.rs | 112 ++++++++++++++++++++++++++----------------- vm/src/frame.rs | 13 ++--- 2 files changed, 74 insertions(+), 51 deletions(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 473460aed..5ed4ba5ba 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -3,10 +3,10 @@ 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::{self, PyClassRef}; +use crate::obj::objtype::{self, PyClass, PyClassRef}; use crate::pyobject::{ - Either, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, - TryFromObject, TypeProtocol, + PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, + TypeProtocol, }; use crate::types::create_type; use crate::vm::VirtualMachine; @@ -299,6 +299,70 @@ fn exception_args_as_string( } } +#[derive(Clone)] +pub enum ExceptionCtor { + Class(PyClassRef), + Instance(PyBaseExceptionRef), +} + +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 + )) + }) + } +} + +impl ExceptionCtor { + pub fn instantiate(self, vm: &VirtualMachine) -> PyResult { + match self { + Self::Class(cls) => vm.new_empty_exception(cls), + 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], + }); + vm.new_exception_obj(cls, args) + } + } + } +} + /// Similar to PyErr_NormalizeException in CPython pub fn normalize( exc_type: PyObjectRef, @@ -306,46 +370,8 @@ pub fn normalize( exc_tb: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - let exc_type = Either::::try_from_object(vm, exc_type.clone()) - .ok() - .and_then(|e| { - // make sure the class is actually a subclass of BaseException - if let Either::B(ref cls) = e { - if !objtype::issubclass(cls, &vm.ctx.exceptions.base_exception_type) { - return None; - } - } - Some(e) - }) - .ok_or_else(|| { - vm.new_type_error(format!( - "exceptions must be classes or instances deriving from BaseException, not {}", - exc_type.class().name - )) - })?; - let exc_inst = exc_val.clone().downcast::().ok(); - let exc = match (exc_type, exc_inst) { - (Either::A(_exc_a), Some(_exc_b)) => { - // both are instances; which would we choose? - return 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" - (Either::A(exc), None) => exc, - // if the value is an instance of the type, use the instance value - (Either::B(cls), Some(exc)) if objtype::isinstance(&exc, &cls) => exc, - // otherwise; construct an exception of the type using the value as args - (Either::B(cls), _) => { - let args = match_class!(match exc_val { - PyNone => vec![], - tup @ PyTuple => tup.elements.clone(), - exc @ PyBaseException => exc.args().elements.clone(), - obj => vec![obj], - }); - vm.new_exception_obj(cls, args)? - } - }; + 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)); } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index a3cff68c7..9e122f9d3 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -5,7 +5,7 @@ use indexmap::IndexMap; use itertools::Itertools; use crate::bytecode; -use crate::exceptions::{self, PyBaseExceptionRef}; +use crate::exceptions::{self, ExceptionCtor, PyBaseExceptionRef}; use crate::function::{single_or_tuple_any, PyFuncArgs}; use crate::obj::objbool; use crate::obj::objcode::PyCodeRef; @@ -966,12 +966,9 @@ impl Frame { Some(None) } else { // if the cause arg is an exception, we overwrite it - Some(Some(exceptions::normalize( - val, - vm.get_none(), - vm.get_none(), - vm, - )?)) + Some(Some( + ExceptionCtor::try_from_object(vm, val)?.instantiate(vm)?, + )) } } // if there's no cause arg, we keep the cause as is @@ -987,7 +984,7 @@ impl Frame { )) } }, - 1 | 2 => exceptions::normalize(self.pop_value(), vm.get_none(), vm.get_none())?, + 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"), }; From 777542f7ecbf4d99dec764a68c581c486cca50ea Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 28 Dec 2019 01:31:24 -0600 Subject: [PATCH 09/10] Directly instantiate exceptions in the vm.new_*_error functions --- src/shell.rs | 5 ++- vm/src/builtins.rs | 2 +- vm/src/exceptions.rs | 29 +++++++++------ vm/src/frame.rs | 4 +-- vm/src/lib.rs | 1 - vm/src/obj/objcoroutine.rs | 2 +- vm/src/obj/objdict.rs | 2 +- vm/src/obj/objgenerator.rs | 2 +- vm/src/obj/objiter.rs | 2 +- vm/src/obj/objweakproxy.rs | 4 +-- vm/src/pyobject.rs | 8 +++-- vm/src/stdlib/functools.rs | 2 +- vm/src/stdlib/io.rs | 8 ++--- vm/src/stdlib/json.rs | 2 +- vm/src/stdlib/os.rs | 10 +++--- vm/src/stdlib/random.rs | 2 +- vm/src/stdlib/signal.rs | 2 +- vm/src/stdlib/socket.rs | 8 ++--- vm/src/stdlib/subprocess.rs | 2 +- vm/src/stdlib/zlib.rs | 2 +- vm/src/sysmodule.rs | 2 +- vm/src/vm.rs | 72 +++++++++++++++++++++++-------------- wasm/lib/src/convert.rs | 4 +-- wasm/lib/src/js_module.rs | 2 +- 24 files changed, 104 insertions(+), 75 deletions(-) diff --git a/src/shell.rs b/src/shell.rs index bf26af3a9..af4f3c6cb 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -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/vm/src/builtins.rs b/vm/src/builtins.rs index c052ee3f9..73f237a71 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -629,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 5ed4ba5ba..5552eb154 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -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(args.args).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__")] @@ -325,10 +324,20 @@ impl TryFromObject for ExceptionCtor { } } +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) => vm.new_empty_exception(cls), + Self::Class(cls) => invoke(cls, vec![], vm), Self::Instance(exc) => Ok(exc), } } @@ -357,7 +366,7 @@ impl ExceptionCtor { exc @ PyBaseException => exc.args().elements.clone(), obj => vec![obj], }); - vm.new_exception_obj(cls, args) + invoke(cls, args, vm) } } } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 9e122f9d3..ba13d5c54 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -127,7 +127,7 @@ impl ExecutionResult { } else { vec![value] }; - Err(vm.new_exception_obj(stop_iteration, args).unwrap()) + Err(vm.new_exception(stop_iteration, args)) } } } @@ -978,7 +978,7 @@ impl Frame { 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(), )) diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 71f206a09..1f8fe9be4 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -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 0fd686492..4dad7d71d 100644 --- a/vm/src/obj/objcoroutine.rs +++ b/vm/src/obj/objcoroutine.rs @@ -89,7 +89,7 @@ impl PyCoroutine { 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/objdict.rs b/vm/src/obj/objdict.rs index 47ee14564..c25c68b71 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -527,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/objgenerator.rs b/vm/src/obj/objgenerator.rs index c43fb57fd..56927bd19 100644 --- a/vm/src/obj/objgenerator.rs +++ b/vm/src/obj/objgenerator.rs @@ -102,7 +102,7 @@ impl PyGenerator { 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/objiter.rs b/vm/src/obj/objiter.rs index 94f69c064..b770e1e33 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -70,7 +70,7 @@ pub fn get_all(vm: &VirtualMachine, iter_obj: &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: &PyBaseExceptionRef) -> PyResult { 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 0e2c36b38..e6b6b7312 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -619,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), )) @@ -1085,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)) } fn into_ref_with_type(self, vm: &VirtualMachine, cls: PyClassRef) -> PyResult> { @@ -1103,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) -> PyRef { + PyRef::new_ref_unchecked(PyObject::new(self, cls, None)) + } } 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 dc9f2e217..88216acc3 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -46,7 +46,7 @@ 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)); + 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.as_object(), "colno", vm.ctx.new_int(err.column())) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 1fdf3b978..2aef879f4 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -171,7 +171,7 @@ pub fn convert_io_error(vm: &VirtualMachine, err: io::Error) -> PyBaseExceptionR _ => 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(), @@ -185,19 +185,19 @@ pub fn convert_nix_error(vm: &VirtualMachine, err: nix::Error) -> PyBaseExceptio 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()) } }; 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 fdc599966..c6cbfc8a8 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -485,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 @@ -537,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(), )) @@ -547,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())) } } } @@ -593,7 +593,7 @@ fn invalid_sock() -> Socket { 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/zlib.rs b/vm/src/stdlib/zlib.rs index 0206d7743..62ef5df4c 100644 --- a/vm/src/stdlib/zlib.rs +++ b/vm/src/stdlib/zlib.rs @@ -121,5 +121,5 @@ fn zlib_error(message: &str, vm: &VirtualMachine) -> PyBaseExceptionRef { 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 f9553e6e2..2b943f801 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -198,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 d82cd012e..7a9052039 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -19,7 +19,7 @@ use rustpython_compiler::{compile, error::CompileError}; use crate::builtins::{self, to_ascii}; use crate::bytecode; -use crate::exceptions::PyBaseExceptionRef; +use crate::exceptions::{PyBaseException, PyBaseExceptionRef}; use crate::frame::{ExecutionResult, Frame, FrameRef}; use crate::frozen; use crate::function::PyFuncArgs; @@ -329,45 +329,63 @@ 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( + /// 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, - ) -> PyResult { + ) -> PyBaseExceptionRef { // TODO: add repr of args into logging? vm_trace!("New exception created: {}", exc_type.name); - PyBaseExceptionRef::try_from_object(self, self.invoke(&exc_type.into_object(), args)?) + PyBaseException::new(args, self).into_ref_with_type_unchecked(exc_type) } - 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) -> PyBaseExceptionRef { - 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) -> 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) -> 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) -> 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) -> 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( @@ -386,54 +404,54 @@ impl VirtualMachine { 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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")] @@ -445,7 +463,7 @@ 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.as_object(), "lineno", lineno) @@ -469,7 +487,7 @@ impl VirtualMachine { 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 { diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 789f6cb39..ffb324018 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -57,9 +57,9 @@ pub fn js_err_to_py_err(vm: &VirtualMachine, js_err: &JsValue) -> PyBaseExceptio _ => &vm.ctx.exceptions.exception_type, } .clone(); - vm.new_exception(exc_type, err.message().into()) + vm.new_exception_msg(exc_type, err.message().into()) } - None => vm.new_exception( + None => vm.new_exception_msg( vm.ctx.exceptions.exception_type.clone(), format!("{:?}", js_err), ), diff --git a/wasm/lib/src/js_module.rs b/wasm/lib/src/js_module.rs index 0ffcf2f6a..f640b1d14 100644 --- a/wasm/lib/src/js_module.rs +++ b/wasm/lib/src/js_module.rs @@ -235,7 +235,7 @@ struct NewObjectOptions { } fn new_js_error(vm: &VirtualMachine, err: JsValue) -> PyBaseExceptionRef { - let exc = vm.new_exception(vm.class("_js", "JsError"), format!("{:?}", err)); + let exc = vm.new_exception_msg(vm.class("_js", "JsError"), format!("{:?}", err)); vm.set_attr( exc.as_object(), "js_value", From 181dff867fc2cd4b6556c88cbac0d954dc6a82cf Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 28 Dec 2019 20:19:24 -0600 Subject: [PATCH 10/10] Fix setting attributes on errors --- vm/src/pyobject.rs | 6 +++--- vm/src/vm.rs | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index e6b6b7312..0fe1cdd17 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1085,7 +1085,7 @@ pub trait PyValue: fmt::Debug + Sized + 'static { fn class(vm: &VirtualMachine) -> PyClassRef; fn into_ref(self, vm: &VirtualMachine) -> PyRef { - self.into_ref_with_type_unchecked(Self::class(vm)) + self.into_ref_with_type_unchecked(Self::class(vm), None) } fn into_ref_with_type(self, vm: &VirtualMachine, cls: PyClassRef) -> PyResult> { @@ -1104,8 +1104,8 @@ pub trait PyValue: fmt::Debug + Sized + 'static { } } - fn into_ref_with_type_unchecked(self, cls: PyClassRef) -> PyRef { - PyRef::new_ref_unchecked(PyObject::new(self, cls, None)) + fn into_ref_with_type_unchecked(self, cls: PyClassRef, dict: Option) -> PyRef { + PyRef::new_ref_unchecked(PyObject::new(self, cls, dict)) } } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 7a9052039..488f168fb 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -343,7 +343,8 @@ impl VirtualMachine { ) -> PyBaseExceptionRef { // TODO: add repr of args into logging? vm_trace!("New exception created: {}", exc_type.name); - PyBaseException::new(args, self).into_ref_with_type_unchecked(exc_type) + PyBaseException::new(args, self) + .into_ref_with_type_unchecked(exc_type, Some(self.ctx.new_dict())) } /// Instantiate an exception with no arguments.