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

`