[pypy-commit] pypy py3.5-refactor-sys_exc_info: (arigo, cfbolz): continue refactoring

cfbolz pypy.commits at gmail.com
Wed Nov 16 11:37:21 EST 2016


Author: Carl Friedrich Bolz <cfbolz at gmx.de>
Branch: py3.5-refactor-sys_exc_info
Changeset: r88408:b4bb834f45e9
Date: 2016-11-16 16:47 +0000
http://bitbucket.org/pypy/pypy/changeset/b4bb834f45e9/

Log:	(arigo, cfbolz): continue refactoring

diff --git a/pypy/interpreter/astcompiler/assemble.py b/pypy/interpreter/astcompiler/assemble.py
--- a/pypy/interpreter/astcompiler/assemble.py
+++ b/pypy/interpreter/astcompiler/assemble.py
@@ -622,8 +622,8 @@
 
     ops.PRINT_EXPR: -1,
 
-    ops.WITH_CLEANUP_START: 1,
-    ops.WITH_CLEANUP_FINISH: -2,
+    ops.WITH_CLEANUP_START: 0,
+    ops.WITH_CLEANUP_FINISH: -1,
     ops.LOAD_BUILD_CLASS: 1,
     ops.POP_BLOCK: 0,
     ops.POP_EXCEPT: -1,
diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py
--- a/pypy/interpreter/executioncontext.py
+++ b/pypy/interpreter/executioncontext.py
@@ -226,7 +226,6 @@
             self._trace(frame, 'exception', None, operationerr)
         #operationerr.print_detailed_traceback(self.space)
 
