Author: Philip Jenvey <[email protected]>
Branch: py3k
Changeset: r72097:53a145f0af1d
Date: 2014-06-17 20:10 -0700
http://bitbucket.org/pypy/pypy/changeset/53a145f0af1d/

Log:    improve __context__ setup vis-a-vis the JIT:

        defer its setup, when possible, until __context__ is explicitly
        requested by searching for it through the traceback. it's not
        possible to defer when __context__ comes from the current frame as
        the new exception eventually overwrites it (in frame.last_exception)

        in that case we do the setup sooner, when recording the traceback,
        with little cost.. until we need to break __context__ chain cycles
        (which I'll partly disable in the next commit)

diff --git a/pypy/interpreter/error.py b/pypy/interpreter/error.py
--- a/pypy/interpreter/error.py
+++ b/pypy/interpreter/error.py
@@ -36,44 +36,11 @@
 
     def setup(self, w_type, w_value=None):
         assert w_type is not None
-        from pypy.objspace.std.typeobject import W_TypeObject
         self.w_type = w_type
         self._w_value = w_value
-        # HACK: isinstance(w_type, W_TypeObject) won't translate under
-        # the fake objspace, but w_type.__class__ is W_TypeObject does
-        # and short circuits to a False constant there, causing the
-        # isinstance to be ignored =[
-        if (w_type is not None and w_type.__class__ is W_TypeObject and
-            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 or
-            last_operror is get_cleared_operation_error(space)):
-            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):
         # XXX remove this method.  The point is that we cannot always
         # hack at 'self' to clear w_type and _w_value, because in some
@@ -350,6 +317,51 @@
         """
         self._application_traceback = traceback
 
+    def record_context(self, space, frame):
+        """Record a __context__ for this exception from the current
+        frame if one exists.
+
+        __context__ is otherwise lazily determined from the
+        traceback. However the current frame.last_exception must be
+        checked for a __context__ before this OperationError overwrites
+        it (making the previous last_exception unavailable later on).
+        """
+        last_exception = frame.last_exception
+        if (last_exception is not None and not frame.hide() or
+            last_exception is get_cleared_operation_error(space)):
+            # normalize w_value so setup_context can check for cycles
+            self.normalize_exception(space)
+            w_value = self.get_w_value(space)
+            w_context = setup_context(space, w_value,
+                                      last_exception.get_w_value(space))
+            space.setattr(w_value, space.wrap('__context__'), w_context)
+
+
+def setup_context(space, w_exc, w_last):
+    """Determine the __context__ for w_exc from w_last and break
+    reference cycles in the __context__ chain.
+    """
+    if space.is_w(w_exc, w_last):
+        w_last = space.w_None
+    # w_last may also be space.w_None if from ClearedOpErr
+    if not space.is_w(w_last, space.w_None):
+        # Avoid reference cycles through the context chain. This is
+        # O(chain length) but context chains are usually very short.
+        w_obj = w_last
+        while True:
+            # XXX: __context__ becomes not so lazy when we're forced to
+            # access it here! Could this be defered till later? Or at
+            # least limit the check to W_BaseException.w_context
+            # (avoiding W_BaseException._setup_context)
+            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_exc):
+                space.setattr(w_obj, space.wrap('__context__'), space.w_None)
+                break
+            w_obj = w_context
+    return w_last
+
 
 class ClearedOpErr:
     def __init__(self, space):
diff --git a/pypy/interpreter/executioncontext.py 
b/pypy/interpreter/executioncontext.py
--- a/pypy/interpreter/executioncontext.py
+++ b/pypy/interpreter/executioncontext.py
@@ -202,18 +202,21 @@
             self._trace(frame, 'exception', None, operationerr)
         #operationerr.print_detailed_traceback(self.space)
 
+    @staticmethod
+    def last_operr(space, frame):
+        while frame:
+            last = frame.last_exception
+            if (last is not None and
+                (not frame.hide() or
+                 last is get_cleared_operation_error(space))):
+                    return last
+            frame = frame.f_backref()
+        return None
+
     def sys_exc_info(self): # attn: the result is not the wrapped 
sys.exc_info() !!!
         """Implements sys.exc_info().
         Return an OperationError instance or None."""
-        frame = self.gettopframe()
-        while frame:
-            if frame.last_exception is not None:
-                if (not frame.hide() or
-                        frame.last_exception is
-                            get_cleared_operation_error(self.space)):
-                    return frame.last_exception
-            frame = frame.f_backref()
-        return None
+        return self.last_operr(self.space, self.gettopframe())
 
     def set_sys_exc_info(self, operror):
         frame = self.gettopframe_nohidden()
diff --git a/pypy/interpreter/pytraceback.py b/pypy/interpreter/pytraceback.py
--- a/pypy/interpreter/pytraceback.py
+++ b/pypy/interpreter/pytraceback.py
@@ -57,6 +57,7 @@
     tb = operror.get_traceback()
     tb = PyTraceback(space, frame, last_instruction, tb)
     operror.set_traceback(tb)
+    operror.record_context(space, frame)
 
 
 def check_traceback(space, w_tb, msg):
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
@@ -369,6 +369,23 @@
         else:
             fail("No exception raised")
 
+    def test_context_once_removed(self):
+        context = IndexError()
+        def func1():
+            func2()
+        def func2():
+            try:
+                1/0
+            except ZeroDivisionError as e:
+                assert e.__context__ is context
+            else:
+                fail('No exception raised')
+        try:
+            raise context
+        except:
+            func1()
+
+
 class AppTestTraceback:
 
     def test_raise_with___traceback__(self):
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
@@ -76,8 +76,8 @@
 from pypy.interpreter.typedef import (TypeDef, GetSetProperty, descr_get_dict,
     descr_set_dict, descr_del_dict)
 from pypy.interpreter.gateway import interp2app
-from pypy.interpreter.error import OperationError
-from pypy.interpreter.pytraceback import check_traceback
+from pypy.interpreter.error import OperationError, setup_context
+from pypy.interpreter.pytraceback import PyTraceback, check_traceback
 from rpython.rlib import rwin32
 
 
@@ -156,7 +156,27 @@
         self.w_cause = w_newcause
 
     def descr_getcontext(self, space):
-        return self.w_context
+        w_context = self.w_context
+        if w_context is None:
+            self.w_context = w_context = self._setup_context(space)
+        return w_context
+
+    def _setup_context(self, space):
+        """Lazily determine __context__ from w_traceback"""
+        # XXX: w_traceback can be overwritten: it's not necessarily the
+        # authoratative traceback!
+        last_operr = None
+        w_traceback = self.w_traceback
+        if w_traceback is not None and isinstance(w_traceback, PyTraceback):
+            ec = space.getexecutioncontext()
+            # search for __context__ beginning in the previous frame. A
+            # __context__ from the top most frame would have already
+            # been handled by OperationError.record_context
+            last_operr = ec.last_operr(space, w_traceback.frame.f_backref())
+        if last_operr is None:
+            # no __context__
+            return space.w_None
+        return setup_context(space, self, last_operr.get_w_value(space))
 
     def descr_setcontext(self, space, w_newcontext):
         if not (space.is_w(w_newcontext, space.w_None) or
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to