[pypy-commit] pypy py3.5: hg merge py3.5-refactor-sys_exc_info
arigo
pypy.commits at gmail.com
Fri Nov 18 09:20:31 EST 2016
Author: Armin Rigo <arigo at tunes.org>
Branch: py3.5
Changeset: r88466:bdac33b95aeb
Date: 2016-11-18 15:19 +0100
http://bitbucket.org/pypy/pypy/changeset/bdac33b95aeb/
Log: hg merge py3.5-refactor-sys_exc_info
In an except: or finally: block, move the current exception to the
executioncontext, and restore it afterwards, like CPython. This
might make the exception object escape a bit more, but it allows
other operations to work without forcing any frame.
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/astcompiler/test/test_compiler.py b/pypy/interpreter/astcompiler/test/test_compiler.py
--- a/pypy/interpreter/astcompiler/test/test_compiler.py
+++ b/pypy/interpreter/astcompiler/test/test_compiler.py
@@ -869,7 +869,7 @@
with a: pass
"""
code = compile_with_astcompiler(source, 'exec', self.space)
- assert code.co_stacksize == 6 # i.e. <= 7, there is no systematic leak
+ assert code.co_stacksize == 5 # i.e. <= 7, there is no systematic leak
def test_stackeffect_bug5(self):
source = """if 1:
diff --git a/pypy/interpreter/error.py b/pypy/interpreter/error.py
--- a/pypy/interpreter/error.py
+++ b/pypy/interpreter/error.py
@@ -333,13 +333,12 @@
tb = tb.next
self._application_traceback = tb
- def record_context(self, space, frame):
- """Record a __context__ for this exception if one exists,
- searching from the current frame.
+ def record_context(self, space, ec):
+ """Record a __context__ for this exception if one exists.
"""
if self._context_recorded:
return
- last = frame._exc_info_unroll(space)
+ last = ec.sys_exc_info()
try:
if last is not None:
self.chain_exceptions(space, last)
diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py
--- a/pypy/interpreter/executioncontext.py
+++ b/pypy/interpreter/executioncontext.py
@@ -28,6 +28,10 @@
def __init__(self, space):
self.space = space
self.topframeref = jit.vref_None
+ # this is exposed to app-level as 'sys.exc_info()'. At any point in
+ # time it is the exception caught by the topmost 'except ... as e:'
+ # app-level block.
+ self.sys_exc_operror = None
self.w_tracefunc = None
self.is_tracing = 0
self.compiler = space.createcompiler()
@@ -222,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.
@@ -230,23 +233,10 @@
# NOTE: the result is not the wrapped sys.exc_info() !!!
"""
- return self.gettopframe()._exc_info_unroll(self.space)
+ return self.sys_exc_operror
def set_sys_exc_info(self, operror):
- frame = self.gettopframe_nohidden()
- if frame: # else, the exception goes nowhere and is lost
- frame.last_exception = operror
-
- def clear_sys_exc_info(self):
- # Find the frame out of which sys_exc_info() would return its result,
- # and hack this frame's last_exception to become the cleared
- # OperationError (which is different from None!).
- frame = self.gettopframe_nohidden()
- while frame:
- if frame.last_exception is not None:
- frame.last_exception = get_cleared_operation_error(self.space)
- break
- frame = self.getnextframe_nohidden(frame)
+ self.sys_exc_operror = operror
@jit.dont_look_inside
def settrace(self, w_func):
@@ -356,7 +346,6 @@
event == 'c_exception'):
return
- last_exception = frame.last_exception
if event == 'leaveframe':
event = 'return'
@@ -372,7 +361,6 @@
raise
finally:
- frame.last_exception = last_exception
self.is_tracing -= 1
def checksignals(self):
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
@@ -45,42 +46,6 @@
self.get_qualname(),
unicode(addrstring)))
- def descr__reduce__(self, space):
- from pypy.interpreter.mixedmodule import MixedModule
- w_mod = space.getbuiltinmodule('_pickle_support')
- mod = space.interp_w(MixedModule, w_mod)
- new_inst = mod.get(self.KIND + '_new')
- w = space.wrap
- if self.frame:
- w_frame = self.frame._reduce_state(space)
- else:
- w_frame = space.w_None
-
- tup = [w_frame, w(self.running)]
- return space.newtuple([new_inst, space.newtuple([]),
- space.newtuple(tup)])
-
- def descr__setstate__(self, space, w_args):
- from rpython.rlib.objectmodel import instantiate
- args_w = space.unpackiterable(w_args)
- w_framestate, w_running = args_w
- if space.is_w(w_framestate, space.w_None):
- self.frame = None
- self.space = space
- self.pycode = None
- self._name = None
- self._qualname = None
- else:
- frame = instantiate(space.FrameClass) # XXX fish
- frame.descr__setstate__(space, w_framestate)
- if isinstance(self, GeneratorIterator):
- GeneratorIterator.__init__(self, frame)
- elif isinstance(self, Coroutine):
- Coroutine.__init__(self, frame)
- else:
- assert False
- self.running = self.space.is_true(w_running)
-
def descr_send(self, w_arg):
"""send(arg) -> send 'arg' into generator/coroutine,
return next yielded value or raise StopIteration."""
@@ -136,6 +101,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 +119,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 +133,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.
@@ -227,7 +198,7 @@
self.KIND))
e2.chain_exceptions(space, e)
e2.set_cause(space, e.get_w_value(space))
- 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
@@ -21,7 +21,7 @@
# Define some opcodes used
for op in '''DUP_TOP POP_TOP SETUP_LOOP SETUP_EXCEPT SETUP_FINALLY SETUP_WITH
-POP_BLOCK END_FINALLY'''.split():
+SETUP_ASYNC_WITH POP_BLOCK END_FINALLY'''.split():
globals()[op] = stdlib_opcode.opmap[op]
HAVE_ARGUMENT = stdlib_opcode.HAVE_ARGUMENT
@@ -66,7 +66,6 @@
f_generator_wref = rweakref.dead_ref # for generators/coroutines
f_generator_nowref = None # (only one of the two attrs)
last_instr = -1
- last_exception = None
f_backref = jit.vref_None
escaped = False # see mark_as_escaped()
@@ -249,8 +248,20 @@
"""Start this frame's execution."""
if self._is_generator_or_coroutine():
return self.initialize_as_generator(name, qualname)
+ elif we_are_translated():
+ return self.execute_frame()
else:
- return self.execute_frame()
+ # untranslated: check that sys_exc_info is exactly
+ # restored after running any Python function
+ executioncontext = self.space.getexecutioncontext()
+ exc_on_enter = executioncontext.sys_exc_info()
+ try:
+ w_res = self.execute_frame()
+ except OperationError:
+ assert exc_on_enter is executioncontext.sys_exc_info()
+ raise
+ assert exc_on_enter is executioncontext.sys_exc_info()
+ return w_res
run._always_inline_ = True
def initialize_as_generator(self, name, qualname):
@@ -326,10 +337,6 @@
raise self._convert_unexpected_exception(e)
finally:
executioncontext.return_trace(self, w_exitvalue)
- # it used to say self.last_exception = None
- # this is now done by the code in pypyjit module
- # since we don't want to invalidate the virtualizable
- # for no good reason
got_exception = False
finally:
executioncontext.leave(self, w_exitvalue, got_exception)
@@ -458,128 +465,6 @@
self.space, arguments, keywords, keywords_w, w_star,
w_starstar, methodcall=methodcall)
- @jit.dont_look_inside
- def descr__reduce__(self, space):
- from pypy.interpreter.mixedmodule import MixedModule
- w_mod = space.getbuiltinmodule('_pickle_support')
- mod = space.interp_w(MixedModule, w_mod)
- new_inst = mod.get('frame_new')
- w_tup_state = self._reduce_state(space)
- nt = space.newtuple
- return nt([new_inst, nt([]), w_tup_state])
-
- @jit.dont_look_inside
- def _reduce_state(self, space):
- from pypy.module._pickle_support import maker # helper fns
- w = space.wrap
- nt = space.newtuple
-
- if self.get_w_f_trace() is None:
- f_lineno = self.get_last_lineno()
- else:
- f_lineno = self.getorcreatedebug().f_lineno
-
- nlocals = self.pycode.co_nlocals
- values_w = self.locals_cells_stack_w
- w_locals_cells_stack = maker.slp_into_tuple_with_nulls(space, values_w)
-
- w_blockstack = nt([block._get_state_(space) for block in self.get_blocklist()])
- if self.last_exception is None:
- w_exc_value = space.w_None
- w_tb = space.w_None
- else:
- w_exc_value = self.last_exception.get_w_value(space)
- w_tb = w(self.last_exception.get_traceback())
-
- d = self.getorcreatedebug()
- tup_state = [
- w(self.f_backref()),
- w(self.get_builtin()),
- w(self.pycode),
- w_locals_cells_stack,
- w_blockstack,
- w_exc_value, # last_exception
- w_tb, #
- self.get_w_globals(),
- w(self.last_instr),
- w(self.frame_finished_execution),
- w(f_lineno),
- space.w_None, #XXX placeholder for f_locals
-
- #f_restricted requires no additional data!
- space.w_None,
-
- w(d.instr_lb),
- w(d.instr_ub),
- w(d.instr_prev_plus_one),
- w(self.valuestackdepth),
- ]
- return nt(tup_state)
-
- @jit.dont_look_inside
- def descr__setstate__(self, space, w_args):
- from pypy.module._pickle_support import maker # helper fns
- from pypy.interpreter.pycode import PyCode
- from pypy.interpreter.module import Module
- args_w = space.unpackiterable(w_args, 17)
- w_f_back, w_builtin, w_pycode, w_locals_cells_stack, w_blockstack, w_exc_value, w_tb,\
- w_globals, w_last_instr, w_finished, w_f_lineno, w_f_locals, \
- w_f_trace, w_instr_lb, w_instr_ub, w_instr_prev_plus_one, w_stackdepth = args_w
-
- new_frame = self
- pycode = space.interp_w(PyCode, w_pycode)
-
- values_w = maker.slp_from_tuple_with_nulls(space, w_locals_cells_stack)
- nfreevars = len(pycode.co_freevars)
- closure = None
- if nfreevars:
- base = pycode.co_nlocals + len(pycode.co_cellvars)
- closure = values_w[base: base + nfreevars]
-
- # do not use the instance's __init__ but the base's, because we set
- # everything like cells from here
- # XXX hack
- from pypy.interpreter.function import Function
- outer_func = Function(space, None, closure=closure,
- forcename="fake")
- PyFrame.__init__(self, space, pycode, w_globals, outer_func)
- f_back = space.interp_w(PyFrame, w_f_back, can_be_None=True)
- new_frame.f_backref = jit.non_virtual_ref(f_back)
-
- if space.config.objspace.honor__builtins__:
- new_frame.builtin = space.interp_w(Module, w_builtin)
- else:
- assert space.interp_w(Module, w_builtin) is space.builtin
- new_frame.set_blocklist([unpickle_block(space, w_blk)
- for w_blk in space.unpackiterable(w_blockstack)])
- self.locals_cells_stack_w = values_w[:]
- valuestackdepth = space.int_w(w_stackdepth)
- if not self._check_stack_index(valuestackdepth):
- raise oefmt(space.w_ValueError, "invalid stackdepth")
- assert valuestackdepth >= 0
- self.valuestackdepth = valuestackdepth
- if space.is_w(w_exc_value, space.w_None):
- new_frame.last_exception = None
- else:
- from pypy.interpreter.pytraceback import PyTraceback
- tb = space.interp_w(PyTraceback, w_tb)
- new_frame.last_exception = OperationError(space.type(w_exc_value),
- w_exc_value, tb
- )
- new_frame.last_instr = space.int_w(w_last_instr)
- new_frame.frame_finished_execution = space.is_true(w_finished)
- d = new_frame.getorcreatedebug()
- d.f_lineno = space.int_w(w_f_lineno)
-
- if space.is_w(w_f_trace, space.w_None):
- d.w_f_trace = None
- else:
- d.w_f_trace = w_f_trace
-
- d.instr_lb = space.int_w(w_instr_lb) #the three for tracing
- d.instr_ub = space.int_w(w_instr_ub)
- d.instr_prev_plus_one = space.int_w(w_instr_prev_plus_one)
-
def hide(self):
return self.pycode.hidden_applevel
@@ -724,7 +609,7 @@
return space.wrap(self.getorcreatedebug().f_lineno)
def fset_f_lineno(self, space, w_new_lineno):
- "Returns the line number of the instruction currently being executed."
+ "Change the line number of the instruction currently being executed."
try:
new_lineno = space.int_w(w_new_lineno)
except OperationError:
@@ -763,48 +648,62 @@
"can't jump to 'except' line as there's no exception")
# Don't jump into or out of a finally block.
- f_lasti_setup_addr = -1
- new_lasti_setup_addr = -1
- blockstack = []
+ # Unlike CPython, we can't jump into or out of an except block
+ # either---there would be a mess with SysExcInfoRestorer.
+ f_lasti_handler_addr = -1
+ new_lasti_handler_addr = -1
+ blockstack = [-1] # list of odd length:
+ # * addr of most recent outermost handler
+ # [ * addr of start of outermost block
+ # * addr of most recent handler in that block
+ # (last two items repeated) ]
addr = 0
while addr < len(code):
op = ord(code[addr])
- if op in (SETUP_LOOP, SETUP_EXCEPT, SETUP_FINALLY, SETUP_WITH):
- blockstack.append([addr, False])
+ if op in (SETUP_LOOP, SETUP_EXCEPT, SETUP_FINALLY, SETUP_WITH,
+ SETUP_ASYNC_WITH):
+ blockstack.append(addr)
+ blockstack.append(-1)
elif op == POP_BLOCK:
- setup_op = ord(code[blockstack[-1][0]])
- if setup_op == SETUP_FINALLY or setup_op == SETUP_WITH:
- blockstack[-1][1] = True
- else:
- blockstack.pop()
- elif op == END_FINALLY:
- if len(blockstack) > 0:
- setup_op = ord(code[blockstack[-1][0]])
- if setup_op == SETUP_FINALLY or setup_op == SETUP_WITH:
- blockstack.pop()
+ if len(blockstack) < 3:
+ raise oefmt(space.w_SystemError,
+ "blocks not properly nested in this bytecode")
+ blockstack.pop()
+ setup_op = ord(code[blockstack.pop()])
+ if setup_op != SETUP_LOOP:
+ blockstack[-1] = addr
+ elif op == END_FINALLY: # "async for" nests blocks
+ blockstack[-1] = -1 # strangely, careful here
if addr == new_lasti or addr == self.last_instr:
- for ii in range(len(blockstack)):
- setup_addr, in_finally = blockstack[~ii]
- if in_finally:
- if addr == new_lasti:
- new_lasti_setup_addr = setup_addr
- if addr == self.last_instr:
- f_lasti_setup_addr = setup_addr
- break
+ ii = len(blockstack) - 1
+ while ii > 0 and blockstack[ii] == -1:
+ ii -= 2
+ assert ii >= 0
+ handler_addr = blockstack[ii]
+ if addr == new_lasti:
+ new_lasti_handler_addr = handler_addr
+ if addr == self.last_instr:
+ f_lasti_handler_addr = handler_addr
if op >= HAVE_ARGUMENT:
addr += 3
else:
addr += 1
- assert len(blockstack) == 0
+ if len(blockstack) != 1:
+ raise oefmt(space.w_SystemError,
+ "blocks not properly nested in this bytecode")
- if new_lasti_setup_addr != f_lasti_setup_addr:
+ if new_lasti_handler_addr != f_lasti_handler_addr:
raise oefmt(space.w_ValueError,
- "can't jump into or out of a 'finally' block %d -> %d",
- f_lasti_setup_addr, new_lasti_setup_addr)
+ "can't jump into or out of an 'expect' or "
+ "'finally' block (%d -> %d)",
+ f_lasti_handler_addr, new_lasti_handler_addr)
+ # now we know we're not jumping into or out of a place which
+ # needs a SysExcInfoRestorer. Check that we're not jumping
+ # *into* a block, but only (potentially) out of some blocks.
if new_lasti < self.last_instr:
min_addr = new_lasti
max_addr = self.last_instr
@@ -812,42 +711,40 @@
min_addr = self.last_instr
max_addr = new_lasti
- delta_iblock = min_delta_iblock = 0
+ delta_iblock = min_delta_iblock = 0 # see below for comment
addr = min_addr
while addr < max_addr:
op = ord(code[addr])
- if op in (SETUP_LOOP, SETUP_EXCEPT, SETUP_FINALLY, SETUP_WITH):
+ if op in (SETUP_LOOP, SETUP_EXCEPT, SETUP_FINALLY, SETUP_WITH,
+ SETUP_ASYNC_WITH):
delta_iblock += 1
elif op == POP_BLOCK:
delta_iblock -= 1
if delta_iblock < min_delta_iblock:
min_delta_iblock = delta_iblock
- if op >= stdlib_opcode.HAVE_ARGUMENT:
+ if op >= HAVE_ARGUMENT:
addr += 3
else:
addr += 1
- f_iblock = 0
- block = self.lastblock
- while block:
- f_iblock += 1
- block = block.previous
- min_iblock = f_iblock + min_delta_iblock
+ # 'min_delta_iblock' is <= 0; its absolute value is the number of
+ # blocks we exit. 'go_iblock' is the delta number of blocks
+ # between the last_instr and the new_lasti, in this order.
if new_lasti > self.last_instr:
- new_iblock = f_iblock + delta_iblock
+ go_iblock = delta_iblock
else:
- new_iblock = f_iblock - delta_iblock
+ go_iblock = -delta_iblock
- if new_iblock > min_iblock:
+ if go_iblock > min_delta_iblock:
raise oefmt(space.w_ValueError,
"can't jump into the middle of a block")
+ assert go_iblock <= 0
- while f_iblock > new_iblock:
+ for ii in range(-go_iblock):
block = self.pop_block()
- block.cleanup(self)
- f_iblock -= 1
+ block.cleanupstack(self)
self.getorcreatedebug().f_lineno = new_lineno
self.last_instr = new_lasti
@@ -880,55 +777,11 @@
def fdel_f_trace(self, space):
self.getorcreatedebug().w_f_trace = None
- def fget_f_exc_type(self, space):
- if self.last_exception is not None:
- f = self.f_backref()
- while f is not None and f.last_exception is None:
- f = f.f_backref()
- if f is not None:
- return f.last_exception.w_type
- return space.w_None
-
- def fget_f_exc_value(self, space):
- if self.last_exception is not None:
- f = self.f_backref()
- while f is not None and f.last_exception is None:
- f = f.f_backref()
- if f is not None:
- return f.last_exception.get_w_value(space)
- return space.w_None
-
- def fget_f_exc_traceback(self, space):
- if self.last_exception is not None:
- f = self.f_backref()
- while f is not None and f.last_exception is None:
- f = f.f_backref()
- if f is not None:
- return space.wrap(f.last_exception.get_traceback())
- return space.w_None
-
def fget_f_restricted(self, space):
if space.config.objspace.honor__builtins__:
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()
@@ -936,10 +789,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,
@@ -953,7 +805,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
@@ -967,6 +818,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,17 +63,14 @@
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):
try:
next_instr = self.dispatch_bytecode(co_code, next_instr, ec)
except OperationError as operr:
- operr.record_context(self.space, self)
+ operr.record_context(self.space, ec)
next_instr = self.handle_operation_error(ec, operr)
except RaiseWithExplicitTraceback as e:
next_instr = self.handle_operation_error(ec, e.operr,
@@ -688,12 +685,11 @@
if nbargs > 2:
raise BytecodeCorruption("bad RAISE_VARARGS oparg")
if nbargs == 0:
- last_operr = self._exc_info_unroll(space, for_hidden=True)
+ last_operr = self.space.getexecutioncontext().sys_exc_info()
if last_operr is None:
raise oefmt(space.w_RuntimeError,
"No active exception to reraise")
# re-raise, no new traceback obj will be attached
- self.last_exception = last_operr
raise RaiseWithExplicitTraceback(last_operr)
if nbargs == 2:
w_cause = self.popvalue()
@@ -748,12 +744,20 @@
def POP_EXCEPT(self, oparg, next_instr):
block = self.pop_block()
- block.cleanup(self)
- return
+ 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()
+ last_exception = ec.sys_exc_info()
+ block = SysExcInfoRestorer(last_exception, self.lastblock)
+ self.lastblock = block
+ if operationerr is not None: # otherwise, don't change sys_exc_info
+ ec.set_sys_exc_info(operationerr)
def end_finally(self):
# unlike CPython, there are two statically distinct cases: the
@@ -763,8 +767,14 @@
# [exception value we are now handling]
# [wrapped SApplicationException]
# In the case of a finally: block, the stack contains only one
- # item (unlike CPython which can have 1, 2 or 3 items):
+ # item (unlike CPython which can have 1, 2, 3 or 5 items, and
+ # even in one case a non-fixed number of items):
# [wrapped subclass of SuspendedUnroller]
+
+ block = self.pop_block()
+ assert isinstance(block, SysExcInfoRestorer)
+ block.cleanupstack(self) # restores ec.sys_exc_operror
+
w_top = self.popvalue()
if self.space.is_w(w_top, self.space.w_None):
# case of a finally: block with no exception
@@ -1148,8 +1158,8 @@
w_exit = self.space.get(w_descr, w_manager)
self.settopvalue(w_exit)
w_result = self.space.get_and_call_function(w_enter, w_manager)
- block = WithBlock(self.valuestackdepth,
- next_instr + offsettoend, self.lastblock)
+ block = FinallyBlock(self.valuestackdepth,
+ next_instr + offsettoend, self.lastblock)
self.lastblock = block
self.pushvalue(w_result)
@@ -1161,39 +1171,28 @@
if isinstance(w_unroller, SApplicationException):
# app-level exception
operr = w_unroller.operr
- 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):
@@ -1474,8 +1473,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)
@@ -1557,12 +1556,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."""
@@ -1654,7 +1656,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)
@@ -1691,28 +1693,29 @@
return r_uint(self.handlerposition)
-class ExceptHandlerBlock(FrameBlock):
+class SysExcInfoRestorer(FrameBlock):
"""
- This is a special, implicit block type which is created when entering an
- except handler. It does not belong to any opcode
+ This is a special, implicit block type which is created when entering a
+ finally or except handler. It does not belong to any opcode
"""
_immutable_ = True
- _opname = 'EXCEPT_HANDLER_BLOCK' # it's not associated to any opcode
+ _opname = 'SYS_EXC_INFO_RESTORER' # it's not associated to any opcode
handling_mask = 0 # this block is never handled, only popped by POP_EXCEPT
+ def __init__(self, operr, previous):
+ self.operr = operr
+ self.previous = previous
+
+ def pop_block(self, frame):
+ assert False # never called
+
def handle(self, frame, unroller):
assert False # never called
def cleanupstack(self, frame):
- frame.dropvaluesuntil(self.valuestackdepth+1)
- w_last_exception = frame.popvalue()
- if not isinstance(w_last_exception, W_OperationError):
- msg = "expected an OperationError, got %s" % (
- frame.space.str_w(w_last_exception))
- raise BytecodeCorruption(msg)
- frame.last_exception = w_last_exception.operr
- FrameBlock.cleanupstack(self, frame)
+ ec = frame.space.getexecutioncontext()
+ ec.set_sys_exc_info(self.operr)
class ExceptBlock(FrameBlock):
@@ -1726,22 +1729,18 @@
# push the exception to the value stack for inspection by the
# exception handler (the code after the except:)
self.cleanupstack(frame)
+ # the stack setup is slightly different than in CPython:
+ # instead of the traceback, we store the unroller object,
+ # wrapped.
assert isinstance(unroller, SApplicationException)
operationerr = unroller.operr
operationerr.normalize_exception(frame.space)
- # the stack setup is slightly different than in CPython:
- # instead of the traceback, we store the unroller object,
- # wrapped.
- # this is popped by POP_EXCEPT, which is present only in py3k
- w_last_exception = W_OperationError(frame.last_exception)
- w_last_exception = frame.space.wrap(w_last_exception)
- frame.pushvalue(w_last_exception)
- block = ExceptHandlerBlock(self.valuestackdepth, 0, frame.lastblock)
- frame.lastblock = block
frame.pushvalue(frame.space.wrap(unroller))
frame.pushvalue(operationerr.get_w_value(frame.space))
frame.pushvalue(operationerr.w_type)
- frame.last_exception = operationerr
+ # set the current value of sys_exc_info to operationerr,
+ # saving the old value in a custom type of FrameBlock
+ frame.save_and_change_sys_exc_info(operationerr)
return r_uint(self.handlerposition) # jump to the handler
@@ -1751,7 +1750,6 @@
_immutable_ = True
_opname = 'SETUP_FINALLY'
handling_mask = -1 # handles every kind of SuspendedUnroller
- restore_last_exception = True # set to False by WithBlock
def handle(self, frame, unroller):
# any abnormal reason for unrolling a finally: triggers the end of
@@ -1763,53 +1761,48 @@
operationerr = unroller.operr
operationerr.normalize_exception(frame.space)
frame.pushvalue(frame.space.wrap(unroller))
- if operationerr and self.restore_last_exception:
- frame.last_exception = operationerr
+ # set the current value of sys_exc_info to operationerr,
+ # saving the old value in a custom type of FrameBlock
+ 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)
-class WithBlock(FinallyBlock):
- _immutable_ = True
- restore_last_exception = False
-
- def handle(self, frame, unroller):
- if isinstance(unroller, SApplicationException):
- unroller.operr.normalize_exception(frame.space)
- return FinallyBlock.handle(self, frame, unroller)
-
-block_classes = {'EXCEPT_HANDLER_BLOCK': ExceptHandlerBlock,
+block_classes = {'SYS_EXC_INFO_RESTORER': SysExcInfoRestorer,
'SETUP_LOOP': LoopBlock,
'SETUP_EXCEPT': ExceptBlock,
'SETUP_FINALLY': FinallyBlock,
- 'SETUP_WITH': WithBlock,
+ 'SETUP_WITH': FinallyBlock,
}
-class W_OperationError(W_Root):
- """
- Tiny applevel wrapper around an OperationError.
- """
-
- def __init__(self, operr):
- self.operr = operr
-
- def descr_reduce(self, space):
- from pypy.interpreter.mixedmodule import MixedModule
- w_mod = space.getbuiltinmodule('_pickle_support')
- mod = space.interp_w(MixedModule, w_mod)
- w_new_inst = mod.get('operationerror_new')
- w_args = space.newtuple([])
- operr = self.operr
- if operr is None:
- return space.newtuple([w_new_inst, w_args])
- w_state = space.newtuple([operr.w_type, operr.get_w_value(space),
- operr.get_traceback()])
- return space.newtuple([w_new_inst, w_args, w_state])
-
- def descr_setstate(self, space, w_state):
- w_type, w_value, w_tb = space.fixedview(w_state, 3)
- self.operr = OperationError(w_type, w_value, w_tb)
+##class W_OperationError(W_Root):
+## """
+## Tiny applevel wrapper around an OperationError.
+## """
+##
+## def __init__(self, operr):
+## self.operr = operr
+##
+## def descr_reduce(self, space):
+## from pypy.interpreter.mixedmodule import MixedModule
+## w_mod = space.getbuiltinmodule('_pickle_support')
+## mod = space.interp_w(MixedModule, w_mod)
+## w_new_inst = mod.get('operationerror_new')
+## w_args = space.newtuple([])
+## operr = self.operr
+## if operr is None:
+## return space.newtuple([w_new_inst, w_args])
+## w_state = space.newtuple([operr.w_type, operr.get_w_value(space),
+## operr.get_traceback()])
+## return space.newtuple([w_new_inst, w_args, w_state])
+##
+## def descr_setstate(self, space, w_state):
+## w_type, w_value, w_tb = space.fixedview(w_state, 3)
+## self.operr = OperationError(w_type, w_value, w_tb)
def source_as_str(space, w_source, funcname, what, flags):
"""Return source code as str0 with adjusted compiler flags
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
@@ -87,6 +87,40 @@
sys.settrace(None)
# assert did not crash
+ def test_f_lineno_set_2(self):
+ counter = [0]
+ errors = []
+
+ def tracer(f, event, *args):
+ if event == 'line':
+ counter[0] += 1
+ if counter[0] == 2:
+ try:
+ f.f_lineno += 2
+ except ValueError as e:
+ errors.append(e)
+ return tracer
+
+ # obscure: call open beforehand, py3k's open invokes some app
+ # level code that confuses our tracing (likely due to the
+ # testing env, otherwise it's not a problem)
+ f = open(self.tempfile1, 'w')
+ def function():
+ try:
+ raise ValueError
+ except ValueError:
+ x = 42
+ return x
+
+ import sys
+ sys.settrace(tracer)
+ x = function()
+ sys.settrace(None)
+ assert x == 42
+ assert len(errors) == 1
+ assert str(errors[0]).startswith(
+ "can't jump into or out of an 'expect' or 'finally' block")
+
def test_f_lineno_set_firstline(self):
r"""
seen = []
@@ -148,30 +182,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_raise.py b/pypy/interpreter/test/test_raise.py
--- a/pypy/interpreter/test/test_raise.py
+++ b/pypy/interpreter/test/test_raise.py
@@ -79,6 +79,25 @@
assert sys.exc_info()[0] is ValueError
assert sys.exc_info() == (None, None, None)
+ def test_revert_exc_info_2_finally(self):
+ import sys
+ assert sys.exc_info() == (None, None, None)
+ try:
+ try:
+ raise ValueError
+ finally:
+ try:
+ try:
+ raise IndexError
+ finally:
+ assert sys.exc_info()[0] is IndexError
+ except IndexError:
+ pass
+ assert sys.exc_info()[0] is ValueError
+ except ValueError:
+ pass
+ assert sys.exc_info() == (None, None, None)
+
def test_reraise_1(self):
raises(IndexError, """
import sys
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
@@ -464,7 +464,7 @@
from pypy.interpreter.eval import Code
from pypy.interpreter.pycode import PyCode, CO_VARARGS, CO_VARKEYWORDS
from pypy.interpreter.pyframe import PyFrame
-from pypy.interpreter.pyopcode import SuspendedUnroller, W_OperationError
+from pypy.interpreter.pyopcode import SuspendedUnroller
from pypy.interpreter.module import Module
from pypy.interpreter.function import (Function, Method, StaticMethod,
ClassMethod, BuiltinFunction, descr_function_get)
@@ -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),
@@ -616,9 +616,6 @@
f_lasti = GetSetProperty(PyFrame.fget_f_lasti),
f_trace = GetSetProperty(PyFrame.fget_f_trace, PyFrame.fset_f_trace,
PyFrame.fdel_f_trace),
- f_exc_type = GetSetProperty(PyFrame.fget_f_exc_type),
- f_exc_value = GetSetProperty(PyFrame.fget_f_exc_value),
- f_exc_traceback = GetSetProperty(PyFrame.fget_f_exc_traceback),
f_restricted = GetSetProperty(PyFrame.fget_f_restricted),
f_code = GetSetProperty(PyFrame.fget_code),
f_locals = GetSetProperty(PyFrame.fget_getdictscope),
@@ -787,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,
@@ -813,8 +810,8 @@
Coroutine.typedef = TypeDef("coroutine",
__repr__ = interp2app(Coroutine.descr__repr__),
- __reduce__ = interp2app(Coroutine.descr__reduce__),
- __setstate__ = interp2app(Coroutine.descr__setstate__),
+ #__reduce__ = interp2app(Coroutine.descr__reduce__),
+ #__setstate__ = interp2app(Coroutine.descr__setstate__),
send = interp2app(Coroutine.descr_send,
descrmismatch='send'),
throw = interp2app(Coroutine.descr_throw,
@@ -878,8 +875,8 @@
SuspendedUnroller.typedef = TypeDef("SuspendedUnroller")
SuspendedUnroller.typedef.acceptable_as_base_class = False
-W_OperationError.typedef = TypeDef("OperationError",
- __reduce__ = interp2app(W_OperationError.descr_reduce),
- __setstate__ = interp2app(W_OperationError.descr_setstate),
-)
-W_OperationError.typedef.acceptable_as_base_class = False
+## W_OperationError.typedef = TypeDef("OperationError",
+## __reduce__ = interp2app(W_OperationError.descr_reduce),
+## __setstate__ = interp2app(W_OperationError.descr_setstate),
+## )
+## W_OperationError.typedef.acceptable_as_base_class = False
diff --git a/pypy/module/_pickle_support/__init__.py b/pypy/module/_pickle_support/__init__.py
--- a/pypy/module/_pickle_support/__init__.py
+++ b/pypy/module/_pickle_support/__init__.py
@@ -22,5 +22,4 @@
'intrangeiter_new': 'maker.intrangeiter_new',
'builtin_code': 'maker.builtin_code',
'builtin_function' : 'maker.builtin_function',
- 'operationerror_new': 'maker.operationerror_new',
}
diff --git a/pypy/module/_pickle_support/maker.py b/pypy/module/_pickle_support/maker.py
--- a/pypy/module/_pickle_support/maker.py
+++ b/pypy/module/_pickle_support/maker.py
@@ -69,10 +69,6 @@
new_iter = W_IntRangeIterator(space, current, remaining, step)
return space.wrap(new_iter)
-def operationerror_new(space):
- from pypy.interpreter.pyopcode import W_OperationError
- return W_OperationError(None)
-
@unwrap_spec(identifier=str)
def builtin_code(space, identifier):
from pypy.interpreter import gateway
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()."""
diff --git a/pypy/tool/pytest/apptest.py b/pypy/tool/pytest/apptest.py
--- a/pypy/tool/pytest/apptest.py
+++ b/pypy/tool/pytest/apptest.py
@@ -172,7 +172,8 @@
f.write('\n'.join(defs))
f.write('def %s():\n' % target_name)
f.write('\n'.join(source))
- f.write("\n%s()\n" % target_name)
+ f.write("\ntry:\n %s()\n" % target_name)
+ f.write('finally:\n print("===aefwuiheawiu===")')
helper_dir = os.path.join(pypydir, 'tool', 'cpyext')
env = os.environ.copy()
env['PYTHONPATH'] = helper_dir
@@ -186,6 +187,8 @@
(python_, usemodules))
elif res > 0:
raise AssertionError("Subprocess failed:\n" + stderr)
+ elif "===aefwuiheawiu===" not in stdout:
+ raise AssertionError("%r crashed:\n%s" % (python_, stderr))
def extract_docstring_if_empty_function(fn):
@@ -209,6 +212,7 @@
def execute_appex(self, space, target, *args):
self.space = space
+ space.getexecutioncontext().set_sys_exc_info(None)
try:
target(*args)
except OperationError as e:
More information about the pypy-commit
mailing list