Commits

Armin Rigo  committed 1d77c09

exception handling fixed, it now works well (see test_interpreter.py)

  • Participants
  • Parent commits 1d6a77c

Comments (0)

Files changed (4)

File pypy/interpreter/executioncontext.py

 
     def __init__(self, space):
         self.space = space
-        self.framestack = []
+        self.framestack = Stack()
 
     def eval_frame(self, frame):
         __executioncontext__ = self
-        self.framestack.append(frame)
+        self.framestack.push(frame)
         try:
             result = frame.eval(self)
         finally:
         return result
 
     def get_w_builtins(self):
-        if self.framestack:
-            return self.framestack[-1].w_builtins
+        if self.framestack.empty():
+            return self.space.w_builtins
         else:
-            return self.space.w_builtins
+            return self.framestack.top().w_builtins
 
     def make_standard_w_globals(self):
         "Create a new empty 'globals' dictionary."
         operationerr.record_interpreter_traceback()
         #operationerr.print_detailed_traceback(self.space)
 
+    def sys_exc_info(self):
+        """Implements sys.exc_info().
+        Return an OperationError instance or None."""
+        for i in range(self.framestack.depth()):
+            frame = self.framestack.top(i)
+            if frame.last_exception is not None:
+                return frame.last_exception
+        return None
+
 
 class OperationError(Exception):
     """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 w_traceback, which contain the wrapped type, value
-    and traceback describing the exception."""
+    w_type, w_value and application_traceback, which contain the wrapped
+    type and value describing the exception, and the unwrapped list of
+    (frame, instruction_position) making the application-level traceback.
+    """
 
-    def __init__(self, w_type, w_value, w_traceback=None):
+    def __init__(self, w_type, w_value):
         self.w_type = w_type
         self.w_value = w_value
-        self.w_traceback = w_traceback
+        self.application_traceback = []
         self.debug_tb = None
 
+    def record_application_traceback(self, frame, last_instruction):
+        self.application_traceback.append((frame, last_instruction))
+
     def match(self, space, w_check_class):
         "Check if this application-level exception matches 'w_check_class'."
         return space.is_true(space.exception_match(self.w_type, w_check_class))
         exc_value = space.unwrap(self.w_value)
         return '%s: %s' % (exc_type.__name__, exc_value)
 
+    def getframe(self):
+        "The frame this exception was raised in, or None."
+        if self.application_traceback:
+            frame, last_instruction = self.application_traceback[0]
+            return frame
+        else:
+            return None
+
     def record_interpreter_traceback(self):
         """Records the current traceback inside the interpreter.
         This traceback is only useful to debug the interpreter, not the
         print >> file, self.errorstr(space)
 
     def print_app_tb_only(self, file):
-        tb = self.w_traceback
+        tb = self.application_traceback[:]
         if tb:
             import linecache
-            tb = self.w_traceback[:]
             tb.reverse()
             print >> file, "Traceback (application-level):"
             for f, i in tb:
         self.file.write(data.replace('\n', '\n'+self.prefix))
         if self.linestart:
             self.file.write('\n')
+
+class Stack:
+    """Utility class implementing a stack."""
+
+    def __init__(self):
+        self.items = []
+
+    def push(self, item):
+        self.items.append(item)
+
+    def pop(self):
+        return self.items.pop()
+
+    def top(self, position=0):
+        """'position' is 0 for the top of the stack, 1 for the item below,
+        and so on.  It must not be negative."""
+        return self.items[~position]
+
+    def depth(self):
+        return len(self.items)
+
+    def empty(self):
+        return not self.items

File pypy/interpreter/opcode.py

     else:
         raise pyframe.BytecodeCorruption, "bad RAISE_VARARGS oparg"
     w_type, w_value, w_traceback = f.space.unpacktuple(w_resulttuple)
-    raise OperationError(w_type, w_value, w_traceback)
+    # XXX the three-arguments 'raise' is not supported yet
+    raise OperationError(w_type, w_value)
 
 def LOAD_LOCALS(f):
     f.valuestack.push(f.w_locals)
     block.cleanup(f)  # the block knows how to clean up the value stack
 
 def END_FINALLY(f):
-    # unlike CPython, the information on what to do at the end
-    # of a 'finally' is not stored in the value stack, but in
-    # the block stack, in a new dedicated block type which knows
-    # how to restore the environment (exception, break/continue...)
-    # at the beginning of the 'finally'.
-    block = f.blockstack.pop()
-    block.cleanup(f)
+    # unlike CPython, when we reach this opcode the value stack has
+    # always been set up as follows (topmost first):
+    #   [exception type  or None]
+    #   [exception value or None]
+    #   [wrapped stack unroller ]
+    f.valuestack.pop()   # ignore the exception type
+    f.valuestack.pop()   # ignore the exception value
+    unroller = f.space.unwrap(f.valuestack.pop())
+    if unroller is not None:
+        raise unroller   # re-raise the unroller, if any
 
 def BUILD_CLASS(f):
     w_methodsdict = f.valuestack.pop()

File pypy/interpreter/pyframe.py

 """
 
 import opcode
+from executioncontext import OperationError, Stack
 import baseobjspace
 from appfile import AppFile
 
         self.load_builtins()
         self.valuestack = Stack()
         self.blockstack = Stack()
+        self.last_exception = None
         self.next_instr = 0
-        self.lasti = -1
 
     def eval(self, executioncontext):
         "Interpreter main loop!"
         try:
             while True:
                 try:
+                    last_instr = self.next_instr
                     try:
-                        self.lasti = self.next_instr
                         # fetch and dispatch the next opcode
                         op = self.nextop()
                         if opcode.has_arg(op):
                         else:
                             opcode.dispatch_noarg(self, op)
 
-                    except baseobjspace.OperationError, e:
+                    except OperationError, e:
+                        e.record_application_traceback(self, last_instr)
+                        self.last_exception = e
                         executioncontext.exception_trace(e)
                         # convert an OperationError into a reason to unroll
                         # the stack
-                        if e.w_traceback is None:
-                            e.w_traceback = []
-
-                        e.w_traceback.append((self, self.lasti))
                         raise SApplicationException(e)
                     # XXX some other exceptions could be caught here too,
                     #     like KeyboardInterrupt
             
         except ExitFrame, e:
             # leave that frame
-            w_exitvalue, = e.args
+            w_exitvalue = e.args[0]
             return w_exitvalue
 
     ### accessor functions ###
         w_attrname = self.space.wrap("__dict__")
         try:
             w_builtins = self.space.getattr(w_builtins, w_attrname)
-        except baseobjspace.OperationError, e:
+        except OperationError:
             pass # catch and ignore any error
         self.w_builtins = w_builtins
 
+    ### exception stack ###
+
+    def clean_exceptionstack(self):
+        # remove all exceptions that can no longer be re-raised
+        # because the current valuestack is no longer deep enough
+        # to hold the corresponding information
+        while self.exceptionstack:
+            unroller, valuestackdepth = self.exceptionstack.top()
+            if valuestackdepth <= self.valuestack.depth():
+                break
+            self.exceptionstack.pop()
+
 
 ### Frame Blocks ###
 
 class FrameBlock:
 
-    """Abstract base class for frame blocks from the blockstack."""
-    def cleanup(self, frame):
-        "Clean up a frame when we normally exit the block."
-
-    def unroll(self, frame, unroller):
-        "Clean up a frame when we abnormally exit the block."
-
-class SyntacticBlock(FrameBlock):
-    """Abstract subclass for blocks which are syntactic Python blocks
-    corresponding to the SETUP_XXX / POP_BLOCK opcodes."""
+    """Abstract base class for frame blocks from the blockstack,
+    used by the SETUP_XXX and POP_BLOCK opcodes."""
 
     def __init__(self, frame, handlerposition):
         self.handlerposition = handlerposition
         self.valuestackdepth = frame.valuestack.depth()
 
-    def cleanup(self, frame):
+    def cleanupstack(self, frame):
         for i in range(self.valuestackdepth, frame.valuestack.depth()):
             frame.valuestack.pop()
 
+    def cleanup(self, frame):
+        "Clean up a frame when we normally exit the block."
+        self.cleanupstack(frame)
+
     def unroll(self, frame, unroller):
-        self.cleanup(frame)   # same behavior except in FinallyBlock
+        "Clean up a frame when we abnormally exit the block."
+        self.cleanupstack(frame)
 
-class LoopBlock(SyntacticBlock):
+
+class LoopBlock(FrameBlock):
     """A loop block.  Stores the end-of-loop pointer in case of 'break'."""
 
-class ExceptBlock(SyntacticBlock):
+    def unroll(self, frame, unroller):
+        if isinstance(unroller, SContinueLoop):
+            # re-push the loop block without cleaning up the value stack,
+            # and jump to the beginning of the loop, stored in the
+            # exception's argument
+            frame.blockstack.push(self)
+            jump_to = unroller.args[0]
+            frame.next_instr = jump_to
+            raise StopUnrolling
+        self.cleanupstack(frame)
+        if isinstance(unroller, SBreakLoop):
+            # jump to the end of the loop
+            frame.next_instr = self.handlerposition
+            raise StopUnrolling
+
+
+class ExceptBlock(FrameBlock):
     """An try:except: block.  Stores the position of the exception handler."""
 
-class FinallyBlock(SyntacticBlock):
+    def unroll(self, frame, unroller):
+        self.cleanupstack(frame)
+        if isinstance(unroller, SApplicationException):
+            # push the exception to the value stack for inspection by the
+            # exception handler (the code after the except:)
+            operationerr = unroller.args[0]
+            # the stack setup is slightly different than in CPython:
+            # instead of the traceback, we store the unroller object,
+            # wrapped.
+            frame.valuestack.push(frame.space.wrap(unroller))
+            frame.valuestack.push(operationerr.w_value)
+            frame.valuestack.push(operationerr.w_type)
+            frame.next_instr = self.handlerposition   # jump to the handler
+            raise StopUnrolling
+
+
+class FinallyBlock(FrameBlock):
     """A try:finally: block.  Stores the position of the exception handler."""
 
     def cleanup(self, frame):
-        # upon normal entry into the finally: part, we push on the block stack
-        # a block that says that we entered the finally: with no exception set
-        SyntacticBlock.cleanup(self, frame)
-        frame.blockstack.push(NoExceptionInFinally())
+        # upon normal entry into the finally: part, the standard Python
+        # bytecode pushes a single None for END_FINALLY.  In our case we
+        # always push three values into the stack: the wrapped unroller,
+        # the exception value and the exception type (which are all None
+        # here).
+        self.cleanupstack(frame)
+        # one None already pushed by the bytecode
+        frame.valuestack.push(frame.space.w_None)
+        frame.valuestack.push(frame.space.w_None)
 
     def unroll(self, frame, unroller):
         # any abnormal reason for unrolling a finally: triggers the end of
         # the block unrolling and the entering the finally: handler.
-        block = ExceptionInFinally(unroller)
-        frame.blockstack.push(block)
+        # see comments in cleanup().
+        self.cleanupstack(frame)
+        frame.valuestack.push(frame.space.wrap(unroller))
+        frame.valuestack.push(frame.space.w_None)
+        frame.valuestack.push(frame.space.w_None)
         frame.next_instr = self.handlerposition   # jump to the handler
         raise StopUnrolling
 
-class NoExceptionInFinally(FrameBlock):
-    """When we enter a finally: construct with no exception set."""
-
-class ExceptionInFinally(FrameBlock):
-    """When we enter a finally: construct with a Python exception set."""
-
-    def __init__(self, original_unroller):
-        self.original_unroller = original_unroller
-
-    def cleanup(self, frame):
-        # re-activate the block stack unroller when we normally reach the
-        # end of the finally: handler
-        raise self.original_unroller
-
 
 ### Block Stack unrollers ###
 
             while not frame.blockstack.empty():
                 block = frame.blockstack.pop()
                 block.unroll(frame, self)
-                self.unrolledblock(frame, block)
             self.emptystack(frame)
         except StopUnrolling:
             pass
 class SApplicationException(StackUnroller):
     """Unroll the stack because of an application-level exception
     (i.e. an OperationException)."""
-
-    def unrolledblock(self, frame, block):
-        if isinstance(block, ExceptBlock):
-            # push the exception to the value stack for inspection by the
-            # exception handler (the code after the except:)
-            operationerr = self.args[0]
-            frame.valuestack.push(operationerr.w_traceback)
-            frame.valuestack.push(operationerr.w_value)
-            frame.valuestack.push(operationerr.w_type)
-            frame.next_instr = block.handlerposition   # jump to the handler
-            
-            # XXX
-            # XXX this is broken because we don't always reach the END_FINALLY
-            # XXX
-            frame.blockstack.push(NoExceptionInFinally())
-            
-            raise StopUnrolling
-
     def emptystack(self, frame):
         # propagate the exception to the caller
         operationerr = self.args[0]
 class SBreakLoop(StackUnroller):
     """Signals a 'break' statement."""
 
-    def unrolledblock(self, frame, block):
-        if isinstance(block, LoopBlock):
-            # jump to the end of the loop
-            frame.next_instr = block.handlerposition
-            raise StopUnrolling
-
 class SContinueLoop(StackUnroller):
     """Signals a 'continue' statement.
     Argument is the bytecode position of the beginning of the loop."""
 
-    def unrolledblock(self, frame, block):
-        if isinstance(block, LoopBlock):
-            # re-push the loop block and jump to the beginning of the
-            # loop, stored in the exception's argument
-            frame.blockstack.push(block)
-            jump_to, = self.args
-            frame.next_instr = jump_to
-            raise StopUnrolling
-
 class SReturnValue(StackUnroller):
     """Signals a 'return' statement.
     Argument is the wrapped object to return."""
-
-    def unrolledblock(self, frame, block):
-        pass
-
     def emptystack(self, frame):
         # XXX do something about generators, like throw a NoValue
-        w_returnvalue, = self.args
+        w_returnvalue = self.args[0]
         raise ExitFrame(w_returnvalue)
 
 class SYieldValue(StackUnroller):
     """Signals a 'yield' statement.
     Argument is the wrapped object to return."""
-
     def unrollstack(self, frame):
-        w_yieldedvalue, = self.args
+        w_yieldedvalue = self.args[0]
         raise ExitFrame(w_yieldedvalue)
 
 class StopUnrolling(Exception):
 
 class BytecodeCorruption(ValueError):
     """Detected bytecode corruption.  Never caught; it's an error."""
