[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