[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()

`