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

Commite32bbaf

Browse files
xdegayeserhiy-storchaka
authored andcommitted
[3.7]bpo-17288: Prevent jumps from 'return' and 'exception' trace events. (GH-5928)
1 parentb4c8871 commite32bbaf

File tree

3 files changed

+94
-15
lines changed

3 files changed

+94
-15
lines changed

‎Lib/test/test_sys_settrace.py‎

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

559-
def__init__(self,function,jumpFrom,jumpTo):
560-
self.function=function
559+
def__init__(self,function,jumpFrom,jumpTo,event='line',
560+
decorated=False):
561+
self.code=function.__code__
561562
self.jumpFrom=jumpFrom
562563
self.jumpTo=jumpTo
564+
self.event=event
565+
self.firstLine=Noneifdecoratedelseself.code.co_firstlineno
563566
self.done=False
564567

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

612-
defrun_test(self,func,jumpFrom,jumpTo,expected,error=None):
613-
tracer=JumpTracer(func,jumpFrom,jumpTo)
627+
defrun_test(self,func,jumpFrom,jumpTo,expected,error=None,
628+
event='line',decorated=False):
629+
tracer=JumpTracer(func,jumpFrom,jumpTo,event,decorated)
614630
sys.settrace(tracer.trace)
615631
output= []
616632
iferrorisNone:
@@ -621,15 +637,15 @@ def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
621637
sys.settrace(None)
622638
self.compare_jump_output(expected,output)
623639

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

@@ -1104,6 +1120,36 @@ class fake_function:
11041120
sys.settrace(None)
11051121
self.compare_jump_output([2,3,2,3,4],namespace["output"])
11061122

1123+
@jump_test(2,3, [1],event='call',error=(ValueError,"can't jump from"
1124+
" the 'call' trace event of a new frame"))
1125+
deftest_no_jump_from_call(output):
1126+
output.append(1)
1127+
defnested():
1128+
output.append(3)
1129+
nested()
1130+
output.append(5)
1131+
1132+
@jump_test(2,1, [1],event='return',error=(ValueError,
1133+
"can only jump from a 'line' trace event"))
1134+
deftest_no_jump_from_return_event(output):
1135+
output.append(1)
1136+
return
1137+
1138+
@jump_test(2,1, [1],event='exception',error=(ValueError,
1139+
"can only jump from a 'line' trace event"))
1140+
deftest_no_jump_from_exception_event(output):
1141+
output.append(1)
1142+
1/0
1143+
1144+
@jump_test(3,2, [2],event='return',error=(ValueError,
1145+
"can't jump from a yield statement"))
1146+
deftest_no_jump_from_yield(output):
1147+
defgen():
1148+
output.append(2)
1149+
yield3
1150+
next(gen())
1151+
output.append(5)
1152+
11071153

11081154
if__name__=="__main__":
11091155
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: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ frame_getlineno(PyFrameObject *f, void *closure)
5959
* o 'try'/'for'/'while' blocks can't be jumped into because the blockstack
6060
* needs to be set up before their code runs, and for 'for' loops the
6161
* iterator needs to be on the stack.
62+
* o Jumps cannot be made from within a trace function invoked with a
63+
* 'return' or 'exception' event since the eval loop has been exited at
64+
* that time.
6265
*/
6366
staticint
6467
frame_setlineno(PyFrameObject*f,PyObject*p_new_lineno)
@@ -94,13 +97,32 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
9497
return-1;
9598
}
9699

100+
/* Upon the 'call' trace event of a new frame, f->f_lasti is -1 and
101+
* f->f_trace is NULL, check first on the first condition.
102+
* Forbidding jumps from the 'call' event of a new frame is a side effect
103+
* of allowing to set f_lineno only from trace functions. */
104+
if (f->f_lasti==-1) {
105+
PyErr_Format(PyExc_ValueError,
106+
"can't jump from the 'call' trace event of a new frame");
107+
return-1;
108+
}
109+
97110
/* You can only do this from within a trace function, not via
98111
* _getframe or similar hackery. */
99-
if (!f->f_trace)
100-
{
112+
if (!f->f_trace) {
101113
PyErr_Format(PyExc_ValueError,
102-
"f_lineno can only be set by a"
103-
" line trace function");
114+
"f_lineno can only be set by a trace function");
115+
return-1;
116+
}
117+
118+
/* Forbid jumps upon a 'return' trace event (except after executing a
119+
* YIELD_VALUE or YIELD_FROM opcode, f_stacktop is not NULL in that case)
120+
* and upon an 'exception' trace event.
121+
* Jumps from 'call' trace events have already been forbidden above for new
122+
* frames, so this check does not change anything for 'call' events. */
123+
if (f->f_stacktop==NULL) {
124+
PyErr_SetString(PyExc_ValueError,
125+
"can only jump from a 'line' trace event");
104126
return-1;
105127
}
106128

@@ -159,6 +181,16 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
159181

160182
/* We're now ready to look at the bytecode. */
161183
PyBytes_AsStringAndSize(f->f_code->co_code, (char**)&code,&code_len);
184+
185+
/* The trace function is called with a 'return' trace event after the
186+
* execution of a yield statement. */
187+
assert(f->f_lasti!=-1);
188+
if (code[f->f_lasti]==YIELD_VALUE||code[f->f_lasti]==YIELD_FROM) {
189+
PyErr_SetString(PyExc_ValueError,
190+
"can't jump from a yield statement");
191+
return-1;
192+
}
193+
162194
min_addr=Py_MIN(new_lasti,f->f_lasti);
163195
max_addr=Py_MAX(new_lasti,f->f_lasti);
164196

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2026 Movatter.jp