[3.7] bpo-17288: Prevent jumps from 'return' and 'exception' trace ev… · python/cpython@cf61a81 (original) (raw)
`@@ -510,20 +510,35 @@ def g(frame, event, arg):
`
510
510
`class JumpTracer:
`
511
511
`"""Defines a trace function that jumps from one place to another."""
`
512
512
``
513
``
`-
def init(self, function, jumpFrom, jumpTo):
`
514
``
`-
self.function = function
`
``
513
`+
def init(self, function, jumpFrom, jumpTo, event='line',
`
``
514
`+
decorated=False):
`
``
515
`+
self.code = function.code
`
515
516
`self.jumpFrom = jumpFrom
`
516
517
`self.jumpTo = jumpTo
`
``
518
`+
self.event = event
`
``
519
`+
self.firstLine = None if decorated else self.code.co_firstlineno
`
517
520
`self.done = False
`
518
521
``
519
522
`def trace(self, frame, event, arg):
`
520
``
`-
if not self.done and frame.f_code == self.function.code:
`
521
``
`-
firstLine = frame.f_code.co_firstlineno
`
522
``
`-
if event == 'line' and frame.f_lineno == firstLine + self.jumpFrom:
`
``
523
`+
if self.done:
`
``
524
`+
return
`
``
525
`+
frame.f_code.co_firstlineno is the first line of the decorator when
`
``
526
`+
'function' is decorated and the decorator may be written using
`
``
527
`+
multiple physical lines when it is too long. Use the first line
`
``
528
`+
trace event in 'function' to find the first line of 'function'.
`
``
529
`+
if (self.firstLine is None and frame.f_code == self.code and
`
``
530
`+
event == 'line'):
`
``
531
`+
self.firstLine = frame.f_lineno - 1
`
``
532
`+
if (event == self.event and self.firstLine and
`
``
533
`+
frame.f_lineno == self.firstLine + self.jumpFrom):
`
``
534
`+
f = frame
`
``
535
`+
while f is not None and f.f_code != self.code:
`
``
536
`+
f = f.f_back
`
``
537
`+
if f is not None:
`
523
538
`# Cope with non-integer self.jumpTo (because of
`
524
539
`# no_jump_to_non_integers below).
`
525
540
`try:
`
526
``
`-
frame.f_lineno = firstLine + self.jumpTo
`
``
541
`+
frame.f_lineno = self.firstLine + self.jumpTo
`
527
542
`except TypeError:
`
528
543
`frame.f_lineno = self.jumpTo
`
529
544
`self.done = True
`
`@@ -563,8 +578,9 @@ def compare_jump_output(self, expected, received):
`
563
578
`"Expected: " + repr(expected) + "\n" +
`
564
579
`"Received: " + repr(received))
`
565
580
``
566
``
`-
def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
`
567
``
`-
tracer = JumpTracer(func, jumpFrom, jumpTo)
`
``
581
`+
def run_test(self, func, jumpFrom, jumpTo, expected, error=None,
`
``
582
`+
event='line', decorated=False):
`
``
583
`+
tracer = JumpTracer(func, jumpFrom, jumpTo, event, decorated)
`
568
584
`sys.settrace(tracer.trace)
`
569
585
`output = []
`
570
586
`if error is None:
`
`@@ -575,15 +591,15 @@ def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
`
575
591
`sys.settrace(None)
`
576
592
`self.compare_jump_output(expected, output)
`
577
593
``
578
``
`-
def jump_test(jumpFrom, jumpTo, expected, error=None):
`
``
594
`+
def jump_test(jumpFrom, jumpTo, expected, error=None, event='line'):
`
579
595
`"""Decorator that creates a test that makes a jump
`
580
596
` from one place to another in the following code.
`
581
597
` """
`
582
598
`def decorator(func):
`
583
599
`@wraps(func)
`
584
600
`def test(self):
`
585
``
`-
+1 to compensate a decorator line
`
586
``
`-
self.run_test(func, jumpFrom+1, jumpTo+1, expected, error)
`
``
601
`+
self.run_test(func, jumpFrom, jumpTo, expected,
`
``
602
`+
error=error, event=event, decorated=True)
`
587
603
`return test
`
588
604
`return decorator
`
589
605
``
`@@ -1058,6 +1074,36 @@ class fake_function:
`
1058
1074
`sys.settrace(None)
`
1059
1075
`self.compare_jump_output([2, 3, 2, 3, 4], namespace["output"])
`
1060
1076
``
``
1077
`+
@jump_test(2, 3, [1], event='call', error=(ValueError, "can't jump from"
`
``
1078
`+
" the 'call' trace event of a new frame"))
`
``
1079
`+
def test_no_jump_from_call(output):
`
``
1080
`+
output.append(1)
`
``
1081
`+
def nested():
`
``
1082
`+
output.append(3)
`
``
1083
`+
nested()
`
``
1084
`+
output.append(5)
`
``
1085
+
``
1086
`+
@jump_test(2, 1, [1], event='return', error=(ValueError,
`
``
1087
`+
"can only jump from a 'line' trace event"))
`
``
1088
`+
def test_no_jump_from_return_event(output):
`
``
1089
`+
output.append(1)
`
``
1090
`+
return
`
``
1091
+
``
1092
`+
@jump_test(2, 1, [1], event='exception', error=(ValueError,
`
``
1093
`+
"can only jump from a 'line' trace event"))
`
``
1094
`+
def test_no_jump_from_exception_event(output):
`
``
1095
`+
output.append(1)
`
``
1096
`+
1 / 0
`
``
1097
+
``
1098
`+
@jump_test(3, 2, [2], event='return', error=(ValueError,
`
``
1099
`+
"can't jump from a yield statement"))
`
``
1100
`+
def test_no_jump_from_yield(output):
`
``
1101
`+
def gen():
`
``
1102
`+
output.append(2)
`
``
1103
`+
yield 3
`
``
1104
`+
next(gen())
`
``
1105
`+
output.append(5)
`
``
1106
+
1061
1107
``
1062
1108
`if name == "main":
`
1063
1109
`unittest.main()
`