This is Richie Hindle's patch
[ 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.
This commit is contained in:
parent
f680cc460c
commit
cfd3884882
|
@ -255,6 +255,16 @@ Continue execution until the current function returns.
|
||||||
|
|
||||||
Continue execution, only stop when a breakpoint is encountered.
|
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}}}]
|
\item[l(ist) \optional{\var{first\optional{, last}}}]
|
||||||
|
|
||||||
List source code for the current file. Without arguments, list 11
|
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
|
#Print instance variables in self
|
||||||
alias ps pi self
|
alias ps pi self
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
|
|
||||||
\item[unalias \var{name}]
|
\item[unalias \var{name}]
|
||||||
|
|
||||||
Deletes the specified alias.
|
Deletes the specified alias.
|
||||||
|
|
|
@ -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;
|
variables; \member{f_globals} is used for global variables;
|
||||||
\member{f_builtins} is used for built-in (intrinsic) names;
|
\member{f_builtins} is used for built-in (intrinsic) names;
|
||||||
\member{f_restricted} is a flag indicating whether the function is
|
\member{f_restricted} is a flag indicating whether the function is
|
||||||
executing in restricted execution mode;
|
executing in restricted execution mode; \member{f_lasti} gives the
|
||||||
\member{f_lineno} gives the line number and \member{f_lasti} gives the
|
|
||||||
precise instruction (this is an index into the bytecode string of
|
precise instruction (this is an index into the bytecode string of
|
||||||
the code object).
|
the code object).
|
||||||
\withsubitem{(frame attribute)}{
|
\withsubitem{(frame attribute)}{
|
||||||
|
@ -821,7 +820,6 @@ the code object).
|
||||||
\ttindex{f_code}
|
\ttindex{f_code}
|
||||||
\ttindex{f_globals}
|
\ttindex{f_globals}
|
||||||
\ttindex{f_locals}
|
\ttindex{f_locals}
|
||||||
\ttindex{f_lineno}
|
|
||||||
\ttindex{f_lasti}
|
\ttindex{f_lasti}
|
||||||
\ttindex{f_builtins}
|
\ttindex{f_builtins}
|
||||||
\ttindex{f_restricted}}
|
\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
|
function called at the start of each source code line (this is used by
|
||||||
the debugger); \member{f_exc_type}, \member{f_exc_value},
|
the debugger); \member{f_exc_type}, \member{f_exc_value},
|
||||||
\member{f_exc_traceback} represent the most recent exception caught in
|
\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)}{
|
\withsubitem{(frame attribute)}{
|
||||||
\ttindex{f_trace}
|
\ttindex{f_trace}
|
||||||
\ttindex{f_exc_type}
|
\ttindex{f_exc_type}
|
||||||
\ttindex{f_exc_value}
|
\ttindex{f_exc_value}
|
||||||
\ttindex{f_exc_traceback}}
|
\ttindex{f_exc_traceback}
|
||||||
|
\ttindex{f_lineno}}
|
||||||
|
|
||||||
\item[Traceback objects] \label{traceback}
|
\item[Traceback objects] \label{traceback}
|
||||||
Traceback objects represent a stack trace of an exception. A
|
Traceback objects represent a stack trace of an exception. A
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
% MacOS framework-related changes (section of its own, probably)
|
% MacOS framework-related changes (section of its own, probably)
|
||||||
|
|
||||||
|
% the new set-next-statement functionality of pdb (SF #643835)
|
||||||
|
|
||||||
%\section{Introduction \label{intro}}
|
%\section{Introduction \label{intro}}
|
||||||
|
|
||||||
{\large This article is a draft, and is currently up to date for some
|
{\large This article is a draft, and is currently up to date for some
|
||||||
|
@ -1201,13 +1203,13 @@ For example:
|
||||||
|
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
>>> days = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'St', 'Sn']
|
>>> 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']
|
['St', 'Sn', 'Th']
|
||||||
>>> random.sample(days, 7) # Choose 7 elements
|
>>> random.sample(days, 7) # Choose 7 elements
|
||||||
['Tu', 'Th', 'Mo', 'We', 'St', 'Fr', 'Sn']
|
['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']
|
['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):
|
Traceback (most recent call last):
|
||||||
File "<stdin>", line 1, in ?
|
File "<stdin>", line 1, in ?
|
||||||
File "random.py", line 414, in sample
|
File "random.py", line 414, in sample
|
||||||
|
|
26
Lib/pdb.py
26
Lib/pdb.py
|
@ -506,6 +506,25 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
||||||
return 1
|
return 1
|
||||||
do_c = do_cont = do_continue
|
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):
|
def do_quit(self, arg):
|
||||||
self.set_quit()
|
self.set_quit()
|
||||||
return 1
|
return 1
|
||||||
|
@ -805,6 +824,13 @@ Continue execution until the current function returns."""
|
||||||
print """c(ont(inue))
|
print """c(ont(inue))
|
||||||
Continue execution, only stop when a breakpoint is encountered."""
|
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):
|
def help_list(self):
|
||||||
self.help_l()
|
self.help_l()
|
||||||
|
|
||||||
|
|
|
@ -221,9 +221,298 @@ class RaisingTraceFuncTestCase(unittest.TestCase):
|
||||||
def test_exception(self):
|
def test_exception(self):
|
||||||
self.run_test_for_event('exception')
|
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():
|
def test_main():
|
||||||
test_support.run_unittest(TraceTestCase)
|
test_support.run_unittest(TraceTestCase)
|
||||||
test_support.run_unittest(RaisingTraceFuncTestCase)
|
test_support.run_unittest(RaisingTraceFuncTestCase)
|
||||||
|
test_support.run_unittest(JumpTestCase)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_main()
|
test_main()
|
||||||
|
|
11
Misc/NEWS
11
Misc/NEWS
|
@ -84,6 +84,10 @@ Type/class unification and new-style classes
|
||||||
Core and builtins
|
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
|
- The _codecs support module for codecs.py was turned into a builtin
|
||||||
module to assure that at least the builtin codecs are available
|
module to assure that at least the builtin codecs are available
|
||||||
to the Python parser for source code decoding according to PEP 263.
|
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
|
- 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
|
call the trace function. C code that accessed f_lineno should call
|
||||||
PyCode_Addr2Line instead (f_lineno is still there, but not kept up
|
PyCode_Addr2Line instead (f_lineno is still there, but only kept up
|
||||||
to date).
|
to date when there is a trace function set).
|
||||||
|
|
||||||
- There's a new warning category, FutureWarning. This is used to warn
|
- 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
|
about a number of situations where the value or sign of an integer
|
||||||
|
@ -439,6 +443,9 @@ Extension modules
|
||||||
Library
|
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
|
- The distutils created windows installers now can run a
|
||||||
postinstallation script.
|
postinstallation script.
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
#include "opcode.h"
|
#include "opcode.h"
|
||||||
#include "structmember.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)
|
#define OFF(x) offsetof(PyFrameObject, x)
|
||||||
|
|
||||||
static PyMemberDef frame_memberlist[] = {
|
static PyMemberDef frame_memberlist[] = {
|
||||||
|
@ -44,6 +47,260 @@ frame_getlineno(PyFrameObject *f, void *closure)
|
||||||
return PyInt_FromLong(lineno);
|
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 *
|
static PyObject *
|
||||||
frame_gettrace(PyFrameObject *f, void *closure)
|
frame_gettrace(PyFrameObject *f, void *closure)
|
||||||
{
|
{
|
||||||
|
@ -77,7 +334,8 @@ frame_settrace(PyFrameObject *f, PyObject* v, void *closure)
|
||||||
|
|
||||||
static PyGetSetDef frame_getsetlist[] = {
|
static PyGetSetDef frame_getsetlist[] = {
|
||||||
{"f_locals", (getter)frame_getlocals, NULL, NULL},
|
{"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},
|
{"f_trace", (getter)frame_gettrace, (setter)frame_settrace, NULL},
|
||||||
{0}
|
{0}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue