[pypy-commit] pypy py3.6: hg merge py3.6-exc-info

arigo pypy.commits at gmail.com
Thu Nov 7 08:08:15 EST 2019


Author: Armin Rigo <arigo at tunes.org>
Branch: py3.6
Changeset: r97978:251b47698e8e
Date: 2019-11-07 14:04 +0100
http://bitbucket.org/pypy/pypy/changeset/251b47698e8e/

Log:	hg merge py3.6-exc-info

	Generators need to store the old current 'exc_info' in a place that
	is visible, because in one corner case a call to sys.exc_info()
	might need it. See issue #3096

diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py
--- a/pypy/interpreter/executioncontext.py
+++ b/pypy/interpreter/executioncontext.py
@@ -34,6 +34,7 @@
         # time it is the exception caught by the topmost 'except ... as e:'
         # app-level block.
         self.sys_exc_operror = None
+        self.previous_operror_stack = []
         self.w_tracefunc = None
         self.is_tracing = 0
         self.compiler = space.createcompiler()
@@ -238,18 +239,62 @@
             self._trace(frame, 'exception', None, operationerr)
         #operationerr.print_detailed_traceback(self.space)
 
+    @jit.unroll_safe
     def sys_exc_info(self):
         """Implements sys.exc_info().
         Return an OperationError instance or None.
+        Returns the "top-most" exception in the stack.
 
         # NOTE: the result is not the wrapped sys.exc_info() !!!
 
         """
-        return self.sys_exc_operror
+        result = self.sys_exc_operror
+        if result is None:
+            i = len(self.previous_operror_stack) - 1
+            while i >= 0:
+                result = self.previous_operror_stack[i]
+                if result is not None:
+                    break
+                i -= 1
+        return result
 
     def set_sys_exc_info(self, operror):
         self.sys_exc_operror = operror
 
+    def set_sys_exc_info3(self, w_type, w_value, w_traceback):
+        space = self.space
+        if space.is_none(w_value):
+            operror = None
+        else:
+            tb = None
+            if not space.is_none(w_traceback):
+                try:
+                    tb = pytraceback.check_traceback(space, w_traceback, '?')
+                except OperationError:    # catch and ignore bogus objects
+                    pass
+            operror = OperationError(w_type, w_value, tb)
+        self.set_sys_exc_info(operror)
+
+    def enter_error_stack_item(self, saved_operr):
+        # 'sys_exc_operror' should be logically considered as the last
+        # item on the stack, so pushing a new item has the following effect:
+        self.previous_operror_stack.append(self.sys_exc_operror)
+        self.sys_exc_operror = saved_operr
+
+    def leave_error_stack_item(self):
+        result = self.sys_exc_operror
+        self.sys_exc_operror = self.previous_operror_stack.pop()
+        return result
+
+    def fetch_and_clear_error_stack_state(self):
+        result = self.sys_exc_operror, self.previous_operror_stack
+        self.sys_exc_operror = None
+        self.previous_operror_stack = []
+        return result
+
+    def restore_error_stack_state(self, saved):
+        self.sys_exc_operror, self.previous_operror_stack = saved
+
     @jit.dont_look_inside
     def settrace(self, w_func):
         """Set the global trace function."""
diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py
--- a/pypy/interpreter/generator.py
+++ b/pypy/interpreter/generator.py
@@ -105,11 +105,6 @@
         frame = self.frame
         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.saved_operr = None
         #
         # Optimization only: after we've started a Coroutine without
         # CO_YIELD_INSIDE_TRY, then Coroutine._finalize_() will be a no-op
@@ -123,6 +118,8 @@
                 raise oefmt(space.w_TypeError,
                             "can't send non-None value to a just-started %s",
                             self.KIND)
+        ec = space.getexecutioncontext()
+        ec.enter_error_stack_item(self.saved_operr)
         self.running = True
         try:
             w_result = frame.execute_frame(w_arg_or_err)
@@ -142,10 +139,8 @@
             self.running = False
             # note: this is not perfectly correct: see
             # test_exc_info_in_generator_4.  But it's simpler and
-            # bug-to-bug compatible with CPython 3.5.
-            if frame._any_except_or_finally_handler():
-                self.saved_operr = ec.sys_exc_info()
-            ec.set_sys_exc_info(current_exc_info)
+            # bug-to-bug compatible with CPython 3.5 and 3.6.
+            self.saved_operr = ec.leave_error_stack_item()
         return w_result
 
     def get_delegate(self):
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -255,29 +255,7 @@
         if self._is_generator_or_coroutine():
             return self.initialize_as_generator(name, qualname)
         else:
