Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitb8e9d6c

Browse files
xdegayeserhiy-storchaka
authored andcommitted
bpo-17288: Prevent jumps from 'return' and 'exception' trace events. (GH-6107)
(cherry picked from commite32bbaf)
1 parent5affd5c commitb8e9d6c

File tree

3 files changed

+93
-15
lines changed

3 files changed

+93
-15
lines changed

‎Lib/test/test_sys_settrace.py‎

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -555,20 +555,35 @@ def g(frame, event, arg):
555555
classJumpTracer:
556556
"""Defines a trace function that jumps from one place to another."""
557557

558-
def__init__(self,function,jumpFrom,jumpTo):
559-
self.function=function
558+
def__init__(self,function,jumpFrom,jumpTo,event='line',
559+
decorated=False):
560+
self.code=function.__code__
560561
self.jumpFrom=jumpFrom
561562
self.jumpTo=jumpTo
563+
self.event=event
564+
self.firstLine=Noneifdecoratedelseself.code.co_firstlineno
562565
self.done=False
563566

564567
deftrace(self,frame,event,arg):
565-
ifnotself.doneandframe.f_code==self.function.__code__:
566-
firstLine=frame.f_code.co_firstlineno
567-
ifevent=='line'andframe.f_lineno==firstLine+self.jumpFrom:
568+
ifself.done:
569+
return
570+
# frame.f_code.co_firstlineno is the first line of the decorator when
571+
# 'function' is decorated and the decorator may be written using
572+
# multiple physical lines when it is too long. Use the first line
573+
# trace event in 'function' to find the first line of 'function'.
574+
if (self.firstLineisNoneandframe.f_code==self.codeand
575+
event=='line'):
576+
self.firstLine=frame.f_lineno-1
577+
if (event==self.eventandself.firstLineand
578+
frame.f_lineno==self.firstLine+self.jumpFrom):
579+
f=frame
580+
whilefisnotNoneandf.f_code!=self.code:
581+
f=f.f_back
582+
iffisnotNone:
568583
# Cope with non-integer self.jumpTo (because of
569584
# no_jump_to_non_integers below).
570585
try:
571-
frame.f_lineno=firstLine+self.jumpTo
586+
frame.f_lineno=self.firstLine+self.jumpTo
572587
exceptTypeError:
573588
frame.f_lineno=self.jumpTo
574589
self.done=True
@@ -608,8 +623,9 @@ def compare_jump_output(self, expected, received):
608623
"Expected: "+repr(expected)+"\n"+
609624
"Received: "+repr(received))
610625

611-
defrun_test(self,func,jumpFrom,jumpTo,expected,error=None):
612-
tracer=JumpTracer(func,jumpFrom,jumpTo)
626+
defrun_test(self,func,jumpFrom,jumpTo,expected,error=None,
627+
event='line',decorated=False):
628+
tracer=JumpTracer(func,jumpFrom,jumpTo,event,decorated)
613629
sys.settrace(tracer.trace)
614630
output= []
615631
iferrorisNone:
@@ -620,15 +636,15 @@ def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
620636
sys.settrace(None)
621637
self.compare_jump_output(expected,output)
622638

623-
defjump_test(jumpFrom,jumpTo,expected,error=None):
639+
defjump_test(jumpFrom,jumpTo,expected,error=None,event='line'):
624640
"""Decorator that creates a test that makes a jump
625641
from one place to another in the following code.
626642
"""
627643
defdecorator(func):
628644
@wraps(func)
629645
deftest(self):
630-
# +1 to compensate a decorator line
631-
self.run_test(func,jumpFrom+1,jumpTo+1,expected,error)
646+
self.run_test(func,jumpFrom,jumpTo,expected,
647+
error=error,event=event,decorated=True)
632648
returntest
633649
returndecorator
634650

@@ -1128,6 +1144,36 @@ class fake_function:
11281144
sys.settrace(None)
11291145
self.compare_jump_output([2,3,2,3,4],namespace["output"])
11301146

