[2.7] bpo-17288: Prevent jumps from 'return' and 'exception' trace ev… · python/cpython@baca85f
@@ -482,20 +482,35 @@ def g(frame, why, extra):
482482class JumpTracer:
483483"""Defines a trace function that jumps from one place to another."""
484484485-def __init__(self, function, jumpFrom, jumpTo):
486-self.function = function
485+def __init__(self, function, jumpFrom, jumpTo, event='line',
486+decorated=False):
487+self.code = function.func_code
487488self.jumpFrom = jumpFrom
488489self.jumpTo = jumpTo
490+self.event = event
491+self.firstLine = None if decorated else self.code.co_firstlineno
489492self.done = False
490493491494def trace(self, frame, event, arg):
492-if not self.done and frame.f_code == self.function.func_code:
493-firstLine = frame.f_code.co_firstlineno
494-if event == 'line' and frame.f_lineno == firstLine + self.jumpFrom:
495+if self.done:
496+return
497+# frame.f_code.co_firstlineno is the first line of the decorator when
498+# 'function' is decorated and the decorator may be written using
499+# multiple physical lines when it is too long. Use the first line
500+# trace event in 'function' to find the first line of 'function'.
501+if (self.firstLine is None and frame.f_code == self.code and
502+event == 'line'):
503+self.firstLine = frame.f_lineno - 1
504+if (event == self.event and self.firstLine and
505+frame.f_lineno == self.firstLine + self.jumpFrom):
506+f = frame
507+while f is not None and f.f_code != self.code:
508+f = f.f_back
509+if f is not None:
495510# Cope with non-integer self.jumpTo (because of
496511# no_jump_to_non_integers below).
497512try:
498-frame.f_lineno = firstLine + self.jumpTo
513+frame.f_lineno = self.firstLine + self.jumpTo
499514except TypeError:
500515frame.f_lineno = self.jumpTo
501516self.done = True
@@ -535,8 +550,9 @@ def compare_jump_output(self, expected, received):
535550"Expected: " + repr(expected) + "\n" +
536551"Received: " + repr(received))
537552538-def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
539-tracer = JumpTracer(func, jumpFrom, jumpTo)
553+def run_test(self, func, jumpFrom, jumpTo, expected, error=None,
554+event='line', decorated=False):
555+tracer = JumpTracer(func, jumpFrom, jumpTo, event, decorated)
540556sys.settrace(tracer.trace)
541557output = []
542558if error is None:
@@ -547,15 +563,15 @@ def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
547563sys.settrace(None)
548564self.compare_jump_output(expected, output)
549565550-def jump_test(jumpFrom, jumpTo, expected, error=None):
566+def jump_test(jumpFrom, jumpTo, expected, error=None, event='line'):
551567"""Decorator that creates a test that makes a jump
552568 from one place to another in the following code.
553569 """
554570def decorator(func):
555571@wraps(func)
556572def test(self):
557-# +1 to compensate a decorator line
558-self.run_test(func, jumpFrom+1, jumpTo+1, expected, error)
573+self.run_test(func, jumpFrom, jumpTo, expected,
574+ error=error, event=event, decorated=True)
559575return test
560576return decorator
561577@@ -1018,6 +1034,36 @@ class fake_function:
10181034sys.settrace(None)
10191035self.compare_jump_output([2, 3, 2, 3, 4], namespace["output"])
102010361037+@jump_test(2, 3, [1], event='call', error=(ValueError, "can't jump from"
1038+ " the 'call' trace event of a new frame"))
1039+def test_no_jump_from_call(output):
1040+output.append(1)
1041+def nested():
1042+output.append(3)
1043+nested()
1044+output.append(5)
1045+1046+@jump_test(2, 1, [1], event='return', error=(ValueError,
1047+ "can only jump from a 'line' trace event"))
1048+def test_no_jump_from_return_event(output):
1049+output.append(1)
1050+return
1051+1052+@jump_test(2, 1, [1], event='exception', error=(ValueError,
1053+ "can only jump from a 'line' trace event"))
1054+def test_no_jump_from_exception_event(output):
1055+output.append(1)
1056+1 / 0
1057+1058+@jump_test(3, 2, [2], event='return', error=(ValueError,
1059+ "can't jump from a yield statement"))
1060+def test_no_jump_from_yield(output):
1061+def gen():
1062+output.append(2)
1063+yield 3
1064+next(gen())
1065+output.append(5)
1066+1021106710221068def test_main():
10231069test_support.run_unittest(