-            # untranslated: check that sys_exc_info is exactly
-            # restored after running any Python function.
-            # Translated: actually save and restore it, as an attempt to
-            # work around rare cases that can occur if RecursionError or
-            # MemoryError is raised at just the wrong place
-            executioncontext = self.space.getexecutioncontext()
-            exc_on_enter = executioncontext.sys_exc_info()
-            if we_are_translated():
-                try:
-                    return self.execute_frame()
-                finally:
-                    executioncontext.set_sys_exc_info(exc_on_enter)
-            else:
-                # untranslated, we check consistency, but not in case of
-                # interp-level exceptions different than OperationError
-                # (e.g. a random failing test, or a pytest Skipped exc.)
-                try:
-                    w_res = self.execute_frame()
-                    assert exc_on_enter is executioncontext.sys_exc_info()
-                except OperationError:
-                    assert exc_on_enter is executioncontext.sys_exc_info()
-                    raise
-                return w_res
+            return self.execute_frame()
     run._always_inline_ = True
 
     def initialize_as_generator(self, name, qualname):
diff --git a/pypy/module/__pypy__/interp_magic.py b/pypy/module/__pypy__/interp_magic.py
--- a/pypy/module/__pypy__/interp_magic.py
+++ b/pypy/module/__pypy__/interp_magic.py
@@ -211,3 +211,7 @@
         return      # cpyext not imported yet, ignore
     from pypy.module.cpyext.api import invoke_pyos_inputhook
     invoke_pyos_inputhook(space)
+
+def set_exc_info(space, w_type, w_value, w_traceback=None):
+    ec = space.getexecutioncontext()
+    ec.set_sys_exc_info3(w_type, w_value, w_traceback)
diff --git a/pypy/module/__pypy__/moduledef.py b/pypy/module/__pypy__/moduledef.py
--- a/pypy/module/__pypy__/moduledef.py
+++ b/pypy/module/__pypy__/moduledef.py
@@ -118,6 +118,7 @@
         'fsdecode'                  : 'interp_magic.fsdecode',
         'pyos_inputhook'            : 'interp_magic.pyos_inputhook',
         'newmemoryview'             : 'interp_buffer.newmemoryview',
