Commits

Armin Rigo committed 98a69d0

Add an interface get_traceback()/set_traceback() on OperationError.
The frame is marked as escaping only if we call get_traceback(),
or if the exception escapes the current frame.

Comments (0)

Files changed (13)

pypy/interpreter/error.py

     """Interpreter-level exception that signals an exception that should be
     sent to the application level.
 
-    OperationError instances have three public attributes (and no .args),
-    w_type, w_value and application_traceback, which contain the wrapped
+    OperationError instances have three attributes (and no .args),
+    w_type, _w_value and _application_traceback, which contain the wrapped
     type and value describing the exception, and a chained list of
     PyTraceback objects making the application-level traceback.
     """
 
+    _attrs_ = ['w_type', '_w_value', '_application_traceback']
     _w_value = None
-    application_traceback = None
+    _application_traceback = None
 
     def __init__(self, w_type, w_value, tb=None):
         if not we_are_translated() and w_type is None:
             raise FlowingError(w_value)
         self.setup(w_type)
         self._w_value = w_value
-        self.application_traceback = tb
+        self._application_traceback = tb
 
     def setup(self, w_type):
         self.w_type = w_type
         # for sys.exc_clear()
         self.w_type = space.w_None
         self._w_value = space.w_None
-        self.application_traceback = None
+        self._application_traceback = None
         if not we_are_translated():
             del self.debug_excs[:]
 
 
     def print_app_tb_only(self, file):
         "NOT_RPYTHON"
-        tb = self.application_traceback
+        tb = self._application_traceback
         if tb:
             import linecache
             print >> file, "Traceback (application-level):"
     def _compute_value(self):
         raise NotImplementedError
 
+    def get_traceback(self):
+        """Calling this marks the PyTraceback as escaped, i.e. it becomes
+        accessible and inspectable by app-level Python code.  For the JIT.
+        Note that this has no effect if there are already several traceback
+        frames recorded, because in this case they are already marked as
+        escaping by executioncontext.leave() being called with
+        got_exception=True.
+        """
+        if self._application_traceback is not None:
+            self._application_traceback.frame.mark_as_escaped()
+        return self._application_traceback
+
+    def set_traceback(self, traceback):
+        """Set the current traceback.  It should either be a traceback
+        pointing to some already-escaped frame, or a traceback for the
+        current frame.  To support the latter case we do not mark the
+        frame as escaped.  The idea is that it will be marked as escaping
+        only if the exception really propagates out of this frame, by
+        executioncontext.leave() being called with got_exception=True.
+        """
+        self._application_traceback = traceback
+
 # ____________________________________________________________
 # optimization only: avoid the slowest operation -- the string
 # formatting with '%' -- in the common case were we don't

pypy/interpreter/executioncontext.py

         frame.f_backref = self.topframeref
         self.topframeref = jit.virtual_ref(frame)
 
-    def leave(self, frame, w_exitvalue):
+    def leave(self, frame, w_exitvalue, got_exception):
         try:
             if self.profilefunc:
                 self._trace(frame, 'leaveframe', w_exitvalue)
         finally:
             frame_vref = self.topframeref
             self.topframeref = frame.f_backref
-            if frame.escaped:
+            if frame.escaped or got_exception:
                 # if this frame escaped to applevel, we must ensure that also
                 # f_back does
                 f_back = frame.f_backref()
                 if f_back:
-                    f_back.escaped = True
+                    f_back.mark_as_escaped()
                 # force the frame (from the JIT point of view), so that it can
                 # be accessed also later
                 frame_vref()
             if operr is not None:
                 w_value = operr.get_w_value(space)
                 w_arg = space.newtuple([operr.w_type, w_value,
-                                     space.wrap(operr.application_traceback)])
+                                     space.wrap(operr.get_traceback())])
 
             frame.fast2locals()
             self.is_tracing += 1

pypy/interpreter/main.py

         operationerr.normalize_exception(space)
         w_type = operationerr.w_type
         w_value = operationerr.get_w_value(space)
-        w_traceback = space.wrap(operationerr.application_traceback)
+        w_traceback = space.wrap(operationerr.get_traceback())
 
         # for debugging convenience we also insert the exception into
         # the interpreter-level sys.last_xxx

pypy/interpreter/pyframe.py

         Must be called on frames that are exposed to applevel, e.g. by
         sys._getframe().  This ensures that the virtualref holding the frame
         is properly forced by ec.leave(), and thus the frame will be still
