Author: Armin Rigo <[email protected]>
Branch:
Changeset: r88183:b05f65fe2cc4
Date: 2016-11-07 16:47 +0100
http://bitbucket.org/pypy/pypy/changeset/b05f65fe2cc4/
Log: hg merge rpython-error-to-systemerror
Any uncaught RPython exception is turned into an app-level
SystemError. The RPython traceback is also printed, but only up to
the first Python frame. The rest is normally printed as a regular
app-level traceback for the SystemError. This should improve a lot
the live of users hitting an uncaught RPython error.
diff --git a/pypy/interpreter/error.py b/pypy/interpreter/error.py
--- a/pypy/interpreter/error.py
+++ b/pypy/interpreter/error.py
@@ -58,10 +58,14 @@
def __str__(self):
"NOT_RPYTHON: Convenience for tracebacks."
s = self._w_value
- if self.__class__ is not OperationError and s is None:
- space = getattr(self.w_type, 'space')
- if space is not None:
+ space = getattr(self.w_type, 'space', None)
+ if space is not None:
+ if self.__class__ is not OperationError and s is None:
s = self._compute_value(space)
+ try:
+ s = space.str_w(s)
+ except Exception:
+ pass
return '[%s: %s]' % (self.w_type, s)
def errorstr(self, space, use_repr=False):
diff --git a/pypy/interpreter/gateway.py b/pypy/interpreter/gateway.py
--- a/pypy/interpreter/gateway.py
+++ b/pypy/interpreter/gateway.py
@@ -712,6 +712,8 @@
if not we_are_translated():
raise
raise e
+ except OperationError:
+ raise
except KeyboardInterrupt:
raise OperationError(space.w_KeyboardInterrupt, space.w_None)
except MemoryError:
@@ -722,6 +724,14 @@
"maximum recursion depth exceeded")
except RuntimeError: # not on top of py.py
raise OperationError(space.w_RuntimeError, space.w_None)
+ except Exception as e: # general fall-back
+ if we_are_translated():
+ from rpython.rlib.debug import debug_print_traceback
+ debug_print_traceback()
+ # propagate the exception anyway, which will be turned
+ # into a proper OperationError(SystemError) when we
+ # reach PyFrame.execute_frame()
+ raise
# (verbose) performance hack below
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -1,6 +1,7 @@
""" PyFrame class implementation with the interpreter main loop.
"""
+import sys
from rpython.rlib import jit
from rpython.rlib.debug import make_sure_not_resized, check_nonneg
from rpython.rlib.jit import hint
@@ -276,8 +277,13 @@
next_instr = r_uint(self.last_instr + 1)
if next_instr != 0:
self.pushvalue(w_inputvalue)
- w_exitvalue = self.dispatch(self.pycode, next_instr,
- executioncontext)
+ try:
+ w_exitvalue = self.dispatch(self.pycode, next_instr,
+ executioncontext)
+ except OperationError:
+ raise
+ except Exception as e: # general fall-back
+ raise self._convert_unexpected_exception(e)
finally:
executioncontext.return_trace(self, w_exitvalue)
# it used to say self.last_exception = None
@@ -883,6 +889,34 @@
frame = frame.f_backref()
return None
+ def _convert_unexpected_exception_extra(self, e):
+ "NOT_RPYTHON"
+ if e.__class__.__name__ in (
+ 'Skipped', # list of exception class names that are ok
+ ): # to get during ==untranslated tests== only
+ raise
+ # include the RPython-level traceback
+ exc = sys.exc_info()
+ import traceback, cStringIO
+ f = cStringIO.StringIO()
+ print >> f, "\nTraceback (interpreter-level):"
+ traceback.print_tb(exc[2], file=f)
+ return f.getvalue()
+
+ def _convert_unexpected_exception(self, e):
+ if we_are_translated():
+ from rpython.rlib.debug import debug_print_traceback
+ debug_print_traceback()
+ extra = '; internal traceback(s) were dumped to stderr'
+ else:
+ extra = self._convert_unexpected_exception_extra(e)
+ operr = OperationError(self.space.w_SystemError, self.space.wrap(
+ "unexpected internal exception (please report a bug): %r%s" %
+ (e, extra)))
+ pytraceback.record_application_traceback(
+ self.space, operr, self, self.last_instr)
+ raise operr
+
# ____________________________________________________________
def get_block_class(opname):
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
@@ -1,8 +1,10 @@
import py
from pypy.interpreter import executioncontext
+from pypy.interpreter.error import OperationError
-class Finished(Exception):
- pass
+class Finished(OperationError):
+ def __init__(self):
+ OperationError.__init__(self, "exception_class", "exception_value")
class TestExecutionContext:
diff --git a/pypy/interpreter/test/test_gateway.py
b/pypy/interpreter/test/test_gateway.py
--- a/pypy/interpreter/test/test_gateway.py
+++ b/pypy/interpreter/test/test_gateway.py
@@ -4,6 +4,7 @@
from pypy.interpreter import gateway, argument
from pypy.interpreter.gateway import ObjSpace, W_Root, WrappedDefault
from pypy.interpreter.signature import Signature
+from pypy.interpreter.error import OperationError
import py
import sys
@@ -771,6 +772,21 @@
w_g = space.wrap(gateway.interp2app_temp(g, doc='bar'))
assert space.unwrap(space.getattr(w_g, space.wrap('__doc__'))) == 'bar'
+ def test_system_error(self):
+ class UnexpectedException(Exception):
+ pass
+ space = self.space
+ def g(space):
+ raise UnexpectedException
+ w_g = space.wrap(gateway.interp2app_temp(g))
+ e = py.test.raises(OperationError, space.appexec, [w_g], """(my_g):
+ my_g()
+ """)
+ err = str(e.value)
+ assert 'SystemError' in err
+ assert ('unexpected internal exception (please '
+ 'report a bug): UnexpectedException') in err
+
class AppTestPyTestMark:
@py.test.mark.unlikely_to_exist
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
@@ -86,7 +86,9 @@
return space.w_None
return space.get(w_descr, w_obj)
-def do_what_I_mean(space):
+def do_what_I_mean(space, w_crash=None):
+ if not space.is_none(w_crash):
+ raise ValueError # RPython-level, uncaught
return space.wrap(42)
diff --git a/pypy/module/__pypy__/test/test_special.py
b/pypy/module/__pypy__/test/test_special.py
--- a/pypy/module/__pypy__/test/test_special.py
+++ b/pypy/module/__pypy__/test/test_special.py
@@ -92,6 +92,7 @@
from __pypy__ import do_what_I_mean
x = do_what_I_mean()
assert x == 42
+ raises(SystemError, do_what_I_mean, 1)
def test_list_strategy(self):
from __pypy__ import strategy
diff --git a/rpython/rlib/debug.py b/rpython/rlib/debug.py
--- a/rpython/rlib/debug.py
+++ b/rpython/rlib/debug.py
@@ -11,7 +11,7 @@
# Expose these here (public interface)
from rpython.rtyper.debug import (
- ll_assert, FatalError, fatalerror, fatalerror_notb)
+ ll_assert, FatalError, fatalerror, fatalerror_notb, debug_print_traceback)
class DebugLog(list):
diff --git a/rpython/rlib/test/test_debug.py b/rpython/rlib/test/test_debug.py
--- a/rpython/rlib/test/test_debug.py
+++ b/rpython/rlib/test/test_debug.py
@@ -118,3 +118,30 @@
finally:
debug._log = None
assert dlog == [("mycat", [('debug_print', 'foo', 2, 'bar', 3)])]
+
+
+def test_debug_print_traceback():
+ from rpython.translator.c.test.test_genc import compile
+ from rpython.rtyper.lltypesystem import lltype
+ from rpython.rtyper.lltypesystem.lloperation import llop
+
+ def ggg(n):
+ if n < 10:
+ ggg(n + 1)
+ else:
+ raise ValueError
+ def recovery():
+ llop.debug_print_traceback(lltype.Void)
+ recovery._dont_inline_ = True
+ def fff():
+ try:
+ ggg(0)
+ except:
+ recovery()
+
+ fn = compile(fff, [], return_stderr=True)
+ stderr = fn()
+ assert 'RPython traceback:\n' in stderr
+ assert stderr.count('entry_point') == 1
+ assert stderr.count('ggg') == 11
+ assert stderr.count('recovery') == 0
diff --git a/rpython/rtyper/debug.py b/rpython/rtyper/debug.py
--- a/rpython/rtyper/debug.py
+++ b/rpython/rtyper/debug.py
@@ -45,3 +45,12 @@
fatalerror_notb._dont_inline_ = True
fatalerror_notb._jit_look_inside_ = False
fatalerror_notb._annenforceargs_ = [str]
+
+def debug_print_traceback():
+ # print to stderr the RPython traceback of the last caught exception,
+ # but without interrupting the program
+ from rpython.rtyper.lltypesystem import lltype
+ from rpython.rtyper.lltypesystem.lloperation import llop
+ llop.debug_print_traceback(lltype.Void)
+debug_print_traceback._dont_inline_ = True
+debug_print_traceback._jit_look_inside_ = False
diff --git a/rpython/translator/c/test/test_genc.py
b/rpython/translator/c/test/test_genc.py
--- a/rpython/translator/c/test/test_genc.py
+++ b/rpython/translator/c/test/test_genc.py
@@ -53,7 +53,8 @@
unsigned_ffffffff)
def compile(fn, argtypes, view=False, gcpolicy="none", backendopt=True,
- annotatorpolicy=None, thread=False, **kwds):
+ annotatorpolicy=None, thread=False,
+ return_stderr=False, **kwds):
argtypes_unroll = unrolling_iterable(enumerate(argtypes))
for argtype in argtypes:
@@ -139,7 +140,8 @@
stdout = t.driver.cbuilder.cmdexec(
" ".join([llrepr_in(arg) for arg in args]),
- expect_crash=(expected_exception_name is not None))
+ expect_crash=(expected_exception_name is not None),
+ err=return_stderr)
#
if expected_exception_name is not None:
stdout, stderr = stdout
@@ -154,6 +156,8 @@
assert lastline == expected or prevline == expected
return None
+ if return_stderr:
+ stdout, stderr = stdout
output(stdout)
stdout, lastline, empty = stdout.rsplit('\n', 2)
assert empty == ''
@@ -168,6 +172,8 @@
else:
assert mallocs - frees in expected_extra_mallocs
#
+ if return_stderr:
+ return stderr
if ll_res in [lltype.Signed, lltype.Unsigned, lltype.SignedLongLong,
lltype.UnsignedLongLong]:
return int(res)
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit