]> granicus.if.org Git - python/commitdiff
This is Richie Hindle's patch
authorMichael W. Hudson <mwh@python.net>
Tue, 17 Dec 2002 16:15:34 +0000 (16:15 +0000)
committerMichael W. Hudson <mwh@python.net>
Tue, 17 Dec 2002 16:15:34 +0000 (16:15 +0000)
[ 643835 ] Set Next Statement for Python debuggers

with a few tweaks by me: adding an unsigned or two, mentioning that
not all jumps are allowed in the doc for pdb, adding a NEWS item and
a note to whatsnew, and AuCTeX doing something cosmetic to libpdb.tex.

Doc/lib/libpdb.tex
Doc/ref/ref3.tex
Doc/whatsnew/whatsnew23.tex
Lib/pdb.py
Lib/test/test_trace.py
Misc/NEWS
Objects/frameobject.c

index f8417b8b92787f625c3db5c77b613dfd54388e97..bf779fe730a41970008c599093bf8878a747f6ae 100644 (file)
@@ -255,6 +255,16 @@ Continue execution until the current function returns.
 
 Continue execution, only stop when a breakpoint is encountered.
 
+\item[j(ump) \var{lineno}]
+
+Set the next line that will be executed.  Only available in the
+bottom-most frame.  This lets you jump back and execute code
+again, or jump forward to skip code that you don't want to run.
+
+It should be noted that not all jumps are allowed -- for instance it
+it not possible to jump into the middle of a for loop or out of a
+finally clause.
+
 \item[l(ist) \optional{\var{first\optional{, last}}}]
 
 List source code for the current file.  Without arguments, list 11
@@ -303,7 +313,7 @@ alias pi for k in %1.__dict__.keys(): print "%1.",k,"=",%1.__dict__[k]
 #Print instance variables in self
 alias ps pi self
 \end{verbatim}
-               
+                
 \item[unalias \var{name}]
 
 Deletes the specified alias.
index eb1d658ef3d7953fe847c88618c512c758f9b84a..605ed55a89d25bddd7bfb4614ccfd7ecb96d52a0 100644 (file)
@@ -812,8 +812,7 @@ frame; \member{f_locals} is the dictionary used to look up local
 variables; \member{f_globals} is used for global variables;
 \member{f_builtins} is used for built-in (intrinsic) names;
 \member{f_restricted} is a flag indicating whether the function is
-executing in restricted execution mode;
-\member{f_lineno} gives the line number and \member{f_lasti} gives the
+executing in restricted execution mode; \member{f_lasti} gives the
 precise instruction (this is an index into the bytecode string of
 the code object).
 \withsubitem{(frame attribute)}{
@@ -821,7 +820,6 @@ the code object).
   \ttindex{f_code}
   \ttindex{f_globals}
   \ttindex{f_locals}
-  \ttindex{f_lineno}
   \ttindex{f_lasti}
   \ttindex{f_builtins}
   \ttindex{f_restricted}}
@@ -830,12 +828,16 @@ Special writable attributes: \member{f_trace}, if not \code{None}, is a
 function called at the start of each source code line (this is used by
 the debugger); \member{f_exc_type}, \member{f_exc_value},
 \member{f_exc_traceback} represent the most recent exception caught in
-this frame.
+this frame; \member{f_lineno} is the current line number of the frame
+--- writing to this from within a trace function jumps to the given line
+(only for the bottom-most frame).  A debugger can implement a Jump
+command (aka Set Next Statement) by writing to f_lineno.
 \withsubitem{(frame attribute)}{
   \ttindex{f_trace}
   \ttindex{f_exc_type}
   \ttindex{f_exc_value}
-  \ttindex{f_exc_traceback}}
+  \ttindex{f_exc_traceback}
+  \ttindex{f_lineno}}
 
 \item[Traceback objects] \label{traceback}
 Traceback objects represent a stack trace of an exception.  A
