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

Reply via email to