1147+
@jump_test(2,3, [1],event='call',error=(ValueError,"can't jump from"
1148+
" the 'call' trace event of a new frame"))
1149+
deftest_no_jump_from_call(output):
1150+
output.append(1)
1151+
defnested():
1152+
output.append(3)
1153+
nested()
1154+
output.append(5)
1155+
1156+
@jump_test(2,1, [1],event='return',error=(ValueError,
1157+
"can only jump from a 'line' trace event"))
1158+
deftest_no_jump_from_return_event(output):
1159+
output.append(1)
1160+
return
1161+
1162+
@jump_test(2,1, [1],event='exception',error=(ValueError,
1163+
"can only jump from a 'line' trace event"))
1164+
deftest_no_jump_from_exception_event(output):
1165+
output.append(1)
1166+
1/0
1167+
1168+
@jump_test(3,2, [2],event='return',error=(ValueError,
1169+
"can't jump from a yield statement"))
1170+
deftest_no_jump_from_yield(output):
1171+
defgen():
1172+
output.append(2)
1173+
yield3
1174+
next(gen())
1175+
output.append(5)
1176+
11311177

11321178
if__name__=="__main__":
11331179
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Prevent jumps from 'return' and 'exception' trace events.

‎Objects/frameobject.c‎

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ get_arg(const _Py_CODEUNIT *codestr, Py_ssize_t i)
8181
* the blockstack needs to be set up before their code runs.
8282
* o 'for' and 'async for' loops can't be jumped into because the
8383
* iterator needs to be on the stack.
84+
* o Jumps cannot be made from within a trace function invoked with a
85+
* 'return' or 'exception' event since the eval loop has been exited at
86+
* that time.
8487
*/
8588
staticint
8689
frame_setlineno(PyFrameObject*f,PyObject*p_new_lineno)
@@ -109,13 +112,32 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
109112
return-1;
110113
}
111114

115+
/* Upon the 'call' trace event of a new frame, f->f_lasti is -1 and
116+
* f->f_trace is NULL, check first on the first condition.
117+
* Forbidding jumps from the 'call' event of a new frame is a side effect
118+
* of allowing to set f_lineno only from trace functions. */
119+
if (f->f_lasti==-1) {
120+
PyErr_Format(PyExc_ValueError,
121+
"can't jump from the 'call' trace event of a new frame");
122+
return-1;
123+
}
124+
112125
/* You can only do this from within a trace function, not via
113126
* _getframe or similar hackery. */
114-
if (!f->f_trace)
115-
{
127+
if (!f->f_trace) {
116128
PyErr_Format(PyExc_ValueError,
117-
"f_lineno can only be set by a"
118-
" line trace function");
129+
"f_lineno can only be set by a trace function");
130+
return-1;
131+
}
132+
133+
/* Forbid jumps upon a 'return' trace event (except after executing a
134+
* YIELD_VALUE or YIELD_FROM opcode, f_stacktop is not NULL in that case)
135+
* and upon an 'exception' trace event.
136+
* Jumps from 'call' trace events have already been forbidden above for new
137+
* frames, so this check does not change anything for 'call' events. */
138+
if (f->f_stacktop==NULL) {
139+
PyErr_SetString(PyExc_ValueError,
140+
"can only jump from a 'line' trace event");
119141
return-1;
120142
}
121143

@@ -175,6 +197,15 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
175197
/* We're now ready to look at the bytecode. */
176198
PyBytes_AsStringAndSize(f->f_code->co_code, (char**)&code,&code_len);
177199

200+
/* The trace function is called with a 'return' trace event after the
201+
* execution of a yield statement. */
202+
assert(f->f_lasti!=-1);
203+
if (code[f->f_lasti]==YIELD_VALUE||code[f->f_lasti]==YIELD_FROM) {
204+
PyErr_SetString(PyExc_ValueError,
205+
"can't jump from a yield statement");
206+
return-1;
207+
}
208+
178209
/* You can't jump onto a line with an 'except' statement on it -
179210
* they expect to have an exception on the top of the stack, which
180211
* won't be true if you jump to them. They always start with code

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2026 Movatter.jp