-    @jit.dont_look_inside
     def sys_exc_info(self):
         """Implements sys.exc_info().
         Return an OperationError instance or None.
diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py
--- a/pypy/interpreter/generator.py
+++ b/pypy/interpreter/generator.py
@@ -22,6 +22,7 @@
         self._qualname = qualname   # may be null, use get_qualname()
         if self.pycode.co_flags & CO_YIELD_INSIDE_TRY:
             self.register_finalizer(self.space)
+        self.saved_operr = None
 
     def get_name(self):
         # 'name' is a byte string that is valid utf-8
@@ -46,6 +47,7 @@
                            unicode(addrstring)))
 
     def descr__reduce__(self, space):
+        # DEAD CODE, see frame.__reduce__
         from pypy.interpreter.mixedmodule import MixedModule
         w_mod = space.getbuiltinmodule('_pickle_support')
         mod = space.interp_w(MixedModule, w_mod)
@@ -61,6 +63,7 @@
                                space.newtuple(tup)])
 
     def descr__setstate__(self, space, w_args):
+        # DEAD CODE, see frame.__reduce__
         from rpython.rlib.objectmodel import instantiate
         args_w = space.unpackiterable(w_args)
         w_framestate, w_running = args_w
@@ -136,6 +139,10 @@
         space = self.space
         if self.running:
             raise oefmt(space.w_ValueError, "%s already executing", self.KIND)
+        ec = space.getexecutioncontext()
+        current_exc_info = ec.sys_exc_info()
+        if self.saved_operr is not None:
+            ec.set_sys_exc_info(self.saved_operr)
         self.running = True
         try:
             w_result = frame.execute_frame(self, w_arg_or_err)
@@ -150,6 +157,8 @@
         finally:
             frame.f_backref = jit.vref_None
             self.running = False
+            self.saved_operr = ec.sys_exc_info()
+            ec.set_sys_exc_info(current_exc_info)
         return w_result
 
     def resume_execute_frame(self, frame, w_arg_or_err):
@@ -162,7 +171,7 @@
             try:
                 self.next_yield_from(frame, w_yf, w_arg_or_err)
             except OperationError as operr:
-                operr.record_context(space, frame)
+                operr.record_context(space, space.getexecutioncontext())
                 return frame.handle_generator_error(operr)
             # Normal case: the call above raises Yield.
             # We reach this point if the iterable is exhausted.
@@ -226,7 +235,7 @@
                                 space.wrap("%s raised StopIteration" %
                                            self.KIND))
             e2.chain_exceptions(space, e)
-            e2.record_context(space, self.frame)
+            e2.record_context(space, space.getexecutioncontext())
             raise e2
         else:
             space.warn(space.wrap(u"generator '%s' raised StopIteration"
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -457,6 +457,9 @@
 
     @jit.dont_look_inside
     def descr__reduce__(self, space):
+        # DEAD CODE AHEAD: frame.__reduce__() has been removed.
+        # Either re-add at some point, or kill this code.
+        dead_code
         from pypy.interpreter.mixedmodule import MixedModule
         w_mod    = space.getbuiltinmodule('_pickle_support')
         mod      = space.interp_w(MixedModule, w_mod)
@@ -467,6 +470,8 @@
 
     @jit.dont_look_inside
     def _reduce_state(self, space):
+        # DEAD CODE AHEAD: frame.__reduce__() has been removed.
+        dead_code
         from pypy.module._pickle_support import maker # helper fns
         w = space.wrap
         nt = space.newtuple
@@ -515,6 +520,9 @@
 
     @jit.dont_look_inside
     def descr__setstate__(self, space, w_args):
+        # DEAD CODE AHEAD: frame.__reduce__() has been removed.
+        # Either re-add at some point, or kill this code.
+        dead_code
         from pypy.module._pickle_support import maker # helper fns
         from pypy.interpreter.pycode import PyCode
         from pypy.interpreter.module import Module
@@ -882,23 +890,6 @@
             return space.wrap(self.builtin is not space.builtin)
         return space.w_False
 
-    @jit.unroll_safe
-    @specialize.arg(2)
-    def _exc_info_unroll(self, space, for_hidden=False):
-        """Return the most recent OperationError being handled in the
-        call stack
-        """
-        frame = self
-        while frame:
-            last = frame.last_exception
-            if last is not None:
-                if last is get_cleared_operation_error(self.space):
-                    break
-                if for_hidden or not frame.hide():
-                    return last
-            frame = frame.f_backref()
-        return None
-
     def get_generator(self):
         if self.space.config.translation.rweakref:
             return self.f_generator_wref()
@@ -906,10 +897,9 @@
             return self.f_generator_nowref
 
     def descr_clear(self, space):
+        """F.clear(): clear most references held by the frame"""
         # Clears a random subset of the attributes (e.g. the fast
-        # locals, but not f_locals).  Also clears last_exception, which
-        # is not quite like CPython when it clears f_exc_* (however
-        # there might not be an observable difference).
+        # locals, but not f_locals).
         if not self.frame_finished_execution:
             if not self._is_generator_or_coroutine():
                 raise oefmt(space.w_RuntimeError,
@@ -923,7 +913,6 @@
                 # awaited" in this case too.  Does it make any sense?
                 gen.descr_close()
 
-        self.last_exception = None
         debug = self.getdebug()
         if debug is not None:
             debug.w_f_trace = None
@@ -937,6 +926,7 @@
                 w_newvalue = None
             self.locals_cells_stack_w[i] = w_newvalue
         self.valuestackdepth = 0
+        self.lastblock = None    # the FrameBlock chained list
 
     def _convert_unexpected_exception(self, e):
         from pypy.interpreter import error
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -63,10 +63,7 @@
         try:
             while True:
                 next_instr = self.handle_bytecode(co_code, next_instr, ec)
-        except Return:
-            self.last_exception = None
-            return self.popvalue()
-        except Yield:
+        except ExitFrame:
             return self.popvalue()
 
     def handle_bytecode(self, co_code, next_instr, ec):
@@ -746,11 +743,13 @@
             self.setdictscope(w_locals)
 
     def POP_EXCEPT(self, oparg, next_instr):
-        pass    # no-op for now: done by the END_FINALLY that follows anyway
+        block = self.pop_block()
+        assert isinstance(block, SysExcInfoRestorer)
+        block.cleanupstack(self)   # restores ec.sys_exc_operror
 
     def POP_BLOCK(self, oparg, next_instr):
         block = self.pop_block()
-        block.cleanup(self)  # the block knows how to clean up the value stack
+        block.pop_block(self)  # the block knows how to clean up the value stack
 
     def save_and_change_sys_exc_info(self, operationerr):
         ec = self.space.getexecutioncontext()
@@ -774,7 +773,7 @@
 
         block = self.pop_block()
         assert isinstance(block, SysExcInfoRestorer)
-        block.cleanupstack()   # restores ec.sys_exc_operror
+        block.cleanupstack(self)   # restores ec.sys_exc_operror
 
         w_top = self.popvalue()
         if self.space.is_w(w_top, self.space.w_None):
@@ -1161,41 +1160,28 @@
         if isinstance(w_unroller, SApplicationException):
             # app-level exception
             operr = w_unroller.operr
-            # this looks again like the kind of code we have. except that it's a call, not a block
-            old_last_exception = self.last_exception
-            self.last_exception = operr
             w_traceback = self.space.wrap(operr.get_traceback())
-            try:
-                w_res = self.call_contextmanager_exit_function(
-                    w_exitfunc,
-                    operr.w_type,
-                    operr.get_w_value(self.space),
-                    w_traceback)
-            except:
-                self.last_exception = old_last_exception
-                raise
-            # push a marker that also contains the old_last_exception,
-            # which must be restored as 'self.last_exception' but only
-            # in WITH_CLEANUP_FINISH
-            self.pushvalue(SApplicationException(old_last_exception))
+            w_res = self.call_contextmanager_exit_function(
+                w_exitfunc,
+                operr.w_type,
+                operr.get_w_value(self.space),
+                w_traceback)
         else:
             w_res = self.call_contextmanager_exit_function(
                 w_exitfunc,
                 self.space.w_None,
                 self.space.w_None,
                 self.space.w_None)
-            self.pushvalue(self.space.w_None)
         self.pushvalue(w_res)
+        # in the stack now:  [w_res, w_unroller-or-w_None..]
 
     def WITH_CLEANUP_FINISH(self, oparg, next_instr):
         w_suppress = self.popvalue()
-        w_marker = self.popvalue()
-        if isinstance(w_marker, SApplicationException):
-            self.last_exception = w_marker.operr    # may be None
-            if self.space.is_true(w_suppress):
-                # __exit__() returned True -> Swallow the exception.
-                self.settopvalue(self.space.w_None)
+        if self.space.is_true(w_suppress):
+            # __exit__() returned True -> Swallow the exception.
+            self.settopvalue(self.space.w_None)
         # this is always followed by END_FINALLY
+        # in the stack now: [w_unroller-or-w_None..]
 
     @jit.unroll_safe
     def call_function(self, oparg, w_starstar=None, has_vararg=False):
@@ -1476,8 +1462,8 @@
 
     def SETUP_ASYNC_WITH(self, offsettoend, next_instr):
         res = self.popvalue()
-        block = WithBlock(self.valuestackdepth,
-                          next_instr + offsettoend, self.lastblock)
+        block = FinallyBlock(self.valuestackdepth,
+                             next_instr + offsettoend, self.lastblock)
         self.lastblock = block
         self.pushvalue(res)
 
@@ -1559,12 +1545,15 @@
 
 ### ____________________________________________________________ ###
 
+class ExitFrame(Exception):
+    pass
 
-class Return(Exception):
+
+class Return(ExitFrame):
     """Raised when exiting a frame via a 'return' statement."""
 
 
-class Yield(Exception):
+class Yield(ExitFrame):
     """Raised when exiting a frame via a 'yield' statement."""
 
 
@@ -1656,7 +1645,7 @@
     def cleanupstack(self, frame):
         frame.dropvaluesuntil(self.valuestackdepth)
 
-    def cleanup(self, frame):
+    def pop_block(self, frame):
         "Clean up a frame when we normally exit the block."
         self.cleanupstack(frame)
 
@@ -1707,6 +1696,9 @@
         self.operr = operr
         self.previous = previous
 
+    def pop_block(self, frame):
+        assert False # never called
+
     def handle(self, frame, unroller):
         assert False # never called
 
@@ -1763,6 +1755,10 @@
         frame.save_and_change_sys_exc_info(operationerr)
         return r_uint(self.handlerposition)   # jump to the handler
 
+    def pop_block(self, frame):
+        self.cleanupstack(frame)
+        frame.save_and_change_sys_exc_info(None)
+
 
 block_classes = {'SYS_EXC_INFO_RESTORER': SysExcInfoRestorer,
                  'SETUP_LOOP': LoopBlock,
diff --git a/pypy/interpreter/test/test_executioncontext.py b/pypy/interpreter/test/test_executioncontext.py
--- a/pypy/interpreter/test/test_executioncontext.py
+++ b/pypy/interpreter/test/test_executioncontext.py
@@ -2,9 +2,8 @@
 from pypy.interpreter import executioncontext
 from pypy.interpreter.error import OperationError
 
-class Finished(OperationError):
-    def __init__(self):
-        OperationError.__init__(self, "exception_class", "exception_value")
+class Finished(Exception):
+    pass
 
 
 class TestExecutionContext:
diff --git a/pypy/interpreter/test/test_generator.py b/pypy/interpreter/test/test_generator.py
--- a/pypy/interpreter/test/test_generator.py
+++ b/pypy/interpreter/test/test_generator.py
@@ -457,6 +457,46 @@
         assert closed == [True]
         """
 
