From 013783c529cfc459d05d261f5053ad7bf7d88c29 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Tue, 20 Jul 2010 22:37:19 +0000 Subject: move test_trace.py so as not to conflict with future tests for the trace module --- Include/opcode.h | 1 + Include/symtable.h | 1 + Lib/test/test_line_tracing.py | 790 ++++++++++++++++++++++++++++++++++++++++++ Lib/test/test_scope.py | 8 + Lib/test/test_trace.py | 790 ------------------------------------------ Objects/typeobject.c | 1 + Python/ceval.c | 16 +- Python/compile.c | 4 +- Python/symtable.c | 11 +- 9 files changed, 821 insertions(+), 801 deletions(-) create mode 100644 Lib/test/test_line_tracing.py delete mode 100644 Lib/test/test_trace.py diff --git a/Include/opcode.h b/Include/opcode.h index 7ffa359..facadc2 100644 --- a/Include/opcode.h +++ b/Include/opcode.h @@ -123,6 +123,7 @@ extern "C" { #define LOAD_CLOSURE 135 /* Load free variable from closure */ #define LOAD_DEREF 136 /* Load and dereference from closure cell */ #define STORE_DEREF 137 /* Store into cell */ +#define LOAD_NAME_LOCAL_ONLY 138 /* The next 3 opcodes must be contiguous and satisfy (CALL_FUNCTION_VAR - CALL_FUNCTION) & 3 == 1 */ diff --git a/Include/symtable.h b/Include/symtable.h index d5ef96f..22075f0 100644 --- a/Include/symtable.h +++ b/Include/symtable.h @@ -88,6 +88,7 @@ PyAPI_FUNC(void) PySymtable_Free(struct symtable *); #define GLOBAL_IMPLICIT 3 #define FREE 4 #define CELL 5 +#define LOCAL_ONLY 6 /* The following two names are used for the ste_unoptimized bit field */ #define OPT_IMPORT_STAR 1 diff --git a/Lib/test/test_line_tracing.py b/Lib/test/test_line_tracing.py new file mode 100644 index 0000000..43134e9 --- /dev/null +++ b/Lib/test/test_line_tracing.py @@ -0,0 +1,790 @@ +# Testing the line trace facility. + +from test import support +import unittest +import sys +import difflib +import gc + +# A very basic example. If this fails, we're in deep trouble. +def basic(): + return 1 + +basic.events = [(0, 'call'), + (1, 'line'), + (1, 'return')] + +# Many of the tests below are tricky because they involve pass statements. +# If there is implicit control flow around a pass statement (in an except +# clause or else caluse) under what conditions do you set a line number +# following that clause? + + +# The entire "while 0:" statement is optimized away. No code +# exists for it, so the line numbers skip directly from "del x" +# to "x = 1". +def arigo_example(): + x = 1 + del x + while 0: + pass + x = 1 + +arigo_example.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (5, 'line'), + (5, 'return')] + +# check that lines consisting of just one instruction get traced: +def one_instr_line(): + x = 1 + del x + x = 1 + +one_instr_line.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (3, 'return')] + +def no_pop_tops(): # 0 + x = 1 # 1 + for a in range(2): # 2 + if a: # 3 + x = 1 # 4 + else: # 5 + x = 1 # 6 + +no_pop_tops.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (6, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (2, 'line'), + (2, 'return')] + +def no_pop_blocks(): + y = 1 + while not y: + bla + x = 1 + +no_pop_blocks.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (4, 'line'), + (4, 'return')] + +def called(): # line -3 + x = 1 + +def call(): # line 0 + called() + +call.events = [(0, 'call'), + (1, 'line'), + (-3, 'call'), + (-2, 'line'), + (-2, 'return'), + (1, 'return')] + +def raises(): + raise Exception + +def test_raise(): + try: + raises() + except Exception as exc: + x = 1 + +test_raise.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (-3, 'call'), + (-2, 'line'), + (-2, 'exception'), + (-2, 'return'), + (2, 'exception'), + (3, 'line'), + (4, 'line'), + (4, 'return')] + +def _settrace_and_return(tracefunc): + sys.settrace(tracefunc) + sys._getframe().f_back.f_trace = tracefunc +def settrace_and_return(tracefunc): + _settrace_and_return(tracefunc) + +settrace_and_return.events = [(1, 'return')] + +def _settrace_and_raise(tracefunc): + sys.settrace(tracefunc) + sys._getframe().f_back.f_trace = tracefunc + raise RuntimeError +def settrace_and_raise(tracefunc): + try: + _settrace_and_raise(tracefunc) + except RuntimeError as exc: + pass + +settrace_and_raise.events = [(2, 'exception'), + (3, 'line'), + (4, 'line'), + (4, 'return')] + +# implicit return example +# This test is interesting because of the else: pass +# part of the code. The code generate for the true +# part of the if contains a jump past the else branch. +# The compiler then generates an implicit "return None" +# Internally, the compiler visits the pass statement +# and stores its line number for use on the next instruction. +# The next instruction is the implicit return None. +def ireturn_example(): + a = 5 + b = 5 + if a == b: + b = a+1 + else: + pass + +ireturn_example.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (6, 'line'), + (6, 'return')] + +# Tight loop with while(1) example (SF #765624) +def tightloop_example(): + items = range(0, 3) + try: + i = 0 + while 1: + b = items[i]; i+=1 + except IndexError: + pass + +tightloop_example.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (5, 'line'), + (5, 'line'), + (5, 'line'), + (5, 'line'), + (5, 'exception'), + (6, 'line'), + (7, 'line'), + (7, 'return')] + +def tighterloop_example(): + items = range(1, 4) + try: + i = 0 + while 1: i = items[i] + except IndexError: + pass + +tighterloop_example.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (4, 'line'), + (4, 'line'), + (4, 'line'), + (4, 'exception'), + (5, 'line'), + (6, 'line'), + (6, 'return')] + +def generator_function(): + try: + yield True + "continued" + finally: + "finally" +def generator_example(): + # any() will leave the generator before its end + x = any(generator_function()) + + # the following lines were not traced + for x in range(10): + y = x + +generator_example.events = ([(0, 'call'), + (2, 'line'), + (-6, 'call'), + (-5, 'line'), + (-4, 'line'), + (-4, 'return'), + (-4, 'call'), + (-4, 'exception'), + (-1, 'line'), + (-1, 'return')] + + [(5, 'line'), (6, 'line')] * 10 + + [(5, 'line'), (5, 'return')]) + + +class Tracer: + def __init__(self): + self.events = [] + def trace(self, frame, event, arg): + self.events.append((frame.f_lineno, event)) + return self.trace + def traceWithGenexp(self, frame, event, arg): + (o for o in [1]) + self.events.append((frame.f_lineno, event)) + return self.trace + +class TraceTestCase(unittest.TestCase): + + # Disable gc collection when tracing, otherwise the + # deallocators may be traced as well. + def setUp(self): + self.using_gc = gc.isenabled() + gc.disable() + + def tearDown(self): + if self.using_gc: + gc.enable() + + def compare_events(self, line_offset, events, expected_events): + events = [(l - line_offset, e) for (l, e) in events] + if events != expected_events: + self.fail( + "events did not match expectation:\n" + + "\n".join(difflib.ndiff([str(x) for x in expected_events], + [str(x) for x in events]))) + + def run_and_compare(self, func, events): + tracer = Tracer() + sys.settrace(tracer.trace) + func() + sys.settrace(None) + self.compare_events(func.__code__.co_firstlineno, + tracer.events, events) + + def run_test(self, func): + self.run_and_compare(func, func.events) + + def run_test2(self, func): + tracer = Tracer() + func(tracer.trace) + sys.settrace(None) + self.compare_events(func.__code__.co_firstlineno, + tracer.events, func.events) + + def set_and_retrieve_none(self): + sys.settrace(None) + assert sys.gettrace() is None + + def set_and_retrieve_func(self): + def fn(*args): + pass + + sys.settrace(fn) + try: + assert sys.gettrace() is fn + finally: + sys.settrace(None) + + def test_01_basic(self): + self.run_test(basic) + def test_02_arigo(self): + self.run_test(arigo_example) + def test_03_one_instr(self): + self.run_test(one_instr_line) + def test_04_no_pop_blocks(self): + self.run_test(no_pop_blocks) + def test_05_no_pop_tops(self): + self.run_test(no_pop_tops) + def test_06_call(self): + self.run_test(call) + def test_07_raise(self): + self.run_test(test_raise) + + def test_08_settrace_and_return(self): + self.run_test2(settrace_and_return) + def test_09_settrace_and_raise(self): + self.run_test2(settrace_and_raise) + def test_10_ireturn(self): + self.run_test(ireturn_example) + def test_11_tightloop(self): + self.run_test(tightloop_example) + def test_12_tighterloop(self): + self.run_test(tighterloop_example) + + def test_13_genexp(self): + self.run_test(generator_example) + # issue1265: if the trace function contains a generator, + # and if the traced function contains another generator + # that is not completely exhausted, the trace stopped. + # Worse: the 'finally' clause was not invoked. + tracer = Tracer() + sys.settrace(tracer.traceWithGenexp) + generator_example() + sys.settrace(None) + self.compare_events(generator_example.__code__.co_firstlineno, + tracer.events, generator_example.events) + + def test_14_onliner_if(self): + def onliners(): + if True: False + else: True + return 0 + self.run_and_compare( + onliners, + [(0, 'call'), + (1, 'line'), + (3, 'line'), + (3, 'return')]) + + def test_15_loops(self): + # issue1750076: "while" expression is skipped by debugger + def for_example(): + for x in range(2): + pass + self.run_and_compare( + for_example, + [(0, 'call'), + (1, 'line'), + (2, 'line'), + (1, 'line'), + (2, 'line'), + (1, 'line'), + (1, 'return')]) + + def while_example(): + # While expression should be traced on every loop + x = 2 + while x > 0: + x -= 1 + self.run_and_compare( + while_example, + [(0, 'call'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (3, 'line'), + (4, 'line'), + (3, 'line'), + (3, 'return')]) + + def test_16_blank_lines(self): + namespace = {} + exec("def f():\n" + "\n" * 256 + " pass", namespace) + self.run_and_compare( + namespace["f"], + [(0, 'call'), + (257, 'line'), + (257, 'return')]) + + +class RaisingTraceFuncTestCase(unittest.TestCase): + def trace(self, frame, event, arg): + """A trace function that raises an exception in response to a + specific trace event.""" + if event == self.raiseOnEvent: + raise ValueError # just something that isn't RuntimeError + else: + return self.trace + + def f(self): + """The function to trace; raises an exception if that's the case + we're testing, so that the 'exception' trace event fires.""" + if self.raiseOnEvent == 'exception': + x = 0 + y = 1/x + else: + return 1 + + def run_test_for_event(self, event): + """Tests that an exception raised in response to the given event is + handled OK.""" + self.raiseOnEvent = event + try: + for i in range(sys.getrecursionlimit() + 1): + sys.settrace(self.trace) + try: + self.f() + except ValueError: + pass + else: + self.fail("exception not thrown!") + except RuntimeError: + self.fail("recursion counter not reset") + + # Test the handling of exceptions raised by each kind of trace event. + def test_call(self): + self.run_test_for_event('call') + def test_line(self): + self.run_test_for_event('line') + def test_return(self): + self.run_test_for_event('return') + def test_exception(self): + self.run_test_for_event('exception') + + def test_trash_stack(self): + def f(): + for i in range(5): + print(i) # line tracing will raise an exception at this line + + def g(frame, why, extra): + if (why == 'line' and + frame.f_lineno == f.__code__.co_firstlineno + 2): + raise RuntimeError("i am crashing") + return g + + sys.settrace(g) + try: + f() + except RuntimeError: + # the test is really that this doesn't segfault: + import gc + gc.collect() + else: + self.fail("exception not propagated") + + +# '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.__code__: + firstLine = frame.f_code.co_firstlineno + if event == 'line' and 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 as 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 as 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 as 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) as 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 as 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 as 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 as 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 as 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 as 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 as 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_20_large_function(self): + d = {} + exec("""def f(output): # line 0 + x = 0 # line 1 + y = 1 # line 2 + ''' # line 3 + %s # lines 4-1004 + ''' # line 1005 + x += 1 # line 1006 + output.append(x) # line 1007 + return""" % ('\n' * 1000,), d) + f = d['f'] + + f.jump = (2, 1007) + f.output = [0] + self.run_test(f) + + def test_jump_to_firstlineno(self): + # This tests that PDB can jump back to the first line in a + # file. See issue #1689458. It can only be triggered in a + # function call if the function is defined on a single line. + code = compile(""" +# Comments don't count. +output.append(2) # firstlineno is here. +output.append(3) +output.append(4) +""", "", "exec") + class fake_function: + __code__ = code + jump = (2, 0) + tracer = JumpTracer(fake_function) + sys.settrace(tracer.trace) + namespace = {"output": []} + exec(code, namespace) + sys.settrace(None) + self.compare_jump_output([2, 3, 2, 3, 4], namespace["output"]) + + +def test_main(): + support.run_unittest( + TraceTestCase, + RaisingTraceFuncTestCase, + JumpTestCase + ) + +if __name__ == "__main__": + test_main() diff --git a/Lib/test/test_scope.py b/Lib/test/test_scope.py index 643dcbc..1961e6e 100644 --- a/Lib/test/test_scope.py +++ b/Lib/test/test_scope.py @@ -690,6 +690,14 @@ result2 = h() h = g() self.assertEqual(h(), 3) + def testLocalClosureShadowing(self): + exec(""" +x = 4 +def f(x): + class C: + x = x +raises(NameError, f, 3)""", {"raises" : self.assertRaises}) + def test_main(): run_unittest(ScopeTests) diff --git a/Lib/test/test_trace.py b/Lib/test/test_trace.py deleted file mode 100644 index 43134e9..0000000 --- a/Lib/test/test_trace.py +++ /dev/null @@ -1,790 +0,0 @@ -# Testing the line trace facility. - -from test import support -import unittest -import sys -import difflib -import gc - -# A very basic example. If this fails, we're in deep trouble. -def basic(): - return 1 - -basic.events = [(0, 'call'), - (1, 'line'), - (1, 'return')] - -# Many of the tests below are tricky because they involve pass statements. -# If there is implicit control flow around a pass statement (in an except -# clause or else caluse) under what conditions do you set a line number -# following that clause? - - -# The entire "while 0:" statement is optimized away. No code -# exists for it, so the line numbers skip directly from "del x" -# to "x = 1". -def arigo_example(): - x = 1 - del x - while 0: - pass - x = 1 - -arigo_example.events = [(0, 'call'), - (1, 'line'), - (2, 'line'), - (5, 'line'), - (5, 'return')] - -# check that lines consisting of just one instruction get traced: -def one_instr_line(): - x = 1 - del x - x = 1 - -one_instr_line.events = [(0, 'call'), - (1, 'line'), - (2, 'line'), - (3, 'line'), - (3, 'return')] - -def no_pop_tops(): # 0 - x = 1 # 1 - for a in range(2): # 2 - if a: # 3 - x = 1 # 4 - else: # 5 - x = 1 # 6 - -no_pop_tops.events = [(0, 'call'), - (1, 'line'), - (2, 'line'), - (3, 'line'), - (6, 'line'), - (2, 'line'), - (3, 'line'), - (4, 'line'), - (2, 'line'), - (2, 'return')] - -def no_pop_blocks(): - y = 1 - while not y: - bla - x = 1 - -no_pop_blocks.events = [(0, 'call'), - (1, 'line'), - (2, 'line'), - (4, 'line'), - (4, 'return')] - -def called(): # line -3 - x = 1 - -def call(): # line 0 - called() - -call.events = [(0, 'call'), - (1, 'line'), - (-3, 'call'), - (-2, 'line'), - (-2, 'return'), - (1, 'return')] - -def raises(): - raise Exception - -def test_raise(): - try: - raises() - except Exception as exc: - x = 1 - -test_raise.events = [(0, 'call'), - (1, 'line'), - (2, 'line'), - (-3, 'call'), - (-2, 'line'), - (-2, 'exception'), - (-2, 'return'), - (2, 'exception'), - (3, 'line'), - (4, 'line'), - (4, 'return')] - -def _settrace_and_return(tracefunc): - sys.settrace(tracefunc) - sys._getframe().f_back.f_trace = tracefunc -def settrace_and_return(tracefunc): - _settrace_and_return(tracefunc) - -settrace_and_return.events = [(1, 'return')] - -def _settrace_and_raise(tracefunc): - sys.settrace(tracefunc) - sys._getframe().f_back.f_trace = tracefunc - raise RuntimeError -def settrace_and_raise(tracefunc): - try: - _settrace_and_raise(tracefunc) - except RuntimeError as exc: - pass - -settrace_and_raise.events = [(2, 'exception'), - (3, 'line'), - (4, 'line'), - (4, 'return')] - -# implicit return example -# This test is interesting because of the else: pass -# part of the code. The code generate for the true -# part of the if contains a jump past the else branch. -# The compiler then generates an implicit "return None" -# Internally, the compiler visits the pass statement -# and stores its line number for use on the next instruction. -# The next instruction is the implicit return None. -def ireturn_example(): - a = 5 - b = 5 - if a == b: - b = a+1 - else: - pass - -ireturn_example.events = [(0, 'call'), - (1, 'line'), - (2, 'line'), - (3, 'line'), - (4, 'line'), - (6, 'line'), - (6, 'return')] - -# Tight loop with while(1) example (SF #765624) -def tightloop_example(): - items = range(0, 3) - try: - i = 0 - while 1: - b = items[i]; i+=1 - except IndexError: - pass - -tightloop_example.events = [(0, 'call'), - (1, 'line'), - (2, 'line'), - (3, 'line'), - (4, 'line'), - (5, 'line'), - (5, 'line'), - (5, 'line'), - (5, 'line'), - (5, 'exception'), - (6, 'line'), - (7, 'line'), - (7, 'return')] - -def tighterloop_example(): - items = range(1, 4) - try: - i = 0 - while 1: i = items[i] - except IndexError: - pass - -tighterloop_example.events = [(0, 'call'), - (1, 'line'), - (2, 'line'), - (3, 'line'), - (4, 'line'), - (4, 'line'), - (4, 'line'), - (4, 'line'), - (4, 'exception'), - (5, 'line'), - (6, 'line'), - (6, 'return')] - -def generator_function(): - try: - yield True - "continued" - finally: - "finally" -def generator_example(): - # any() will leave the generator before its end - x = any(generator_function()) - - # the following lines were not traced - for x in range(10): - y = x - -generator_example.events = ([(0, 'call'), - (2, 'line'), - (-6, 'call'), - (-5, 'line'), - (-4, 'line'), - (-4, 'return'), - (-4, 'call'), - (-4, 'exception'), - (-1, 'line'), - (-1, 'return')] + - [(5, 'line'), (6, 'line')] * 10 + - [(5, 'line'), (5, 'return')]) - - -class Tracer: - def __init__(self): - self.events = [] - def trace(self, frame, event, arg): - self.events.append((frame.f_lineno, event)) - return self.trace - def traceWithGenexp(self, frame, event, arg): - (o for o in [1]) - self.events.append((frame.f_lineno, event)) - return self.trace - -class TraceTestCase(unittest.TestCase): - - # Disable gc collection when tracing, otherwise the - # deallocators may be traced as well. - def setUp(self): - self.using_gc = gc.isenabled() - gc.disable() - - def tearDown(self): - if self.using_gc: - gc.enable() - - def compare_events(self, line_offset, events, expected_events): - events = [(l - line_offset, e) for (l, e) in events] - if events != expected_events: - self.fail( - "events did not match expectation:\n" + - "\n".join(difflib.ndiff([str(x) for x in expected_events], - [str(x) for x in events]))) - - def run_and_compare(self, func, events): - tracer = Tracer() - sys.settrace(tracer.trace) - func() - sys.settrace(None) - self.compare_events(func.__code__.co_firstlineno, - tracer.events, events) - - def run_test(self, func): - self.run_and_compare(func, func.events) - - def run_test2(self, func): - tracer = Tracer() - func(tracer.trace) - sys.settrace(None) - self.compare_events(func.__code__.co_firstlineno, - tracer.events, func.events) - - def set_and_retrieve_none(self): - sys.settrace(None) - assert sys.gettrace() is None - - def set_and_retrieve_func(self): - def fn(*args): - pass - - sys.settrace(fn) - try: - assert sys.gettrace() is fn - finally: - sys.settrace(None) - - def test_01_basic(self): - self.run_test(basic) - def test_02_arigo(self): - self.run_test(arigo_example) - def test_03_one_instr(self): - self.run_test(one_instr_line) - def test_04_no_pop_blocks(self): - self.run_test(no_pop_blocks) - def test_05_no_pop_tops(self): - self.run_test(no_pop_tops) - def test_06_call(self): - self.run_test(call) - def test_07_raise(self): - self.run_test(test_raise) - - def test_08_settrace_and_return(self): - self.run_test2(settrace_and_return) - def test_09_settrace_and_raise(self): - self.run_test2(settrace_and_raise) - def test_10_ireturn(self): - self.run_test(ireturn_example) - def test_11_tightloop(self): - self.run_test(tightloop_example) - def test_12_tighterloop(self): - self.run_test(tighterloop_example) - - def test_13_genexp(self): - self.run_test(generator_example) - # issue1265: if the trace function contains a generator, - # and if the traced function contains another generator - # that is not completely exhausted, the trace stopped. - # Worse: the 'finally' clause was not invoked. - tracer = Tracer() - sys.settrace(tracer.traceWithGenexp) - generator_example() - sys.settrace(None) - self.compare_events(generator_example.__code__.co_firstlineno, - tracer.events, generator_example.events) - - def test_14_onliner_if(self): - def onliners(): - if True: False - else: True - return 0 - self.run_and_compare( - onliners, - [(0, 'call'), - (1, 'line'), - (3, 'line'), - (3, 'return')]) - - def test_15_loops(self): - # issue1750076: "while" expression is skipped by debugger - def for_example(): - for x in range(2): - pass - self.run_and_compare( - for_example, - [(0, 'call'), - (1, 'line'), - (2, 'line'), - (1, 'line'), - (2, 'line'), - (1, 'line'), - (1, 'return')]) - - def while_example(): - # While expression should be traced on every loop - x = 2 - while x > 0: - x -= 1 - self.run_and_compare( - while_example, - [(0, 'call'), - (2, 'line'), - (3, 'line'), - (4, 'line'), - (3, 'line'), - (4, 'line'), - (3, 'line'), - (3, 'return')]) - - def test_16_blank_lines(self): - namespace = {} - exec("def f():\n" + "\n" * 256 + " pass", namespace) - self.run_and_compare( - namespace["f"], - [(0, 'call'), - (257, 'line'), - (257, 'return')]) - - -class RaisingTraceFuncTestCase(unittest.TestCase): - def trace(self, frame, event, arg): - """A trace function that raises an exception in response to a - specific trace event.""" - if event == self.raiseOnEvent: - raise ValueError # just something that isn't RuntimeError - else: - return self.trace - - def f(self): - """The function to trace; raises an exception if that's the case - we're testing, so that the 'exception' trace event fires.""" - if self.raiseOnEvent == 'exception': - x = 0 - y = 1/x - else: - return 1 - - def run_test_for_event(self, event): - """Tests that an exception raised in response to the given event is - handled OK.""" - self.raiseOnEvent = event - try: - for i in range(sys.getrecursionlimit() + 1): - sys.settrace(self.trace) - try: - self.f() - except ValueError: - pass - else: - self.fail("exception not thrown!") - except RuntimeError: - self.fail("recursion counter not reset") - - # Test the handling of exceptions raised by each kind of trace event. - def test_call(self): - self.run_test_for_event('call') - def test_line(self): - self.run_test_for_event('line') - def test_return(self): - self.run_test_for_event('return') - def test_exception(self): - self.run_test_for_event('exception') - - def test_trash_stack(self): - def f(): - for i in range(5): - print(i) # line tracing will raise an exception at this line - - def g(frame, why, extra): - if (why == 'line' and - frame.f_lineno == f.__code__.co_firstlineno + 2): - raise RuntimeError("i am crashing") - return g - - sys.settrace(g) - try: - f() - except RuntimeError: - # the test is really that this doesn't segfault: - import gc - gc.collect() - else: - self.fail("exception not propagated") - - -# '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.__code__: - firstLine = frame.f_code.co_firstlineno - if event == 'line' and 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 as 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 as 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 as 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) as 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 as 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 as 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 as 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 as 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 as 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 as 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_20_large_function(self): - d = {} - exec("""def f(output): # line 0 - x = 0 # line 1 - y = 1 # line 2 - ''' # line 3 - %s # lines 4-1004 - ''' # line 1005 - x += 1 # line 1006 - output.append(x) # line 1007 - return""" % ('\n' * 1000,), d) - f = d['f'] - - f.jump = (2, 1007) - f.output = [0] - self.run_test(f) - - def test_jump_to_firstlineno(self): - # This tests that PDB can jump back to the first line in a - # file. See issue #1689458. It can only be triggered in a - # function call if the function is defined on a single line. - code = compile(""" -# Comments don't count. -output.append(2) # firstlineno is here. -output.append(3) -output.append(4) -""", "", "exec") - class fake_function: - __code__ = code - jump = (2, 0) - tracer = JumpTracer(fake_function) - sys.settrace(tracer.trace) - namespace = {"output": []} - exec(code, namespace) - sys.settrace(None) - self.compare_jump_output([2, 3, 2, 3, 4], namespace["output"]) - - -def test_main(): - support.run_unittest( - TraceTestCase, - RaisingTraceFuncTestCase, - JumpTestCase - ) - -if __name__ == "__main__": - test_main() diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 268a924..505ca68 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6094,6 +6094,7 @@ supercheck(PyTypeObject *type, PyObject *obj) PyErr_SetString(PyExc_TypeError, "super(type, obj): " "obj must be an instance or subtype of type"); + printf("%s\n", type->tp_name); return NULL; } diff --git a/Python/ceval.c b/Python/ceval.c index 2d4b16a..368ad69 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2052,6 +2052,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) break; TARGET(LOAD_NAME) + TARGET(LOAD_NAME_LOCAL_ONLY) w = GETITEM(names, oparg); if ((v = f->f_locals) == NULL) { PyErr_Format(PyExc_SystemError, @@ -2073,15 +2074,14 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) } } if (x == NULL) { - x = PyDict_GetItem(f->f_globals, w); + if (opcode != LOAD_NAME_LOCAL_ONLY) { + x = PyDict_GetItem(f->f_globals, w); + if (x == NULL) + x = PyDict_GetItem(f->f_builtins, w); + } if (x == NULL) { - x = PyDict_GetItem(f->f_builtins, w); - if (x == NULL) { - format_exc_check_arg( - PyExc_NameError, - NAME_ERROR_MSG, w); - break; - } + format_exc_check_arg(PyExc_NameError, NAME_ERROR_MSG, w); + break; } Py_INCREF(x); } diff --git a/Python/compile.c b/Python/compile.c index aae0339..83c9e02 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -787,6 +787,7 @@ opcode_stack_effect(int opcode, int oparg) case LOAD_CONST: return 1; case LOAD_NAME: + case LOAD_NAME_LOCAL_ONLY: return 1; case BUILD_TUPLE: case BUILD_LIST: @@ -2481,6 +2482,7 @@ compiler_nameop(struct compiler *c, identifier name, expr_context_ty ctx) optype = OP_DEREF; break; case LOCAL: + case LOCAL_ONLY: if (c->u->u_ste->ste_type == FunctionBlock) optype = OP_FAST; break; @@ -2556,7 +2558,7 @@ compiler_nameop(struct compiler *c, identifier name, expr_context_ty ctx) break; case OP_NAME: switch (ctx) { - case Load: op = LOAD_NAME; break; + case Load: op = (scope == LOCAL_ONLY) ? LOAD_NAME_LOCAL_ONLY : LOAD_NAME; break; case Store: op = STORE_NAME; break; case Del: op = DELETE_NAME; break; case AugLoad: diff --git a/Python/symtable.c b/Python/symtable.c index 55c9f47..37bdf2a 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -432,7 +432,14 @@ analyze_name(PySTEntryObject *ste, PyObject *scopes, PyObject *name, long flags, return PySet_Add(free, name) >= 0; } if (flags & DEF_BOUND) { - SET_SCOPE(scopes, name, LOCAL); + if (ste->ste_type == ClassBlock && + !(flags & DEF_PARAM) && + bound && PySet_Contains(bound, name)) { + SET_SCOPE(scopes, name, LOCAL_ONLY); + } + else { + SET_SCOPE(scopes, name, LOCAL); + } if (PySet_Add(local, name) < 0) return 0; if (PySet_Discard(global, name) < 0) @@ -489,7 +496,7 @@ analyze_cells(PyObject *scopes, PyObject *free, const char *restricted) long scope; assert(PyLong_Check(v)); scope = PyLong_AS_LONG(v); - if (scope != LOCAL) + if (scope != LOCAL && scope != LOCAL_ONLY) continue; if (!PySet_Contains(free, name)) continue; -- cgit v0.12