[2.7] bpo-17288: Prevent jumps from 'return' and 'exception' trace ev… · python/cpython@baca85f

@@ -482,20 +482,35 @@ def g(frame, why, extra):

482482

class 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

487488

self.jumpFrom = jumpFrom

488489

self.jumpTo = jumpTo

490+

self.event = event

491+

self.firstLine = None if decorated else self.code.co_firstlineno

489492

self.done = False

490493491494

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:

495510

# Cope with non-integer self.jumpTo (because of

496511

# no_jump_to_non_integers below).

497512

try:

498-

frame.f_lineno = firstLine + self.jumpTo

513+

frame.f_lineno = self.firstLine + self.jumpTo

499514

except TypeError:

500515

frame.f_lineno = self.jumpTo

501516

self.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)

540556

sys.settrace(tracer.trace)

541557

output = []

542558

if error is None:

@@ -547,15 +563,15 @@ def run_test(self, func, jumpFrom, jumpTo, expected, error=None):

547563

sys.settrace(None)

548564

self.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

"""

554570

def decorator(func):

555571

@wraps(func)

556572

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)

559575

return test

560576

return decorator

561577

@@ -1018,6 +1034,36 @@ class fake_function:

10181034

sys.settrace(None)

10191035

self.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+1021106710221068

def test_main():

10231069

test_support.run_unittest(