+    def test_exc_info_in_generator(self):
+        import sys
+        def g():
+            try:
+                raise ValueError
+            except ValueError:
+                yield sys.exc_info()[0]
+                yield sys.exc_info()[0]
+        try:
+            raise IndexError
+        except IndexError:
+            gen = g()
+            assert sys.exc_info()[0] is IndexError
+            assert next(gen) is ValueError
+            assert sys.exc_info()[0] is IndexError
+            assert next(gen) is ValueError
+            assert sys.exc_info()[0] is IndexError
+            raises(StopIteration, next, gen)
+            assert sys.exc_info()[0] is IndexError
+
+    def test_exc_info_in_generator_2(self):
+        import sys
+        def g():
+            yield sys.exc_info()[0]
+            try:
+                raise LookupError
+            except LookupError:
+                yield sys.exc_info()[0]
+            yield sys.exc_info()[0]
+        try:
+            raise IndexError
+        except IndexError:
+            gen = g()     # the IndexError is not captured at all
+        try:
+            raise ValueError
+        except ValueError:
+            assert next(gen) is ValueError
+            assert next(gen) is LookupError
+            assert next(gen) is ValueError
+
 
 def test_should_not_inline(space):
     from pypy.interpreter.generator import should_not_inline
diff --git a/pypy/interpreter/test/test_pyframe.py b/pypy/interpreter/test/test_pyframe.py
--- a/pypy/interpreter/test/test_pyframe.py
+++ b/pypy/interpreter/test/test_pyframe.py
@@ -148,30 +148,6 @@
         assert f1bis is f1
         assert f0.f_back is f1
 
