Merge pull request #78 from RustPython/exceptions

Implement exception type checking at catch
This commit is contained in:
cthulahoops
2018-08-25 14:45:19 +01:00
committed by GitHub
9 changed files with 291 additions and 33 deletions

View File

@@ -252,7 +252,7 @@ ForStatement: ast::LocatedStatement = {
};
TryStatement: ast::LocatedStatement = {
<loc:@L> "try" ":" <body:Suite> <handlers:ExceptClause+> <else_suite:("else" ":" Suite)?> <finally:("finally" ":" Suite)?> => {
<loc:@L> "try" ":" <body:Suite> <handlers:ExceptClause*> <else_suite:("else" ":" Suite)?> <finally:("finally" ":" Suite)?> => {
let or_else = match else_suite {
Some(s) => Some(s.2),
None => None,

View File

@@ -7,3 +7,68 @@ except BaseException as ex:
print(type(ex))
# print(ex.__traceback__)
# print(type(ex.__traceback__))
l = []
try:
l.append(1)
assert 0
l.append(2)
except:
l.append(3)
print('boom')
finally:
l.append(4)
print('kablam')
assert l == [1, 3, 4]
l = []
try:
l.append(1)
assert 0
l.append(2)
except AssertionError as ex:
l.append(3)
print('boom', type(ex))
finally:
l.append(4)
print('kablam')
assert l == [1, 3, 4]
l = []
try:
l.append(1)
assert 1
l.append(2)
except AssertionError as ex:
l.append(3)
print('boom', type(ex))
finally:
l.append(4)
print('kablam')
assert l == [1, 2, 4]
l = []
try:
try:
l.append(1)
assert 0
l.append(2)
finally:
l.append(3)
print('kablam')
except AssertionError as ex:
l.append(4)
print('boom', type(ex))
assert l == [1, 3, 4]
l = []
try:
l.append(1)
fubar
l.append(2)
except NameError as ex:
l.append(3)
print('boom', type(ex))
assert l == [1, 3]

View File

@@ -354,10 +354,39 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef {
dict.insert(String::from("tuple"), ctx.tuple_type.clone());
dict.insert(String::from("type"), ctx.type_type.clone());
dict.insert(String::from("object"), ctx.object.clone());
// Exceptions:
dict.insert(
String::from("BaseException"),
ctx.base_exception_type.clone(),
ctx.exceptions.base_exception_type.clone(),
);
dict.insert(
String::from("Exception"),
ctx.exceptions.exception_type.clone(),
);
dict.insert(
String::from("AssertionError"),
ctx.exceptions.assertion_error.clone(),
);
dict.insert(
String::from("AttributeError"),
ctx.exceptions.attribute_error.clone(),
);
dict.insert(String::from("NameError"), ctx.exceptions.name_error.clone());
dict.insert(
String::from("RuntimeError"),
ctx.exceptions.runtime_error.clone(),
);
dict.insert(
String::from("NotImplementedError"),
ctx.exceptions.not_implemented_error.clone(),
);
dict.insert(String::from("TypeError"), ctx.exceptions.type_error.clone());
dict.insert(
String::from("ValueError"),
ctx.exceptions.value_error.clone(),
);
let d2 = PyObject::new(PyObjectKind::Dict { elements: dict }, ctx.type_type.clone());
let scope = PyObject::new(
PyObjectKind::Scope {

View File

@@ -263,25 +263,44 @@ impl Compiler {
// except handlers:
self.set_label(handler_label);
// Exception is on top of stack now
handler_label = self.new_label();
for handler in handlers {
// Check if this handler can handle the exception:
// If we gave a typ,
// check if this handler can handle the exception:
if let Some(exc_type) = &handler.typ {
// Duplicate exception for test:
self.emit(Instruction::Duplicate);
// TODO: self.emit(isinstance()) start of hack
self.emit(Instruction::LoadConst {
value: bytecode::Constant::Boolean { value: false },
});
// End of hack
self.emit(Instruction::JumpIf {
target: handler_label,
});
// We have a match
if let Some(alias) = &handler.name {
self.emit(Instruction::StoreName {
name: alias.clone(),
// Check exception type:
self.emit(Instruction::LoadName {
name: String::from("isinstance"),
});
self.emit(Instruction::Rotate { amount: 2 });
self.compile_expression(exc_type);
self.emit(Instruction::CallFunction { count: 2 });
// We cannot handle this exception type:
self.emit(Instruction::JumpIfFalse {
target: handler_label,
});
// We have a match, store in name (except x as y)
if let Some(alias) = &handler.name {
self.emit(Instruction::StoreName {
name: alias.clone(),
});
} else {
// Drop exception from top of stack:
self.emit(Instruction::Pop);
}
} else {
// Catch all!
// Drop exception from top of stack:
self.emit(Instruction::Pop);
}
// Handler code:
self.compile_statements(&handler.body);
self.emit(Instruction::Jump {
target: finally_label,
@@ -294,6 +313,16 @@ impl Compiler {
self.emit(Instruction::Jump {
target: handler_label,
});
self.set_label(handler_label);
// If code flows here, we have an unhandled exception,
// emit finally code and raise again!
// Duplicate finally code here:
// TODO: this bytecode is now duplicate, could this be
// improved?
if let Some(statements) = finalbody {
self.compile_statements(statements);
}
self.emit(Instruction::Raise { argc: 1 });
// We successfully ran the try block:
// else:
@@ -401,6 +430,7 @@ impl Compiler {
self.emit(Instruction::CallFunction { count: 0 });
}
}
self.emit(Instruction::Raise { argc: 1 });
self.set_label(end_label);
}
ast::Statement::Break => {

View File

@@ -1,12 +1,99 @@
use super::pyobject::{AttributeProtocol, PyContext, PyFuncArgs, PyResult};
use super::pyobject::{
create_type, AttributeProtocol, PyContext, PyFuncArgs, PyObjectRef, PyResult,
};
use super::vm::VirtualMachine;
fn exception_init(vm: &mut VirtualMachine, _args: PyFuncArgs) -> PyResult {
Ok(vm.get_none())
}
#[derive(Debug)]
pub struct ExceptionZoo {
pub base_exception_type: PyObjectRef,
pub exception_type: PyObjectRef,
pub assertion_error: PyObjectRef,
pub attribute_error: PyObjectRef,
pub name_error: PyObjectRef,
pub runtime_error: PyObjectRef,
pub not_implemented_error: PyObjectRef,
pub type_error: PyObjectRef,
pub value_error: PyObjectRef,
}
impl ExceptionZoo {
pub fn new(
type_type: &PyObjectRef,
object_type: &PyObjectRef,
dict_type: &PyObjectRef,
) -> Self {
let base_exception_type =
create_type("BaseException", &type_type, &object_type, &dict_type);
let exception_type = create_type(
&String::from("Exception"),
&type_type,
&base_exception_type,
&dict_type,
);
let assertion_error = create_type(
&String::from("AssertionError"),
&type_type,
&exception_type,
&dict_type,
);
let attribute_error = create_type(
&String::from("AttributeError"),
&type_type,
&exception_type.clone(),
&dict_type,
);
let name_error = create_type(
&String::from("NameError"),
&type_type,
&exception_type.clone(),
&dict_type,
);
let runtime_error = create_type(
&String::from("RuntimeError"),
&type_type,
&exception_type,
&dict_type,
);
let not_implemented_error = create_type(
&String::from("NotImplementedError"),
&type_type,
&runtime_error,
&dict_type,
);
let type_error = create_type(
&String::from("TypeError"),
&type_type,
&exception_type,
&dict_type,
);
let value_error = create_type(
&String::from("ValueError"),
&type_type,
&exception_type,
&dict_type,
);
ExceptionZoo {
base_exception_type: base_exception_type,
exception_type: exception_type,
assertion_error: assertion_error,
attribute_error: attribute_error,
name_error: name_error,
runtime_error: runtime_error,
not_implemented_error: not_implemented_error,
type_error: type_error,
value_error: value_error,
}
}
}
pub fn init(context: &PyContext) {
let ref base_exception_type = context.base_exception_type;
let ref base_exception_type = context.exceptions.base_exception_type;
base_exception_type.set_attr("__init__", context.new_rustfunc(exception_init));
// TODO: create a whole exception hierarchy somehow?

View File

@@ -23,6 +23,7 @@ mod objstr;
mod objtype;
pub mod pyobject;
mod sysmodule;
mod traceback;
mod vm;
// pub use self::pyobject::Executor;

View File

@@ -60,7 +60,7 @@ pub struct PyContext {
pub bound_method_type: PyObjectRef,
pub member_descriptor_type: PyObjectRef,
pub object: PyObjectRef,
pub base_exception_type: PyObjectRef,
pub exceptions: exceptions::ExceptionZoo,
}
/*
@@ -80,7 +80,7 @@ fn _nothing() -> PyObjectRef {
}.into_ref()
}
fn create_type(
pub fn create_type(
name: &str,
type_type: &PyObjectRef,
object: &PyObjectRef,
@@ -97,7 +97,7 @@ fn create_type(
// Basic objects:
impl PyContext {
pub fn new() -> PyContext {
pub fn new() -> Self {
let type_type = _nothing();
let object_type = _nothing();
let dict_type = _nothing();
@@ -116,9 +116,7 @@ impl PyContext {
let float_type = create_type("float", &type_type, &object_type, &dict_type);
let tuple_type = create_type("tuple", &type_type, &object_type, &dict_type);
let bool_type = create_type("bool", &type_type, &object_type, &dict_type);
let base_exception_type =
create_type("BaseException", &type_type, &object_type, &dict_type);
let exceptions = exceptions::ExceptionZoo::new(&type_type, &object_type, &dict_type);
let none = PyObject::new(
PyObjectKind::None,
@@ -139,7 +137,7 @@ impl PyContext {
bound_method_type: bound_method_type,
member_descriptor_type: member_descriptor_type,
type_type: type_type,
base_exception_type: base_exception_type,
exceptions: exceptions,
};
objtype::init(&context);
objlist::init(&context);
@@ -194,6 +192,10 @@ impl PyContext {
)
}
pub fn new_class(&self, name: &String, base: PyObjectRef) -> PyObjectRef {
objtype::new(self.type_type.clone(), name, vec![base], self.new_dict()).unwrap()
}
pub fn new_scope(&self, parent: Option<PyObjectRef>) -> PyObjectRef {
let locals = self.new_dict();
let scope = Scope {

2
vm/src/traceback.rs Normal file
View File

@@ -0,0 +1,2 @@
// python tracebacks
//

View File

@@ -204,11 +204,9 @@ impl VirtualMachine {
} else if scope.has_parent() {
scope = scope.get_parent();
} else {
let name_error = PyObject::new(
PyObjectKind::NameError {
name: name.to_string(),
},
self.get_type(),
let name_error = self.context().new_instance(
self.context().new_dict(),
self.context().exceptions.name_error.clone(),
);
break Some(Err(name_error));
}
@@ -583,6 +581,36 @@ impl VirtualMachine {
self.pop_value();
None
}
bytecode::Instruction::Duplicate => {
// Duplicate top of stack
let value = self.pop_value();
self.push_value(value.clone());
self.push_value(value);
None
}
bytecode::Instruction::Rotate { amount } => {
// Shuffles top of stack amount down
if amount < &2 {
panic!("Can only rotate two or more values");
}
let mut values = Vec::new();
// Pop all values from stack:
for _ in 0..*amount {
values.push(self.pop_value());
}
// Push top of stack back first:
self.push_value(values.remove(0));
// Push other value back in order:
values.reverse();
for value in values {
self.push_value(value);
}
None
}
bytecode::Instruction::BuildList { size } => {
let elements = self.pop_multiple(*size);
let list_obj = self.context().new_list(elements);
@@ -772,8 +800,23 @@ impl VirtualMachine {
0 | 2 | 3 => panic!("Not implemented!"),
_ => panic!("Invalid paramter for RAISE_VARARGS, must be between 0 to 3"),
};
info!("Exception raised: {:?}", exception);
Some(Err(exception))
if self.isinstance(
exception.clone(),
self.context().exceptions.base_exception_type.clone(),
) {
info!("Exception raised: {:?}", exception);
Some(Err(exception))
} else {
Some(Err(exception))
// TODO: enable this when isinstance works properly:
/*
info!(
"Can only raise BaseException derived types: {:?}",
exception
);
let type_error = self.context().exceptions.type_error.clone();
Some(Err(type_error))
*/ }
}
bytecode::Instruction::Break => {
@@ -832,7 +875,6 @@ impl VirtualMachine {
}
None
}
_ => panic!("NOT IMPL {:?}", instruction),
}
}