Author: Philip Jenvey <[email protected]>
Branch: py3k
Changeset: r61638:f2d371471588
Date: 2013-02-22 20:26 -0800
http://bitbucket.org/pypy/pypy/changeset/f2d371471588/
Log: support __context__ and __traceback__, be stricter about __cause__
diff --git a/pypy/interpreter/error.py b/pypy/interpreter/error.py
--- a/pypy/interpreter/error.py
+++ b/pypy/interpreter/error.py
@@ -24,16 +24,43 @@
def __init__(self, w_type, w_value, tb=None, w_cause=None):
assert w_type is not None
- self.setup(w_type)
- self._w_value = w_value
+ self.setup(w_type, w_value)
self._application_traceback = tb
self.w_cause = w_cause
- def setup(self, w_type):
+ def setup(self, w_type, w_value=None):
+ from pypy.objspace.std.typeobject import W_TypeObject
self.w_type = w_type
+ self._w_value = w_value
+ if isinstance(w_type, W_TypeObject):
+ self.setup_context(w_type.space)
if not we_are_translated():
self.debug_excs = []
+ def setup_context(self, space):
+ # Implicit exception chaining
+ last_operror = space.getexecutioncontext().sys_exc_info()
+ if last_operror is None:
+ return
+
+ # We must normalize the value right now to check for cycles
+ self.normalize_exception(space)
+ w_value = self.get_w_value(space)
+ w_last_value = last_operror.get_w_value(space)
+ if not space.is_w(w_value, w_last_value):
+ # Avoid reference cycles through the context chain. This is
+ # O(chain length) but context chains are usually very short.
+ w_obj = w_last_value
+ while True:
+ w_context = space.getattr(w_obj, space.wrap('__context__'))
+ if space.is_w(w_context, space.w_None):
+ break
+ if space.is_w(w_context, w_value):
+ space.setattr(w_obj, space.wrap('__context__'),
space.w_None)
+ break
+ w_obj = w_context
+ space.setattr(w_value, space.wrap('__context__'), w_last_value)
+
def clear(self, space):
self.w_type = space.w_None
self._w_value = space.w_None
@@ -199,8 +226,21 @@
w_value = space.call_function(w_type, w_value)
w_type = self._exception_getclass(space, w_value)
if self.w_cause:
+ # ensure w_cause is of a valid type
+ self._exception_getclass(space, self.w_cause, "exception
causes")
space.setattr(w_value, space.wrap("__cause__"), self.w_cause)
-
+ if self._application_traceback:
+ from pypy.interpreter.pytraceback import PyTraceback
+ from pypy.module.exceptions.interp_exceptions import
W_BaseException
+ tb = self._application_traceback
+ if (isinstance(w_value, W_BaseException) and
+ isinstance(tb, PyTraceback)):
+ # traceback hasn't escaped yet
+ w_value.w_traceback = tb
+ else:
+ # traceback has escaped
+ space.setattr(w_value, space.wrap("__traceback__"),
+ space.wrap(self.get_traceback()))
else:
# the only case left here is (inst, None), from a 'raise inst'.
w_inst = w_type
@@ -215,13 +255,12 @@
self.w_type = w_type
self._w_value = w_value
- def _exception_getclass(self, space, w_inst):
+ def _exception_getclass(self, space, w_inst, what="exceptions"):
w_type = space.exception_getclass(w_inst)
if not space.exception_is_valid_class_w(w_type):
typename = w_type.getname(space)
- msg = ("exceptions must be classes or instances deriving from "
- "BaseException, not %s")
- raise operationerrfmt(space.w_TypeError, msg, typename)
+ msg = "%s must derive from BaseException, not %s"
+ raise operationerrfmt(space.w_TypeError, msg, what, typename)
return w_type
def write_unraisable(self, space, where, w_object=None,
@@ -336,12 +375,12 @@
#
class OpErrFmt(OperationError):
def __init__(self, w_type, strings, *args):
- self.setup(w_type)
assert len(args) == len(strings) - 1
self.xstrings = strings
for i, fmt, attr in entries:
setattr(self, attr, args[i])
assert w_type is not None
+ self.setup(w_type)
def _compute_value(self):
lst = [None] * (len(formats) + len(formats) + 1)
for i, fmt, attr in entries:
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -480,11 +480,12 @@
# re-raise, no new traceback obj will be attached
self.last_exception = operror
raise RaiseWithExplicitTraceback(operror)
- w_value = w_cause = space.w_None
if nbargs == 2:
w_cause = self.popvalue()
if space.exception_is_valid_obj_as_class_w(w_cause):
w_cause = space.call_function(w_cause)
+ else:
+ w_cause = None
w_value = self.popvalue()
if space.exception_is_valid_obj_as_class_w(w_value):
w_type = w_value
diff --git a/pypy/interpreter/test/test_interpreter.py
b/pypy/interpreter/test/test_interpreter.py
--- a/pypy/interpreter/test/test_interpreter.py
+++ b/pypy/interpreter/test/test_interpreter.py
@@ -73,8 +73,7 @@
raise 1
''', 'f', [])
assert "TypeError:" in x
- assert ("exceptions must be classes or instances deriving from "
- "BaseException, not ") in x
+ assert "exceptions must derive from BaseException" in x
def test_except2(self):
x = self.codetest('''
diff --git a/pypy/interpreter/test/test_raise.py
b/pypy/interpreter/test/test_raise.py
--- a/pypy/interpreter/test/test_raise.py
+++ b/pypy/interpreter/test/test_raise.py
@@ -79,21 +79,6 @@
assert sys.exc_info()[0] is ValueError
assert sys.exc_info() == (None, None, None)
- def test_raise_with___traceback__(self):
- import sys
- try:
- raise ValueError
- except:
- exc_type,exc_val,exc_tb = sys.exc_info()
- try:
- exc_val.__traceback__ = exc_tb
- raise exc_val
- except:
- exc_type2,exc_val2,exc_tb2 = sys.exc_info()
- assert exc_type is exc_type2
- assert exc_val is exc_val2
- assert exc_tb is exc_tb2.tb_next
-
def test_reraise_1(self):
raises(IndexError, """
import sys
@@ -321,6 +306,111 @@
break
assert sys.exc_info() == (None, None, None)
+class AppTestRaiseContext:
+
+ def test_instance_context(self):
+ context = IndexError()
+ try:
+ try:
+ raise context
+ except:
+ raise OSError()
+ except OSError as e:
+ assert e.__context__ is context
+ else:
+ fail('No exception raised')
+
+ def test_class_context(self):
+ context = IndexError
+ try:
+ try:
+ raise context
+ except:
+ raise OSError()
+ except OSError as e:
+ assert e.__context__ != context
+ assert isinstance(e.__context__, context)
+ else:
+ fail('No exception raised')
+
+ def test_internal_exception(self):
+ try:
+ try:
+ 1/0
+ except:
+ xyzzy
+ except NameError as e:
+ assert isinstance(e.__context__, ZeroDivisionError)
+ else:
+ fail("No exception raised")
+
+ def test_cycle_broken(self):
+ try:
+ try:
+ 1/0
+ except ZeroDivisionError as e:
+ raise e
+ except ZeroDivisionError as e:
+ assert e.__context__ is None
+ else:
+ fail("No exception raised")
+
+ def test_reraise_cycle_broken(self):
+ try:
+ try:
+ xyzzy
+ except NameError as a:
+ try:
+ 1/0
+ except ZeroDivisionError:
+ raise a
+ except NameError as e:
+ assert e.__context__.__context__ is None
+ else:
+ fail("No exception raised")
+
+class AppTestTraceback:
+
+ def test_raise_with___traceback__(self):
+ import sys
+ try:
+ raise ValueError
+ except:
+ exc_type,exc_val,exc_tb = sys.exc_info()
+ try:
+ exc_val.__traceback__ = exc_tb
+ raise exc_val
+ except:
+ exc_type2,exc_val2,exc_tb2 = sys.exc_info()
+ assert exc_type is exc_type2
+ assert exc_val is exc_val2
+ assert exc_tb is exc_tb2.tb_next
+
+ def test_sets_traceback(self):
+ import types
+ try:
+ raise IndexError()
+ except IndexError as e:
+ assert isinstance(e.__traceback__, types.TracebackType)
+ else:
+ fail("No exception raised")
+
+ def test_accepts_traceback(self):
+ import sys
+ def get_tb():
+ try:
+ raise OSError()
+ except:
+ return sys.exc_info()[2]
+ tb = get_tb()
+ try:
+ raise IndexError().with_traceback(tb)
+ except IndexError as e:
+ assert e.__traceback__ != tb
+ assert e.__traceback__.tb_next is tb
+ else:
+ fail("No exception raised")
+
def test_invalid_reraise(self):
try:
raise
@@ -328,3 +418,27 @@
assert "No active exception" in str(e)
else:
fail("Expected RuntimeError")
+
+ def test_invalid_cause(self):
+ """
+ try:
+ raise IndexError from 5
+ except TypeError as e:
+ assert "exception cause" in str(e)
+ else:
+ fail("Expected TypeError")
+ """
+
+ def test_invalid_cause_setter(self):
+ """
+ class Setter(BaseException):
+ def set_cause(self, cause):
+ self.cause = cause
+ __cause__ = property(fset=set_cause)
+ try:
+ raise Setter from 5
+ except TypeError as e:
+ assert "exception cause" in str(e)
+ else:
+ fail("Expected TypeError")
+ """
diff --git a/pypy/module/exceptions/interp_exceptions.py
b/pypy/module/exceptions/interp_exceptions.py
--- a/pypy/module/exceptions/interp_exceptions.py
+++ b/pypy/module/exceptions/interp_exceptions.py
@@ -171,7 +171,12 @@
self.w_context = w_newcontext
def descr_gettraceback(self, space):
- return self.w_traceback
+ from pypy.interpreter.pytraceback import PyTraceback
+ tb = self.w_traceback
+ if tb is not None and isinstance(tb, PyTraceback):
+ # tb escapes to app level (see OperationError.get_traceback)
+ tb.frame.mark_as_escaped()
+ return tb
def descr_settraceback(self, space, w_newtraceback):
msg = '__traceback__ must be a traceback or None'
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit