Author: Armin Rigo <[email protected]>
Branch: py3.6
Changeset: r97598:29268d8eba51
Date: 2019-09-24 14:40 +0200
http://bitbucket.org/pypy/pypy/changeset/29268d8eba51/

Log:    Update logic to CPython 3.6.9 for setting f_lineno. Issue #3066 is
        not resolved; I guess CPython doesn't do the same dead code
        elimination

diff --git a/pypy/interpreter/executioncontext.py 
b/pypy/interpreter/executioncontext.py
--- a/pypy/interpreter/executioncontext.py
+++ b/pypy/interpreter/executioncontext.py
@@ -329,8 +329,11 @@
                 # if it does not exist yet and the tracer accesses it via
                 # frame.f_locals, it is filled by PyFrame.getdictscope
                 frame.fast2locals()
+            prev_line_tracing = d.is_in_line_tracing
             self.is_tracing += 1
             try:
+                if event == 'line':
+                    d.is_in_line_tracing = True
                 try:
                     w_result = space.call_function(w_callback, frame, 
space.newtext(event), w_arg)
                     if space.is_w(w_result, space.w_None):
@@ -345,6 +348,7 @@
                     raise
             finally:
                 self.is_tracing -= 1
+                d.is_in_line_tracing = prev_line_tracing
                 if d.w_locals is not None:
                     frame.locals2fast()
 
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -23,7 +23,7 @@
 
 # Define some opcodes used
 for op in '''DUP_TOP POP_TOP SETUP_LOOP SETUP_EXCEPT SETUP_FINALLY SETUP_WITH
-SETUP_ASYNC_WITH POP_BLOCK END_FINALLY'''.split():
+SETUP_ASYNC_WITH POP_BLOCK END_FINALLY WITH_CLEANUP_START'''.split():
     globals()[op] = stdlib_opcode.opmap[op]
 
 class FrameDebugData(object):
@@ -35,6 +35,7 @@
     instr_prev_plus_one      = 0
     f_lineno                 = 0      # current lineno for tracing
     is_being_profiled        = False
+    is_in_line_tracing       = False
     w_locals                 = None
     hidden_operationerr      = None
 
@@ -667,20 +668,30 @@
         except OperationError:
             raise oefmt(space.w_ValueError, "lineno must be an integer")
 
+        # You can only do this from within a trace function, not via
+        # _getframe or similar hackery.
         if self.get_w_f_trace() is None:
             raise oefmt(space.w_ValueError,
                         "f_lineno can only be set by a trace function.")
 
+        # Only allow jumps when we're tracing a line event.
+        d = self.getorcreatedebug()
+        if not d.is_in_line_tracing:
+            raise oefmt(space.w_ValueError,
+                        "can only jump from a 'line' trace event")
+
         line = self.pycode.co_firstlineno
         if new_lineno < line:
             raise oefmt(space.w_ValueError,
-                        "line %d comes before the current code.", new_lineno)
+                        "line %d comes before the current code block", 
new_lineno)
         elif new_lineno == line:
             new_lasti = 0
         else:
+            # Find the bytecode offset for the start of the given
+            # line, or the first code-owning line after it.
+            lnotab = self.pycode.co_lnotab
+            addr = 0
             new_lasti = -1
-            addr = 0
-            lnotab = self.pycode.co_lnotab
             for offset in xrange(0, len(lnotab), 2):
                 addr += ord(lnotab[offset])
                 line_offset = ord(lnotab[offset + 1])
@@ -692,23 +703,47 @@
                     new_lineno = line
                     break
 
+        # If we didn't reach the requested line, return an error.
         if new_lasti == -1:
             raise oefmt(space.w_ValueError,
-                        "line %d comes after the current code.", new_lineno)
+                        "line %d comes after the current code block", 
new_lineno)
 
-        # Don't jump to a line with an except in it.
+        min_addr = min(new_lasti, self.last_instr)
+        max_addr = max(new_lasti, self.last_instr)
+
+        # You can't jump onto a line with an 'except' statement on it -
+        # they expect to have an exception on the top of the stack, which
+        # won't be true if you jump to them.  They always start with code
+        # that either pops the exception using POP_TOP (plain 'except:'
+        # lines do this) or duplicates the exception on the stack using
+        # DUP_TOP (if there's an exception type specified).  See compile.c,
+        # 'com_try_except' for the full details.  There aren't any other
+        # cases (AFAIK) where a line's code can start with DUP_TOP or
+        # POP_TOP, but if any ever appear, they'll be subject to the same
+        # restriction (but with a different error message).
         code = self.pycode.co_code
         if ord(code[new_lasti]) in (DUP_TOP, POP_TOP):
             raise oefmt(space.w_ValueError,
                         "can't jump to 'except' line as there's no exception")
 
-        # Don't jump inside or out of an except or a finally block.
-        # Note that CPython doesn't check except blocks,
-        # but that results in crashes (tested on 3.5.2+).
-        f_lasti_handler_addr = -1
-        new_lasti_handler_addr = -1
+        # You can't jump into or out of a 'finally' block because the 'try'
+        # block leaves something on the stack for the END_FINALLY to clean
+        # up.      So we walk the bytecode, maintaining a simulated blockstack.
+        # When we reach the old or new address and it's in a 'finally' block
+        # we note the address of the corresponding SETUP_FINALLY.  The jump
+        # is only legal if neither address is in a 'finally' block or
+        # they're both in the same one.  'blockstack' is a stack of the
+        # bytecode addresses of the SETUP_X opcodes, and 'in_finally' tracks
+        # whether we're in a 'finally' block at each blockstack level.
+
+        # PYPY NOTE: CPython (at least 3.5.2+) doesn't check except blocks,
+        # but that results in crashes.  But for now I'm going to follow the
+        # logic of CPython 3.6.9 exactly anyway, which is quite messy already.
+
+        f_lasti_setup_addr = -1
+        new_lasti_setup_addr = -1
         blockstack = []     # current blockstack (addresses of SETUP_*)
-        endblock = [-1]     # current finally/except block stack
+        in_finally = []     # list of flags "is this a finally block?"
         addr = 0
         while addr < len(code):
             assert addr & 1 == 0
@@ -716,47 +751,69 @@
             if op in (SETUP_LOOP, SETUP_EXCEPT, SETUP_FINALLY, SETUP_WITH,
                       SETUP_ASYNC_WITH):
                 blockstack.append(addr)
+                in_finally.append(False)
             elif op == POP_BLOCK:
                 if len(blockstack) == 0:
                     raise oefmt(space.w_SystemError,
                            "POP_BLOCK not properly nested in this bytecode")
-                setup_op = ord(code[blockstack.pop()])
-                if setup_op != SETUP_LOOP:
-                    endblock.append(addr)
+                setup_op = ord(code[blockstack[-1]])
+                if setup_op in (SETUP_FINALLY, SETUP_WITH, SETUP_ASYNC_WITH):
+                    in_finally[-1] = True
+                else:
+                    del blockstack[-1]
             elif op == END_FINALLY:
-                if len(endblock) <= 1:
-                    raise oefmt(space.w_SystemError,
-                           "END_FINALLY not properly nested in this bytecode")
-                endblock.pop()
+                # Ignore END_FINALLYs for SETUP_EXCEPTs - they exist
+                # in the bytecode but don't correspond to an actual
+                # 'finally' block.  (If len(blockstack) is 0, we must
+                # be seeing such an END_FINALLY.)
+                if len(blockstack) > 0:
+                    setup_op = ord(code[blockstack[-1]])
+                    if setup_op in (SETUP_FINALLY, SETUP_WITH, 
SETUP_ASYNC_WITH):
+                        del blockstack[-1]
 
-            if addr == new_lasti:
-                new_lasti_handler_addr = endblock[-1]
-            if addr == self.last_instr:
-                f_lasti_handler_addr = endblock[-1]
+            # For the addresses we're interested in, see whether they're
+            # within a 'finally' block and if so, remember the address
+            # of the SETUP_FINALLY.
+            if addr == new_lasti or addr == self.last_instr:
+                setup_addr = -1
+                for i in xrange(len(blockstack) - 1, -1, -1):
+                    if in_finally[i]:
+                        setup_addr = blockstack[i]
+                        break
 
+                if setup_addr != -1:
+                    if addr == new_lasti:
+                        new_lasti_setup_addr = setup_addr
+                    if addr == self.last_instr:
+                        f_lasti_setup_addr = setup_addr
             addr += 2
 
-        if len(blockstack) != 0 or len(endblock) != 1:
+        # Verify that the blockstack tracking code didn't get lost.
+        if len(blockstack) != 0:
             raise oefmt(space.w_SystemError,
                         "blocks not properly nested in this bytecode")
 
-        if new_lasti_handler_addr != f_lasti_handler_addr:
+        # After all that, are we jumping into / out of a 'finally' block?
+        if new_lasti_setup_addr != f_lasti_setup_addr:
             raise oefmt(space.w_ValueError,
-                        "can't jump into or out of an 'expect' or "
-                        "'finally' block (%d -> %d)",
-                        f_lasti_handler_addr, new_lasti_handler_addr)
+                        "can't jump into or out of a 'finally' block "
+                        "(%d -> %d)",
+                        f_lasti_setup_addr, new_lasti_setup_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
-        else:
-            min_addr = self.last_instr
-            max_addr = new_lasti
 
-        delta_iblock = min_delta_iblock = 0    # see below for comment
+        # Police block-jumping (you can't jump into the middle of a block)
+        # and ensure that the blockstack finishes up in a sensible state (by
+        # popping any blocks we're jumping out of).  We look at all the
+        # blockstack operations between the current position and the new
+        # one, and keep track of how many blocks we drop out of on the way.
+        # By also keeping track of the lowest blockstack position we see, we
+        # can tell whether the jump goes into any blocks without coming out
+        # again - in that case we raise an exception below.
+
+        delta_iblock = min_delta_iblock = 0
         addr = min_addr
         while addr < max_addr:
             assert addr & 1 == 0
@@ -767,29 +824,33 @@
                 delta_iblock += 1
             elif op == POP_BLOCK:
                 delta_iblock -= 1
-                if delta_iblock < min_delta_iblock:
-                    min_delta_iblock = delta_iblock
 
+            min_delta_iblock = min(min_delta_iblock, delta_iblock)
             addr += 2
 
         # '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:
-            go_iblock = delta_iblock
+            go_iblock = delta_iblock        # Forwards jump.
         else:
-            go_iblock = -delta_iblock
+            go_iblock = -delta_iblock       # Backwards jump.
 
         if go_iblock > min_delta_iblock:
             raise oefmt(space.w_ValueError,
                         "can't jump into the middle of a block")
         assert go_iblock <= 0
 
+        # Pop any blocks that we're jumping out of.
+        from pypy.interpreter.pyopcode import FinallyBlock
         for ii in range(-go_iblock):
             block = self.pop_block()
             block.cleanupstack(self)
+            if (isinstance(block, FinallyBlock) and
+                ord(code[block.handlerposition]) == WITH_CLEANUP_START):
+                self.popvalue()     # Pop the exit function.
 
-        self.getorcreatedebug().f_lineno = new_lineno
+        d.f_lineno = new_lineno
         assert new_lasti & 1 == 0
         self.last_instr = new_lasti
 
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to