index 39b7c70ba572ff2ee0054618b4e44bc7dc7be18c..ee8d644f75d9552ed8abcec2c9d6027dd5d81ecd 100644 (file)
@@ -12,6 +12,8 @@
 
 % MacOS framework-related changes (section of its own, probably)
 
+% the new set-next-statement functionality of pdb (SF #643835)
+
 %\section{Introduction \label{intro}}
 
 {\large This article is a draft, and is currently up to date for some
@@ -1201,13 +1203,13 @@ For example:
 
 \begin{verbatim}
 >>> days = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'St', 'Sn']
->>> random.sample(days, 3)     # Choose 3 elements
+>>> random.sample(days, 3)      # Choose 3 elements
 ['St', 'Sn', 'Th']
->>> random.sample(days, 7)     # Choose 7 elements
+>>> random.sample(days, 7)      # Choose 7 elements
 ['Tu', 'Th', 'Mo', 'We', 'St', 'Fr', 'Sn']
->>> random.sample(days, 7)     # Choose 7 again
+>>> random.sample(days, 7)      # Choose 7 again
 ['We', 'Mo', 'Sn', 'Fr', 'Tu', 'St', 'Th']
->>> random.sample(days, 8)     # Can't choose eight
+>>> random.sample(days, 8)      # Can't choose eight
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "random.py", line 414, in sample
index 94518aebf65e5e15882c7b6d7d4d56213579266d..fffd0ad3b8ff6fd660ebdef54d75e0eb8d0f8c50 100755 (executable)
@@ -506,6 +506,25 @@ class Pdb(bdb.Bdb, cmd.Cmd):
         return 1
     do_c = do_cont = do_continue
 
+    def do_jump(self, arg):
+        if self.curindex + 1 != len(self.stack):
+            print "*** You can only jump within the bottom frame"
+            return
+        try:
+            arg = int(arg)
+        except ValueError:
+            print "*** The 'jump' command requires a line number."
+        else:
+            try:
+                # Do the jump, fix up our copy of the stack, and display the
+                # new position
+                self.curframe.f_lineno = arg
+                self.stack[self.curindex] = self.stack[self.curindex][0], arg
+                self.print_stack_entry(self.stack[self.curindex])
+            except ValueError, e:
+                print '*** Jump failed:', e
+    do_j = do_jump
+
     def do_quit(self, arg):
         self.set_quit()
         return 1
@@ -805,6 +824,13 @@ Continue execution until the current function returns."""
         print """c(ont(inue))
 Continue execution, only stop when a breakpoint is encountered."""
 
+    def help_jump(self):
+        self.help_j()
+
+    def help_j(self):
+        print """j(ump) lineno
+Set the next line that will be executed."""
+
     def help_list(self):
         self.help_l()
 
index faee713b410f748e7344b48375d7574646c49a50..f973a1939350983c64dd4b8701a8895517801d2c 100644 (file)
@@ -221,9 +221,298 @@ class RaisingTraceFuncTestCase(unittest.TestCase):
     def test_exception(self):
         self.run_test_for_event('exception')
 
+
+# 'Jump' tests: assigning to frame.f_lineno within a trace function
+# moves the execution position - it's how debuggers implement a Jump
+# command (aka. "Set next statement").
+
+class JumpTracer:
+    """Defines a trace function that jumps from one place to another,
+    with the source and destination lines of the jump being defined by
+    the 'jump' property of the function under test."""
+
+    def __init__(self, function):
+        self.function = function
+        self.jumpFrom = function.jump[0]
+        self.jumpTo = function.jump[1]
+        self.done = False
+
+    def trace(self, frame, event, arg):
+        if not self.done and frame.f_code == self.function.func_code:
+            firstLine = frame.f_code.co_firstlineno
+            if frame.f_lineno == firstLine + self.jumpFrom:
+                # Cope with non-integer self.jumpTo (because of
+                # no_jump_to_non_integers below).
+                try:
+                    frame.f_lineno = firstLine + self.jumpTo
+                except TypeError:
+                    frame.f_lineno = self.jumpTo
+                self.done = True
+        return self.trace
+
+# The first set of 'jump' tests are for things that are allowed:
+
+def jump_simple_forwards(output):
+    output.append(1)
+    output.append(2)
+    output.append(3)
+
+jump_simple_forwards.jump = (1, 3)
+jump_simple_forwards.output = [3]
+
+def jump_simple_backwards(output):
+    output.append(1)
+    output.append(2)
+
+jump_simple_backwards.jump = (2, 1)
+jump_simple_backwards.output = [1, 1, 2]
+
+def jump_out_of_block_forwards(output):
+    for i in 1, 2:
+        output.append(2)
+        for j in [3]:  # Also tests jumping over a block
+            output.append(4)
+    output.append(5)
+
+jump_out_of_block_forwards.jump = (3, 5)
+jump_out_of_block_forwards.output = [2, 5]
+
+def jump_out_of_block_backwards(output):
+    output.append(1)
+    for i in [1]:
+        output.append(3)
+        for j in [2]:  # Also tests jumping over a block
+            output.append(5)
+        output.append(6)
+    output.append(7)
+
+jump_out_of_block_backwards.jump = (6, 1)
+jump_out_of_block_backwards.output = [1, 3, 5, 1, 3, 5, 6, 7]
+
+def jump_to_codeless_line(output):
+    output.append(1)
+    # Jumping to this line should skip to the next one.
+    output.append(3)
+
+jump_to_codeless_line.jump = (1, 2)
+jump_to_codeless_line.output = [3]
+
+def jump_to_same_line(output):
+    output.append(1)
+    output.append(2)
+    output.append(3)
+
+jump_to_same_line.jump = (2, 2)
+jump_to_same_line.output = [1, 2, 3]
+
+# Tests jumping within a finally block, and over one.
+def jump_in_nested_finally(output):
+    try:
+        output.append(2)
+    finally:
+        output.append(4)
+        try:
+            output.append(6)
+        finally:
+            output.append(8)
+        output.append(9)
+
+jump_in_nested_finally.jump = (4, 9)
+jump_in_nested_finally.output = [2, 9]
+
+# The second set of 'jump' tests are for things that are not allowed:
+
+def no_jump_too_far_forwards(output):
+    try:
+        output.append(2)
+        output.append(3)
+    except ValueError, e:
+        output.append('after' in str(e))
+
+no_jump_too_far_forwards.jump = (3, 6)
+no_jump_too_far_forwards.output = [2, True]
+
+def no_jump_too_far_backwards(output):
+    try:
+        output.append(2)
+        output.append(3)
+    except ValueError, e:
+        output.append('before' in str(e))
+
+no_jump_too_far_backwards.jump = (3, -1)
+no_jump_too_far_backwards.output = [2, True]
+
+# Test each kind of 'except' line.
+def no_jump_to_except_1(output):
+    try:
+        output.append(2)
+    except:
+        e = sys.exc_info()[1]
+        output.append('except' in str(e))
+
+no_jump_to_except_1.jump = (2, 3)
+no_jump_to_except_1.output = [True]
+
+def no_jump_to_except_2(output):
+    try:
+        output.append(2)
+    except ValueError:
+        e = sys.exc_info()[1]
+        output.append('except' in str(e))
+
+no_jump_to_except_2.jump = (2, 3)
+no_jump_to_except_2.output = [True]
+
+def no_jump_to_except_3(output):
+    try:
+        output.append(2)
+    except ValueError, e:
+        output.append('except' in str(e))
+
+no_jump_to_except_3.jump = (2, 3)
+no_jump_to_except_3.output = [True]
+
+def no_jump_to_except_4(output):
+    try:
+        output.append(2)
+    except (ValueError, RuntimeError), e:
+        output.append('except' in str(e))
+
+no_jump_to_except_4.jump = (2, 3)
+no_jump_to_except_4.output = [True]
+
+def no_jump_forwards_into_block(output):
+    try:
+        output.append(2)
+        for i in 1, 2:
+            output.append(4)
+    except ValueError, e:
+        output.append('into' in str(e))
+
+no_jump_forwards_into_block.jump = (2, 4)
+no_jump_forwards_into_block.output = [True]
+
+def no_jump_backwards_into_block(output):
+    try:
+        for i in 1, 2:
+            output.append(3)
+        output.append(4)
+    except ValueError, e:
+        output.append('into' in str(e))
+
+no_jump_backwards_into_block.jump = (4, 3)
+no_jump_backwards_into_block.output = [3, 3, True]
+
+def no_jump_into_finally_block(output):
+    try:
+        try:
+            output.append(3)
+            x = 1
+        finally:
+            output.append(6)
+    except ValueError, e:
+        output.append('finally' in str(e))
+
+no_jump_into_finally_block.jump = (4, 6)
+no_jump_into_finally_block.output = [3, 6, True]  # The 'finally' still runs
+
+def no_jump_out_of_finally_block(output):
+    try:
+        try:
+            output.append(3)
+        finally:
+            output.append(5)
+            output.append(6)
+    except ValueError, e:
+        output.append('finally' in str(e))
+
+no_jump_out_of_finally_block.jump = (5, 1)
+no_jump_out_of_finally_block.output = [3, True]
+
+# This verifies the line-numbers-must-be-integers rule.
+def no_jump_to_non_integers(output):
+    try:
+        output.append(2)
+    except ValueError, e:
+        output.append('integer' in str(e))
+
+no_jump_to_non_integers.jump = (2, "Spam")
+no_jump_to_non_integers.output = [True]
+
+# This verifies that you can't set f_lineno via _getframe or similar
+# trickery.
+def no_jump_without_trace_function():
+    try:
+        previous_frame = sys._getframe().f_back
+        previous_frame.f_lineno = previous_frame.f_lineno
+    except ValueError, e:
+        # This is the exception we wanted; make sure the error message
+        # talks about trace functions.
+        if 'trace' not in str(e):
+            raise
+    else:
+        # Something's wrong - the expected exception wasn't raised.
+        raise RuntimeError, "Trace-function-less jump failed to fail"
+
+
+class JumpTestCase(unittest.TestCase):
+    def compare_jump_output(self, expected, received):
+        if received != expected:
+            self.fail( "Outputs don't match:\n" +
+                       "Expected: " + repr(expected) + "\n" +
+                       "Received: " + repr(received))
+
+    def run_test(self, func):
+        tracer = JumpTracer(func)
+        sys.settrace(tracer.trace)
+        output = []
+        func(output)
+        sys.settrace(None)
+        self.compare_jump_output(func.output, output)
+
+    def test_01_jump_simple_forwards(self):
+        self.run_test(jump_simple_forwards)
+    def test_02_jump_simple_backwards(self):
+        self.run_test(jump_simple_backwards)
+    def test_03_jump_out_of_block_forwards(self):
+        self.run_test(jump_out_of_block_forwards)
+    def test_04_jump_out_of_block_backwards(self):
+        self.run_test(jump_out_of_block_backwards)
+    def test_05_jump_to_codeless_line(self):
+        self.run_test(jump_to_codeless_line)
+    def test_06_jump_to_same_line(self):
+        self.run_test(jump_to_same_line)
+    def test_07_jump_in_nested_finally(self):
+        self.run_test(jump_in_nested_finally)
+    def test_08_no_jump_too_far_forwards(self):
+        self.run_test(no_jump_too_far_forwards)
+    def test_09_no_jump_too_far_backwards(self):
+        self.run_test(no_jump_too_far_backwards)
+    def test_10_no_jump_to_except_1(self):
+        self.run_test(no_jump_to_except_1)
+    def test_11_no_jump_to_except_2(self):
+        self.run_test(no_jump_to_except_2)
+    def test_12_no_jump_to_except_3(self):
+        self.run_test(no_jump_to_except_3)
+    def test_13_no_jump_to_except_4(self):
+        self.run_test(no_jump_to_except_4)
+    def test_14_no_jump_forwards_into_block(self):
+        self.run_test(no_jump_forwards_into_block)
+    def test_15_no_jump_backwards_into_block(self):
+        self.run_test(no_jump_backwards_into_block)
+    def test_16_no_jump_into_finally_block(self):
+        self.run_test(no_jump_into_finally_block)
+    def test_17_no_jump_out_of_finally_block(self):
+        self.run_test(no_jump_out_of_finally_block)
+    def test_18_no_jump_to_non_integers(self):
+        self.run_test(no_jump_to_non_integers)
+    def test_19_no_jump_without_trace_function(self):
+        no_jump_without_trace_function()
+
 def test_main():
     test_support.run_unittest(TraceTestCase)
     test_support.run_unittest(RaisingTraceFuncTestCase)
+    test_support.run_unittest(JumpTestCase)
 
 if __name__ == "__main__":
     test_main()
index 6292ba5ff0603d9d124a68999a15c89026cf0bfc..efd8048f7f5db0c95bbe71d2b73cea916f5e4d05 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -84,6 +84,10 @@ Type/class unification and new-style classes
 Core and builtins
 -----------------
 
+- A frame object's f_lineno attribute can now be written to from a
+  trace function to change which line will execute next.  A command to
+  exploit this from pdb has been added.  [SF patch #643835]
+
 - The _codecs support module for codecs.py was turned into a builtin
   module to assure that at least the builtin codecs are available
   to the Python parser for source code decoding according to PEP 263.
@@ -118,8 +122,8 @@ Core and builtins
 
 - SET_LINENO is gone.  co_lnotab is now consulted to determine when to
   call the trace function.  C code that accessed f_lineno should call
-  PyCode_Addr2Line instead (f_lineno is still there, but not kept up
-  to date).
+  PyCode_Addr2Line instead (f_lineno is still there, but only kept up
+  to date when there is a trace function set).
 
 - There's a new warning category, FutureWarning.  This is used to warn
   about a number of situations where the value or sign of an integer
@@ -439,6 +443,9 @@ Extension modules
 Library
 -------
 
+- pdb has a new 'j(ump)' command to select the next line to be
+  executed.
+
 - The distutils created windows installers now can run a
   postinstallation script.
 
index 3036ab684d939ba68d28a4f33691869430f90a18..b7b30214dfd56e3446532d97fe1d511426eeee37 100644 (file)
@@ -8,6 +8,9 @@
 #include "opcode.h"
 #include "structmember.h"
 
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
 #define OFF(x) offsetof(PyFrameObject, x)
 
 static PyMemberDef frame_memberlist[] = {
@@ -44,6 +47,260 @@ frame_getlineno(PyFrameObject *f, void *closure)
        return PyInt_FromLong(lineno);
 }
 
+/* Setter for f_lineno - you can set f_lineno from within a trace function in
+ * order to jump to a given line of code, subject to some restrictions.  Most
+ * lines are OK to jump to because they don't make any assumptions about the
+ * state of the stack (obvious because you could remove the line and the code
+ * would still work without any stack errors), but there are some constructs
+ * that limit jumping:
+ *
+ *  o Lines with an 'except' statement on them can't be jumped to, because
+ *    they expect an exception to be on the top of the stack.
+ *  o Lines that live in a 'finally' block can't be jumped from or to, since
+ *    the END_FINALLY expects to clean up the stack after the 'try' block.
+ *  o 'try'/'for'/'while' blocks can't be jumped into because the blockstack
+ *    needs to be set up before their code runs, and for 'for' loops the
+ *    iterator needs to be on the stack.
+ */
+static int
+frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
+{
+       int new_lineno = 0;             /* The new value of f_lineno */
+       int new_lasti = 0;              /* The new value of f_lasti */
+       int new_iblock = 0;             /* The new value of f_iblock */
+       char *code = NULL;              /* The bytecode for the frame... */
+       int code_len = 0;               /* ...and its length */
+       char *lnotab = NULL;            /* Iterating over co_lnotab */
+       int lnotab_len = 0;             /* (ditto) */
+       int offset = 0;                 /* (ditto) */
+       int line = 0;                   /* (ditto) */
+       int addr = 0;                   /* (ditto) */
+       int min_addr = 0;               /* Scanning the SETUPs and POPs */
+       int max_addr = 0;               /* (ditto) */
+       int delta_iblock = 0;           /* (ditto) */
+       int min_delta_iblock = 0;       /* (ditto) */
+       int min_iblock = 0;             /* (ditto) */
+       int f_lasti_setup_addr = 0;     /* Policing no-jump-into-finally */
+       int new_lasti_setup_addr = 0;   /* (ditto) */
+       int blockstack[CO_MAXBLOCKS];   /* Walking the 'finally' blocks */
+       int in_finally[CO_MAXBLOCKS];   /* (ditto) */
+       int blockstack_top = 0;         /* (ditto) */
+       int setup_op = 0;               /* (ditto) */
+
+       /* f_lineno must be an integer. */
+       if (!PyInt_Check(p_new_lineno)) {
+               PyErr_SetString(PyExc_ValueError,
+                               "lineno must be an integer");
+               return -1;
+       }
+
+       /* You can only do this from within a trace function, not via
+        * _getframe or similar hackery. */
+       if (!f->f_trace)
+       {
+               PyErr_Format(PyExc_ValueError,
+                            "f_lineno can only be set by a trace function");
+               return -1;
+       }
+
+       /* Fail if the line comes before the start of the code block. */
+       new_lineno = (int) PyInt_AsLong(p_new_lineno);
+       if (new_lineno < f->f_code->co_firstlineno) {
+               PyErr_Format(PyExc_ValueError,
+                            "line %d comes before the current code block",
+                            new_lineno);
+               return -1;
+       }
+
+       /* Find the bytecode offset for the start of the given line, or the
+        * first code-owning line after it. */
+       PyString_AsStringAndSize(f->f_code->co_lnotab, &lnotab, &lnotab_len);
+       addr = 0;
+       line = f->f_code->co_firstlineno;
+       new_lasti = -1;
+       for (offset = 0; offset < lnotab_len; offset += 2) {
+               addr += lnotab[offset];
+               line += lnotab[offset+1];
+               if (line >= new_lineno) {
+                       new_lasti = addr;
+                       new_lineno = line;
+                       break;
+               }
+       }
+
+       /* If we didn't reach the requested line, return an error. */
+       if (new_lasti == -1) {
+               PyErr_Format(PyExc_ValueError,
+                            "line %d comes after the current code block",
+                            new_lineno);
+               return -1;
+       }
+
+       /* We're now ready to look at the bytecode. */
+       PyString_AsStringAndSize(f->f_code->co_code, &code, &code_len);
+       min_addr = MIN(new_lasti, f->f_lasti);
+       max_addr = MAX(new_lasti, f->f_lasti);
+
+       /* 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). */
+       if (code[new_lasti] == DUP_TOP || code[new_lasti] == POP_TOP) {
+               PyErr_SetString(PyExc_ValueError,
+                   "can't jump to 'except' line as there's no exception");
+               return -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. */
+       f_lasti_setup_addr = -1;
+       new_lasti_setup_addr = -1;
+       memset(blockstack, '\0', sizeof(blockstack));
+       memset(in_finally, '\0', sizeof(in_finally));
+       blockstack_top = 0;
+       for (addr = 0; addr < code_len; addr++) {
+               unsigned char op = code[addr];
+               switch (op) {
+               case SETUP_LOOP:
+               case SETUP_EXCEPT:
+               case SETUP_FINALLY:
+                       blockstack[blockstack_top++] = addr;
+                       in_finally[blockstack_top-1] = 0;
+                       break;
+
+               case POP_BLOCK:
+                       setup_op = code[blockstack[blockstack_top-1]];
+                       if (setup_op == SETUP_FINALLY) {
+                               in_finally[blockstack_top-1] = 1;
+                       }
+                       else {
+                               blockstack_top--;
+                       }
+                       break;
+
+               case END_FINALLY:
+                       /* Ignore END_FINALLYs for SETUP_EXCEPTs - they exist
+                        * in the bytecode but don't correspond to an actual
+                        * 'finally' block. */
+                       setup_op = code[blockstack[blockstack_top-1]];
+                       if (setup_op == SETUP_FINALLY) {
+                               blockstack_top--;
+                       }
+                       break;
+               }
+
+               /* 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 || addr == f->f_lasti) {
+                       int i = 0;
+                       int setup_addr = -1;
+                       for (i = blockstack_top-1; i >= 0; i--) {
+                               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 == f->f_lasti) {
+                                       f_lasti_setup_addr = setup_addr;
+                               }
+                       }
+               }
+
+               if (op >= HAVE_ARGUMENT) {
+                       addr += 2;
+               }
+       }
+
+       if (new_lasti_setup_addr != f_lasti_setup_addr) {
+               PyErr_SetString(PyExc_ValueError,
+                           "can't jump into or out of a 'finally' block");
+               return -1;
+       }
+
+
+       /* 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 = 0;
+       for (addr = min_addr; addr < max_addr; addr++) {
+               unsigned char op = code[addr];
+               switch (op) {
+               case SETUP_LOOP:
+               case SETUP_EXCEPT:
+               case SETUP_FINALLY:
+                       delta_iblock++;
+                       break;
+
+               case POP_BLOCK:
+                       delta_iblock--;
+                       break;
+               }
+
+               min_delta_iblock = MIN(min_delta_iblock, delta_iblock);
+
+               if (op >= HAVE_ARGUMENT) {
+                       addr += 2;
+               }
+       }
+
+       /* Derive the absolute iblock values from the deltas. */
+       min_iblock = f->f_iblock + min_delta_iblock;
+       if (new_lasti > f->f_lasti) {
+               /* Forwards jump. */
+               new_iblock = f->f_iblock + delta_iblock;
+       }
+       else {
+               /* Backwards jump. */
+               new_iblock = f->f_iblock - delta_iblock;
+       }
+
+       /* Are we jumping into a block? */
+       if (new_iblock > min_iblock) {
+               PyErr_SetString(PyExc_ValueError,
+                               "can't jump into the middle of a block");
+               return -1;
+       }
+
+       /* Pop any blocks that we're jumping out of. */
+       while (f->f_iblock > new_iblock) {
+               PyTryBlock *b = &f->f_blockstack[--f->f_iblock];
+               while ((f->f_stacktop - f->f_valuestack) > b->b_level) {
+                       PyObject *v = (*--f->f_stacktop);
+                       Py_DECREF(v);
+               }
+       }
+
+       /* Finally set the new f_lineno and f_lasti and return OK. */
+       f->f_lineno = new_lineno;
+       f->f_lasti = new_lasti;
+       return 0;
+}
+
 static PyObject *
 frame_gettrace(PyFrameObject *f, void *closure)
 {
@@ -77,7 +334,8 @@ frame_settrace(PyFrameObject *f, PyObject* v, void *closure)
 
 static PyGetSetDef frame_getsetlist[] = {
        {"f_locals",    (getter)frame_getlocals, NULL, NULL},
-       {"f_lineno",    (getter)frame_getlineno, NULL, NULL},
+       {"f_lineno",    (getter)frame_getlineno,
+                       (setter)frame_setlineno, NULL},
        {"f_trace",     (getter)frame_gettrace, (setter)frame_settrace, NULL},
        {0}
 };