-        accessible even after the corresponding C stack is died.
+        accessible even after the corresponding C stack died.
         """
         self.escaped = True
 
                 not self.space.config.translating)
         executioncontext = self.space.getexecutioncontext()
         executioncontext.enter(self)
+        got_exception = True
         w_exitvalue = self.space.w_None
         try:
             executioncontext.call_trace(self)
             # clean up the exception, might be useful for not
             # allocating exception objects in some cases
             self.last_exception = None
+            got_exception = False
         finally:
-            executioncontext.leave(self, w_exitvalue)
+            executioncontext.leave(self, w_exitvalue, got_exception)
         return w_exitvalue
     execute_frame.insert_stack_check_here = True
 
             w_tb = space.w_None
         else:
             w_exc_value = self.last_exception.get_w_value(space)
-            w_tb = w(self.last_exception.application_traceback)
+            w_tb = w(self.last_exception.get_traceback())
         
         tup_state = [
             w(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.application_traceback)
+                return space.wrap(f.last_exception.get_traceback())
         return space.w_None
          
     def fget_f_restricted(self, space):

pypy/interpreter/pyopcode.py

         else:
             msg = "raise: arg 3 must be a traceback or None"
             tb = pytraceback.check_traceback(space, w_traceback, msg)
-            operror.application_traceback = tb
+            operror.set_traceback(tb)
             # special 3-arguments raise, no new traceback obj will be attached
             raise RaiseWithExplicitTraceback(operror)
 
                       isinstance(unroller, SApplicationException))
         if is_app_exc:
             operr = unroller.operr
-            w_traceback = self.space.wrap(operr.application_traceback)
+            w_traceback = self.space.wrap(operr.get_traceback())
             w_suppress = self.call_contextmanager_exit_function(
                 w_exitfunc,
                 operr.w_type,

pypy/interpreter/pytraceback.py

 def record_application_traceback(space, operror, frame, last_instruction):
     if frame.pycode.hidden_applevel:
         return
-    tb = operror.application_traceback
+    tb = operror.get_traceback()
     tb = PyTraceback(space, frame, last_instruction, tb)
-    operror.application_traceback = tb
+    operror.set_traceback(tb)
 
 def offset2lineno(c, stopat):
     tab = c.co_lnotab

pypy/module/_rawffi/callback.py

             unwrap_value(space, push_elem, ll_res, 0,
                          callback_ptr.result, w_res)
     except OperationError, e:
-        tbprint(space, space.wrap(e.application_traceback),
+        tbprint(space, space.wrap(e.get_traceback()),
                 space.wrap(e.errorstr(space)))
         # force the result to be zero
         if callback_ptr.result is not None:

pypy/module/_stackless/interp_coroutine.py

         if isinstance(operror, OperationError):
             w_exctype = operror.w_type
             w_excvalue = operror.get_w_value(space)
-            w_exctraceback = operror.application_traceback
+            w_exctraceback = operror.get_traceback()
             w_excinfo = space.newtuple([w_exctype, w_excvalue, w_exctraceback])
             
             if w_exctype is self.costate.w_CoroutineExit:
                 space.gettypeobject(pytraceback.PyTraceback.typedef))):
                 raise OperationError(space.w_TypeError,
                       space.wrap("throw: arg 3 must be a traceback or None"))
-            operror.application_traceback = tb
+            operror.set_traceback(tb)
         
         self._kill(operror)
 

pypy/module/_stackless/interp_greenlet.py

                 space.gettypeobject(pytraceback.PyTraceback.typedef))):
                 raise OperationError(space.w_TypeError,
                       space.wrap("throw: arg 3 must be a traceback or None"))
-            operror.application_traceback = tb
+            operror.set_traceback(tb)
         # Dead greenlet: turn GreenletExit into a regular return
         if self.isdead() and operror.match(space, self.costate.w_GreenletExit):
             args_w = [operror.get_w_value(space)]

pypy/module/cpyext/pyerrors.py

     if operror:
         ptype[0] = make_ref(space, operror.w_type)
         pvalue[0] = make_ref(space, operror.get_w_value(space))
-        ptraceback[0] = make_ref(space, space.wrap(operror.application_traceback))
+        ptraceback[0] = make_ref(space, space.wrap(operror.get_traceback()))
     else:
         ptype[0] = lltype.nullptr(PyObject.TO)
         pvalue[0] = lltype.nullptr(PyObject.TO)
 
     w_type = operror.w_type
     w_value = operror.get_w_value(space)
-    w_tb = space.wrap(operror.application_traceback)
+    w_tb = space.wrap(operror.get_traceback())
 
     if rffi.cast(lltype.Signed, set_sys_last_vars):
         space.sys.setdictvalue(space, "last_type", w_type)

pypy/module/sys/__init__.py

             if operror is None:
                 return space.w_None
             else:
-                return space.wrap(operror.application_traceback)
+                return space.wrap(operror.get_traceback())
         return None 
 
     def get_w_default_encoder(self):

pypy/module/sys/vm.py

     if operror is None:
         return space.newtuple([space.w_None,space.w_None,space.w_None])
     else:
-        operror.application_traceback.frame.mark_as_escaped()
         return space.newtuple([operror.w_type, operror.get_w_value(space),
-                               space.wrap(operror.application_traceback)])
+                               space.wrap(operror.get_traceback())])
 
 def exc_clear(space):
     """Clear global information on the current exception.  Subsequent calls

pypy/tool/pytest/appsupport.py

         self.space = space
         self.operr = operr
         self.typename = operr.w_type.getname(space, "?")
-        self.traceback = AppTraceback(space, self.operr.application_traceback)
+        self.traceback = AppTraceback(space, self.operr.get_traceback())
         debug_excs = getattr(operr, 'debug_excs', [])
         if debug_excs:
             self._excinfo = debug_excs[0]