-    def test_f_exc_xxx(self):
-        import sys
-
-        class OuterException(Exception):
-            pass
-        class InnerException(Exception):
-            pass
-
-        def g(exc_info):
-            f = sys._getframe()
-            assert f.f_exc_type is None
-            assert f.f_exc_value is None
-            assert f.f_exc_traceback is None
-            try:
-                raise InnerException
-            except:
-                assert f.f_exc_type is exc_info[0]
-                assert f.f_exc_value is exc_info[1]
-                assert f.f_exc_traceback is exc_info[2]
-        try:
-            raise OuterException
-        except:
-            g(sys.exc_info())
-
     def test_virtualref_through_traceback(self):
         import sys
         def g():
diff --git a/pypy/interpreter/test/test_zzpickle_and_slow.py b/pypy/interpreter/test/test_zzpickle_and_slow.py
--- a/pypy/interpreter/test/test_zzpickle_and_slow.py
+++ b/pypy/interpreter/test/test_zzpickle_and_slow.py
@@ -351,6 +351,7 @@
         assert list(result) == [2,3,4]
 
     def test_pickle_generator(self):
+        skip("not supported any more for now")
         self.skip_on_cpython()
         import types
         mod = types.ModuleType('mod')
@@ -375,6 +376,7 @@
             del sys.modules['mod']
 
     def test_pickle_generator_blk(self):
+        skip("not supported any more for now")
         self.skip_on_cpython()
         # same as above but with the generator inside a block
         import types
@@ -448,6 +450,7 @@
 
 
     def test_pickle_generator_crash(self):
+        skip("not supported any more for now")
         self.skip_on_cpython()
         import pickle
 
@@ -464,7 +467,7 @@
         assert 'finished' in repr(y)
         assert y.gi_code is None
 
-class AppTestGeneratorCloning:
+class XAppTestGeneratorCloning:
 
     def setup_class(cls):
         try:
@@ -528,7 +531,7 @@
             raises(StopIteration, next, g3)
             raises(StopIteration, next, g4)
 
