mirror of
https://github.com/python/cpython.git
synced 2025-08-03 16:39:00 +00:00
bpo-17288: Prevent jumps from 'return' and 'exception' trace events. (GH-6107)
(cherry picked from commit e32bbaf376
)
This commit is contained in:
parent
5affd5c29e
commit
b8e9d6c5cd
3 changed files with 93 additions and 15 deletions
|
@ -555,20 +555,35 @@ class RaisingTraceFuncTestCase(unittest.TestCase):
|
|||
class JumpTracer:
|
||||
"""Defines a trace function that jumps from one place to another."""
|
||||
|
||||
def __init__(self, function, jumpFrom, jumpTo):
|
||||
self.function = function
|
||||
def __init__(self, function, jumpFrom, jumpTo, event='line',
|
||||
decorated=False):
|
||||
self.code = function.__code__
|
||||
self.jumpFrom = jumpFrom
|
||||
self.jumpTo = jumpTo
|
||||
self.event = event
|
||||
self.firstLine = None if decorated else self.code.co_firstlineno
|
||||
self.done = False
|
||||
|
||||
def trace(self, frame, event, arg):
|
||||
if not self.done and frame.f_code == self.function.__code__:
|
||||
firstLine = frame.f_code.co_firstlineno
|
||||
if event == 'line' and frame.f_lineno == firstLine + self.jumpFrom:
|
||||
if self.done:
|
||||
return
|
||||
# frame.f_code.co_firstlineno is the first line of the decorator when
|
||||
# 'function' is decorated and the decorator may be written using
|
||||
# multiple physical lines when it is too long. Use the first line
|
||||
# trace event in 'function' to find the first line of 'function'.
|
||||
if (self.firstLine is None and frame.f_code == self.code and
|
||||
event == 'line'):
|
||||
self.firstLine = frame.f_lineno - 1
|
||||
if (event == self.event and self.firstLine and
|
||||
frame.f_lineno == self.firstLine + self.jumpFrom):
|
||||
f = frame
|
||||
while f is not None and f.f_code != self.code:
|
||||
f = f.f_back
|
||||
if f is not None:
|
||||
# Cope with non-integer self.jumpTo (because of
|
||||
# no_jump_to_non_integers below).
|
||||
try:
|
||||
frame.f_lineno = firstLine + self.jumpTo
|
||||
frame.f_lineno = self.firstLine + self.jumpTo
|
||||
except TypeError:
|
||||
frame.f_lineno = self.jumpTo
|
||||
self.done = True
|
||||
|
@ -608,8 +623,9 @@ class JumpTestCase(unittest.TestCase):
|
|||
"Expected: " + repr(expected) + "\n" +
|
||||
"Received: " + repr(received))
|
||||
|
||||
def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
|
||||
tracer = JumpTracer(func, jumpFrom, jumpTo)
|
||||
def run_test(self, func, jumpFrom, jumpTo, expected, error=None,
|
||||
event='line', decorated=False):
|
||||
tracer = JumpTracer(func, jumpFrom, jumpTo, event, decorated)
|
||||
sys.settrace(tracer.trace)
|
||||
output = []
|
||||
if error is None:
|
||||
|
@ -620,15 +636,15 @@ class JumpTestCase(unittest.TestCase):
|
|||
sys.settrace(None)
|
||||
self.compare_jump_output(expected, output)
|
||||
|
||||
def jump_test(jumpFrom, jumpTo, expected, error=None):
|
||||
def jump_test(jumpFrom, jumpTo, expected, error=None, event='line'):
|
||||
"""Decorator that creates a test that makes a jump
|
||||
from one place to another in the following code.
|
||||
"""
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def test(self):
|
||||
# +1 to compensate a decorator line
|
||||
self.run_test(func, jumpFrom+1, jumpTo+1, expected, error)
|
||||
self.run_test(func, jumpFrom, jumpTo, expected,
|
||||
error=error, event=event, decorated=True)
|
||||
return test
|
||||
return decorator
|
||||
|
||||
|
@ -1128,6 +1144,36 @@ output.append(4)
|
|||
sys.settrace(None)
|
||||
self.compare_jump_output([2, 3, 2, 3, 4], namespace["output"])
|
||||
|
||||
@jump_test(2, 3, [1], event='call', error=(ValueError, "can't jump from"
|
||||
" the 'call' trace event of a new frame"))
|
||||
def test_no_jump_from_call(output):
|
||||
output.append(1)
|
||||
def nested():
|
||||
output.append(3)
|
||||
nested()
|
||||
output.append(5)
|
||||
|
||||
@jump_test(2, 1, [1], event='return', error=(ValueError,
|
||||
"can only jump from a 'line' trace event"))
|
||||
def test_no_jump_from_return_event(output):
|
||||
output.append(1)
|
||||
return
|
||||
|
||||
@jump_test(2, 1, [1], event='exception', error=(ValueError,
|
||||
"can only jump from a 'line' trace event"))
|
||||
def test_no_jump_from_exception_event(output):
|
||||
output.append(1)
|
||||
1 / 0
|
||||
|
||||
@jump_test(3, 2, [2], event='return', error=(ValueError,
|
||||
"can't jump from a yield statement"))
|
||||
def test_no_jump_from_yield(output):
|
||||
def gen():
|
||||
output.append(2)
|
||||
yield 3
|
||||
next(gen())
|
||||
output.append(5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue