[2.7] bpo-17288: Prevent jumps from 'return' and 'exception' trace ev… · python/cpython@baca85f (original) (raw)
`@@ -482,20 +482,35 @@ def g(frame, why, extra):
`
482
482
`class JumpTracer:
`
483
483
`"""Defines a trace function that jumps from one place to another."""
`
484
484
``
485
``
`-
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
`
487
488
`self.jumpFrom = jumpFrom
`
488
489
`self.jumpTo = jumpTo
`
``
490
`+
self.event = event
`
``
491
`+
self.firstLine = None if decorated else self.code.co_firstlineno
`
489
492
`self.done = False
`
490
493
``
491
494
`def 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:
`
495
510
`# Cope with non-integer self.jumpTo (because of
`
496
511
`# no_jump_to_non_integers below).
`
497
512
`try:
`
498
``
`-
frame.f_lineno = firstLine + self.jumpTo
`
``
513
`+
frame.f_lineno = self.firstLine + self.jumpTo
`
499
514
`except TypeError:
`
500
515
`frame.f_lineno = self.jumpTo
`
501
516
`self.done = True
`
`@@ -535,8 +550,9 @@ def compare_jump_output(self, expected, received):
`
535
550
`"Expected: " + repr(expected) + "\n" +
`
536
551
`"Received: " + repr(received))
`
537
552
``
538
``
`-
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)
`
540
556
`sys.settrace(tracer.trace)
`
541
557
`output = []
`
542
558
`if error is None:
`
`@@ -547,15 +563,15 @@ def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
`
547
563
`sys.settrace(None)
`
548
564
`self.compare_jump_output(expected, output)
`
549
565
``
550
``
`-
def jump_test(jumpFrom, jumpTo, expected, error=None):
`
``
566
`+
def jump_test(jumpFrom, jumpTo, expected, error=None, event='line'):
`
551
567
`"""Decorator that creates a test that makes a jump
`
552
568
` from one place to another in the following code.
`
553
569
` """
`
554
570
`def decorator(func):
`
555
571
`@wraps(func)
`
556
572
`def 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)
`
559
575
`return test
`
560
576
`return decorator
`
561
577
``
`@@ -1018,6 +1034,36 @@ class fake_function:
`
1018
1034
`sys.settrace(None)
`
1019
1035
`self.compare_jump_output([2, 3, 2, 3, 4], namespace["output"])
`
1020
1036
``
``
1037
`+
@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
+
1021
1067
``
1022
1068
`def test_main():
`
1023
1069
`test_support.run_unittest(
`