-class AppTestFramePickling(object):
+class XAppTestFramePickling(object):
     pytestmark = py.test.mark.skipif("config.option.runappdirect")
     spaceconfig = {
         "usemodules": ["struct"]
diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py
--- a/pypy/interpreter/typedef.py
+++ b/pypy/interpreter/typedef.py
@@ -607,8 +607,8 @@
 PyCode.typedef.acceptable_as_base_class = False
 
 PyFrame.typedef = TypeDef('frame',
-    __reduce__ = interp2app(PyFrame.descr__reduce__),
-    __setstate__ = interp2app(PyFrame.descr__setstate__),
+    #__reduce__ = interp2app(PyFrame.descr__reduce__),  --- logic not updated
+    #__setstate__ = interp2app(PyFrame.descr__setstate__),
     clear = interp2app(PyFrame.descr_clear),
     f_builtins = GetSetProperty(PyFrame.fget_f_builtins),
     f_lineno = GetSetProperty(PyFrame.fget_f_lineno, PyFrame.fset_f_lineno),
@@ -784,8 +784,8 @@
 
 GeneratorIterator.typedef = TypeDef("generator",
     __repr__   = interp2app(GeneratorIterator.descr__repr__),
-    __reduce__   = interp2app(GeneratorIterator.descr__reduce__),
-    __setstate__ = interp2app(GeneratorIterator.descr__setstate__),
+    #__reduce__   = interp2app(GeneratorIterator.descr__reduce__),
+    #__setstate__ = interp2app(GeneratorIterator.descr__setstate__),
     __next__   = interp2app(GeneratorIterator.descr_next,
                             descrmismatch='__next__'),
     send       = interp2app(GeneratorIterator.descr_send,
diff --git a/pypy/module/pypyjit/interp_jit.py b/pypy/module/pypyjit/interp_jit.py
--- a/pypy/module/pypyjit/interp_jit.py
+++ b/pypy/module/pypyjit/interp_jit.py
@@ -26,7 +26,6 @@
                            'valuestackdepth',
                            'locals_cells_stack_w[*]',
                            'debugdata',
-                           'last_exception',
                            'lastblock',
                            'w_globals',
                            ]
@@ -98,7 +97,6 @@
             jit.hint(self, force_virtualizable=True)
             return w_result
         except Return:
-            self.last_exception = None
             return self.popvalue()
 
     def jump_absolute(self, jumpto, ec):
diff --git a/pypy/module/sys/vm.py b/pypy/module/sys/vm.py
--- a/pypy/module/sys/vm.py
+++ b/pypy/module/sys/vm.py
@@ -107,8 +107,7 @@
         return space.newtuple([operror.w_type, operror.get_w_value(space),
                                space.wrap(operror.get_traceback())])
 
-def exc_info_without_tb(space, frame):
-    operror = frame.last_exception
+def exc_info_without_tb(space, operror):
     return space.newtuple([operror.w_type, operror.get_w_value(space),
                            space.w_None])
 
@@ -152,10 +151,11 @@
                         if space.int_w(w_constant) <= 2:
                             need_all_three_args = False
     #
-    if need_all_three_args or frame.last_exception is None or frame.hide():
+    operror = space.getexecutioncontext().sys_exc_info()
+    if need_all_three_args or operror is None or frame.hide():
         return exc_info_with_tb(space)
     else:
-        return exc_info_without_tb(space, frame)
+        return exc_info_without_tb(space, operror)
 
 def exc_clear(space):
     """Clear global information on the current exception.  Subsequent calls
diff --git a/pypy/tool/pytest/appsupport.py b/pypy/tool/pytest/appsupport.py
--- a/pypy/tool/pytest/appsupport.py
+++ b/pypy/tool/pytest/appsupport.py
@@ -210,26 +210,20 @@
                                space.newtuple([w_BuiltinAssertionError]),
                                w_dict)
 
-def _exc_info(space, err):
-    """Hack the fact that exc_info() isn't set until a app except
-    block catches it."""
-    err.normalize_exception(space)
-    frame = space.getexecutioncontext().gettopframe()
-    old = frame.last_exception
-    frame.last_exception = err
-    if not hasattr(space, '_w_ExceptionInfo'):
-        space._w_ExceptionInfo = space.appexec([], """():
-    class _ExceptionInfo(object):
-        def __init__(self):
-            import sys
-            self.type, self.value, self.traceback = sys.exc_info()
-
-    return _ExceptionInfo
-""")
-    try:
-        return space.call_function(space._w_ExceptionInfo)
-    finally:
-        frame.last_exception = old
+def _exc_info(space, operror):
+    """sys.exc_info() isn't set until a app except block catches it,
+    but we can directly copy the two lines of code from module/sys/vm.py."""
+    operror.normalize_exception(space)
+    return space.appexec([operror.w_type, operror.get_w_value(space),
+                          space.wrap(operror.get_traceback())], """(t, v, tb):
+        class _ExceptionInfo:
+            pass
+        e = _ExceptionInfo()
+        e.type = t
+        e.value = v
+        e.traceback = tb
+        return e
+    """)
 
 def pypyraises(space, w_ExpectedException, w_expr, __args__):
     """A built-in function providing the equivalent of py.test.raises()."""


More information about the pypy-commit mailing list