-
-
-### Utilities ###
-
-class Stack:
-    """Utility class implementing a stack."""
-
-    def __init__(self):
-        self.items = []
-
-    def push(self, item):
-        self.items.append(item)
-
-    def pop(self):
-        return self.items.pop()
-
-    def top(self, position=0):
-        """'position' is 0 for the top of the stack, 1 for the item below,
-        and so on.  It must not be negative."""
-        return self.items[~position]
-
-    def depth(self):
-        return len(self.items)
-
-    def empty(self):
-        return not self.items

File pypy/interpreter/test/test_interpreter.py

     try:
         if a:
             raise Exception
+        a = -12
     finally:
-        return 2
+        return a
 '''
-        self.assertEquals(self.codetest(code, 'f', [0]), 2)
-        self.assertEquals(self.codetest(code, 'f', [1]), 2)
+        self.assertEquals(self.codetest(code, 'f', [0]), -12)
+        self.assertEquals(self.codetest(code, 'f', [1]), 1)
 
     def test_raise(self):
         x = self.codetest('''
 ''', 'f', [])
         self.assertEquals(x, '<<<TypeError: exceptions must be classes, instances, or strings (deprecated), not int>>>')
 
+    def test_except2(self):
+        x = self.codetest('''
+def f():
+    try:
+        z = 0
+        try:
+            "x"+1
+        except TypeError, e:
+            z = 5
+            raise e
+    except TypeError:
+        return z
+''', 'f', [])
+        self.assertEquals(x, 5)
+
+    def test_except3(self):
+        x = self.codetest('''
+def f():
+    z = 0
+    try:
+        "x"+1
+    except TypeError, e:
+        z = 5
+    return z
+''', 'f', [])
+        self.assertEquals(x, 5)
+
+    def test_break(self):
+        code = '''
+def f(n):
+    total = 0
+    for i in range(n):
+        try:
+            if i == 4:
+                break
+        finally:
+            total += i
+    return total
+'''
+        self.assertEquals(self.codetest(code, 'f', [4]), 1+2+3)
+        self.assertEquals(self.codetest(code, 'f', [9]), 1+2+3+4)
+
+    def test_continue(self):
+        code = '''
+def f(n):
+    total = 0
+    for i in range(n):
+        try:
+            if i == 4:
+                continue
+        finally:
+            total += 100
+        total += i
+    return total
+'''
+        self.assertEquals(self.codetest(code, 'f', [4]), 1+2+3+400)
+        self.assertEquals(self.codetest(code, 'f', [9]),
+                          1+2+3 + 5+6+7+8+900)
+
 
 if __name__ == '__main__':
     unittest.main()