diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -490,16 +490,20 @@ 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 + def __init__(self, function, event='line'): + self.code = function.__code__ self.jumpFrom = function.jump[0] self.jumpTo = function.jump[1] + self.event = event self.done = False def trace(self, frame, event, arg): - if not self.done and frame.f_code == self.function.__code__: + # Jumps may also be tested in a nested function. + if (not self.done and (frame.f_code == self.code or + (frame.f_back and frame.f_back.f_code == self.code))): firstLine = frame.f_code.co_firstlineno - if event == 'line' and frame.f_lineno == firstLine + self.jumpFrom: + if (event == self.event and + frame.f_lineno == firstLine + self.jumpFrom): # Cope with non-integer self.jumpTo (because of # no_jump_to_non_integers below). try: @@ -706,6 +710,26 @@ jump_across_with.jump = (1, 3) jump_across_with.output = [] +def no_jump_from_return_event(output): + lineno = 1 + return +no_jump_from_return_event.jump = (2, 1) +no_jump_from_return_event.output = [] + +def no_jump_from_yield(output): + def gen(): + lineno = 1 + yield 1 + next(gen()) +no_jump_from_yield.jump = (2, 1) +no_jump_from_yield.output = [] + +def no_jump_from_exception_event(output): + lineno = 1 + 1 / 0 +no_jump_from_exception_event.jump = (2, 1) +no_jump_from_exception_event.output = [] + # This verifies that you can't set f_lineno via _getframe or similar # trickery. def no_jump_without_trace_function(): @@ -733,8 +757,8 @@ "Expected: " + repr(expected) + "\n" + "Received: " + repr(received)) - def run_test(self, func): - tracer = JumpTracer(func) + def run_test(self, func, event='line'): + tracer = JumpTracer(func, event) sys.settrace(tracer.trace) output = [] func(output) @@ -784,6 +808,18 @@ def test_jump_across_with(self): self.addCleanup(support.unlink, support.TESTFN) self.run_test(jump_across_with) + def test_no_jump_from_return_event(self): + self.assertRaisesRegex(ValueError, + "can't jump from a return statement", + self.run_test, no_jump_from_return_event, 'return') + def test_no_jump_from_yield(self): + self.assertRaisesRegex(ValueError, + "can't jump from a yield statement", + self.run_test, no_jump_from_yield, 'return') + def test_no_jump_from_exception_event(self): + self.assertRaisesRegex(ValueError, + "can only jump from a 'line' trace event", + self.run_test, no_jump_from_exception_event, 'exception') def test_20_large_function(self): d = {} diff --git a/Objects/frameobject.c b/Objects/frameobject.c --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -101,6 +101,27 @@ return -1; } + PyBytes_AsStringAndSize(f->f_code->co_code, (char **)&code, &code_len); + switch (code[f->f_lasti]) { + case RETURN_VALUE: + PyErr_SetString(PyExc_ValueError, + "can't jump from a return statement"); + return -1; + + case YIELD_VALUE: + case YIELD_FROM: + PyErr_SetString(PyExc_ValueError, + "can't jump from a yield statement"); + return -1; + } + + if (f->f_stacktop == NULL) { + /* Catching attempts to jump from an 'exception' trace event. */ + PyErr_SetString(PyExc_ValueError, + "can only jump from a 'line' trace event"); + return -1; + } + /* Fail if the line comes before the start of the code block. */ l_new_lineno = PyLong_AsLongAndOverflow(p_new_lineno, &overflow); if (overflow @@ -155,7 +176,6 @@ } /* We're now ready to look at the bytecode. */ - PyBytes_AsStringAndSize(f->f_code->co_code, (char **)&code, &code_len); min_addr = Py_MIN(new_lasti, f->f_lasti); max_addr = Py_MAX(new_lasti, f->f_lasti);