+        'set_exc_info'              : 'interp_magic.set_exc_info',
     }
 
     submodules = {
diff --git a/pypy/module/__pypy__/test/test_magic.py b/pypy/module/__pypy__/test/test_magic.py
--- a/pypy/module/__pypy__/test/test_magic.py
+++ b/pypy/module/__pypy__/test/test_magic.py
@@ -52,3 +52,28 @@
             pass
         a = A()
         assert _promote(a) is a
+
+    def test_set_exc_info(self):
+        from __pypy__ import set_exc_info
+        terr = TypeError("hello world")
+        set_exc_info(TypeError, terr)
+        try:
+            raise ValueError
+        except ValueError as e:
+            assert e.__context__ is terr
+
+    def test_set_exc_info_issue3096(self):
+        from __pypy__ import set_exc_info
+        def recover():
+            set_exc_info(None, None)
+        def main():
+            try:
+                raise RuntimeError('aaa')
+            finally:
+                recover()
+                raise RuntimeError('bbb')
+        try:
+            main()
+        except RuntimeError as e:
+            assert e.__cause__ is None
+            assert e.__context__ is None
diff --git a/pypy/module/_continuation/interp_continuation.py b/pypy/module/_continuation/interp_continuation.py
--- a/pypy/module/_continuation/interp_continuation.py
+++ b/pypy/module/_continuation/interp_continuation.py
@@ -46,9 +46,9 @@
         #
         global_state.origin = self
         self.sthread = sthread
-        saved_exception = pre_switch(sthread)
+        saved_error_state = pre_switch(sthread)
         h = sthread.new(new_stacklet_callback)
-        post_switch(sthread, h, saved_exception)
+        post_switch(sthread, h, saved_error_state)
 
     def switch(self, w_to):
         sthread = self.sthread
@@ -84,9 +84,9 @@
             # double switch: the final destination is to.h
             global_state.destination = to
         #
-        saved_exception = pre_switch(sthread)
+        saved_error_state = pre_switch(sthread)
         h = sthread.switch(global_state.destination.h)
-        return post_switch(sthread, h, saved_exception)
+        return post_switch(sthread, h, saved_error_state)
 
     @unwrap_spec(w_value = WrappedDefault(None),
                  w_to = WrappedDefault(None))
@@ -257,11 +257,9 @@
     return self.h
 
 def pre_switch(sthread):
-    saved_exception = sthread.ec.sys_exc_info()
-    sthread.ec.set_sys_exc_info(None)
-    return saved_exception
+    return sthread.ec.fetch_and_clear_error_stack_state()
 
-def post_switch(sthread, h, saved_exception):
+def post_switch(sthread, h, saved_error_state):
     origin = global_state.origin
     self = global_state.destination
     global_state.origin = None
@@ -270,7 +268,7 @@
     #
     current = sthread.ec.topframeref
     sthread.ec.topframeref = self.bottomframe.f_backref
-    sthread.ec.set_sys_exc_info(saved_exception)
+    sthread.ec.restore_error_stack_state(saved_error_state)
     self.bottomframe.f_backref = origin.bottomframe.f_backref
     origin.bottomframe.f_backref = current
     #
diff --git a/pypy/module/cpyext/pyerrors.py b/pypy/module/cpyext/pyerrors.py
--- a/pypy/module/cpyext/pyerrors.py
+++ b/pypy/module/cpyext/pyerrors.py
@@ -403,8 +403,7 @@
 
 @cpython_api([PyObjectP, PyObjectP, PyObjectP], lltype.Void)
 def PyErr_GetExcInfo(space, ptype, pvalue, ptraceback):
-    """---Cython extension---
-
+    """
     Retrieve the exception info, as known from ``sys.exc_info()``.  This
     refers to an exception that was already caught, not to an exception
     that was freshly raised.  Returns new references for the three
@@ -432,8 +431,7 @@
 
 @cpython_api([PyObject, PyObject, PyObject], lltype.Void)
 def PyErr_SetExcInfo(space, py_type, py_value, py_traceback):
-    """---Cython extension---
-
+    """
     Set the exception info, as known from ``sys.exc_info()``.  This refers
     to an exception that was already caught, not to an exception that was
     freshly raised.  This function steals the references of the arguments.
@@ -450,19 +448,9 @@
     w_type = get_w_obj_and_decref(space, py_type)
     w_value = get_w_obj_and_decref(space, py_value)
     w_traceback = get_w_obj_and_decref(space, py_traceback)
-    if w_value is None or space.is_w(w_value, space.w_None):
-        operror = None
-    else:
-        tb = None
-        if w_traceback is not None:
-            try:
-                tb = pytraceback.check_traceback(space, w_traceback, '?')
-            except OperationError:    # catch and ignore bogus objects
-                pass
-        operror = OperationError(w_type, w_value, tb)
     #
     ec = space.getexecutioncontext()
-    ec.set_sys_exc_info(operror)
+    ec.set_sys_exc_info3(w_type, w_value, w_traceback)
 
 @cpython_api([], rffi.INT_real, error=CANNOT_FAIL)
 def PyOS_InterruptOccurred(space):
diff --git a/pypy/module/sys/moduledef.py b/pypy/module/sys/moduledef.py
--- a/pypy/module/sys/moduledef.py
+++ b/pypy/module/sys/moduledef.py
@@ -209,31 +209,6 @@
         w_modules = self.get('modules')
         self.space.setitem(w_modules, w_name, w_module)
 
-    def getdictvalue(self, space, attr):
-        """ specialize access to dynamic exc_* attributes. """
-        value = MixedModule.getdictvalue(self, space, attr)
-        if value is not None:
-            return value
-        if attr == 'exc_type':
-            operror = space.getexecutioncontext().sys_exc_info()
-            if operror is None:
-                return space.w_None
-            else:
-                return operror.w_type
-        elif attr == 'exc_value':
-            operror = space.getexecutioncontext().sys_exc_info()
-            if operror is None:
-                return space.w_None
-            else:
-                return operror.get_w_value(space)
-        elif attr == 'exc_traceback':
-            operror = space.getexecutioncontext().sys_exc_info()
-            if operror is None:
-                return space.w_None
-            else:
-                return operror.get_w_traceback(space)
-        return None
-
     def get_flag(self, name):
         space = self.space
         return space.int_w(space.getattr(self.get('flags'), space.newtext(name)))
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
@@ -187,13 +187,6 @@
     else:
         return exc_info_without_tb(space, operror)
 
-def exc_clear(space):
-    """Clear global information on the current exception.  Subsequent calls
-to exc_info() will return (None,None,None) until another exception is
-raised and caught in the current thread or the execution stack returns to a
-frame where another exception is being handled."""
-    space.getexecutioncontext().clear_sys_exc_info()
-
 def settrace(space, w_func):
     """Set the global debug tracing function.  It will be called on each
 function call.  See the debugger chapter in the library manual."""


More information about the pypy-commit mailing list