Skip to content

Commit 362088d

Browse files
Do not create cells in PyFrame_LocalsToFast().
1 parent cd2508f commit 362088d

File tree

5 files changed

+171
-63
lines changed

5 files changed

+171
-63
lines changed

Include/internal/pycore_frame.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ _PyFrame_GetBuiltins(PyFrameObject *f)
3232

3333
int _PyFrame_TakeLocals(PyFrameObject *f);
3434

35+
PyAPI_FUNC(int) _PyFrame_OpAlreadyRan(PyFrameObject *f, int opcode, int oparg);
36+
3537
#ifdef __cplusplus
3638
}
3739
#endif

Lib/test/test_scope.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,57 @@ def bar():
176176
self.assertEqual(foo(a=42), 50)
177177
self.assertEqual(foo(), 25)
178178

179+
def testCellIsArgAndEscapes(self):
180+
# We need to be sure that a cell passed in as an arg still
181+
# gets wrapped in a new cell if the arg escapes into an
182+
# inner function (closure).
183+
184+
def external():
185+
value = 42
186+
def inner():
187+
return value
188+
cell, = inner.__closure__
189+
return cell
190+
cell_ext = external()
191+
192+
def spam(arg):
193+
def eggs():
194+
return arg
195+
return eggs
196+
197+
eggs = spam(cell_ext)
198+
cell_closure, = eggs.__closure__
199+
cell_eggs = eggs()
200+
201+
self.assertIs(cell_eggs, cell_ext)
202+
self.assertIsNot(cell_eggs, cell_closure)
203+
204+
def testCellIsLocalAndEscapes(self):
205+
# We need to be sure that a cell bound to a local still
206+
# gets wrapped in a new cell if the local escapes into an
207+
# inner function (closure).
208+
209+
def external():
210+
value = 42
211+
def inner():
212+
return value
213+
cell, = inner.__closure__
214+
return cell
215+
cell_ext = external()
216+
217+
def spam(arg):
218+
cell = arg
219+
def eggs():
220+
return cell
221+
return eggs
222+
223+
eggs = spam(cell_ext)
224+
cell_closure, = eggs.__closure__
225+
cell_eggs = eggs()
226+
227+
self.assertIs(cell_eggs, cell_ext)
228+
self.assertIsNot(cell_eggs, cell_closure)
229+
179230
def testRecursion(self):
180231

181232
def f(x):

Objects/frameobject.c

Lines changed: 98 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,19 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
918918
return f;
919919
}
920920

921+
int
922+
_PyFrame_OpAlreadyRan(PyFrameObject *f, int opcode, int oparg)
923+
{
924+
const _Py_CODEUNIT *code =
925+
(const _Py_CODEUNIT *)PyBytes_AS_STRING(f->f_code->co_code);
926+
for (int i = 0; i < f->f_lasti; i++) {
927+
if (_Py_OPCODE(code[i]) == opcode && _Py_OPARG(code[i]) == oparg) {
928+
return 1;
929+
}
930+
}
931+
return 0;
932+
}
933+
921934
int
922935
PyFrame_FastToLocalsWithError(PyFrameObject *f)
923936
{
@@ -966,32 +979,40 @@ PyFrame_FastToLocalsWithError(PyFrameObject *f)
966979

967980
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
968981
PyObject *value = fast[i];
969-
if (kind & (CO_FAST_CELL | CO_FAST_FREE)) {
982+
int cellargoffset = CO_CELL_NOT_AN_ARG;
983+
if (co->co_cell2arg != NULL) {
984+
cellargoffset = co->co_cell2arg[i - co->co_nlocals];
985+
}
986+
if (kind & CO_FAST_FREE) {
987+
// The cell was set by _PyEval_MakeFrameVector() from
988+
// the function's closure.
989+
assert(value != NULL && PyCell_Check(value));
990+
value = PyCell_GET(value);
991+
}
992+
else if (kind & CO_FAST_CELL) {
993+
// Note that no *_DEREF ops can happen before MAKE_CELL
994+
// executes. So there's no need to duplicate the work
995+
// that MAKE_CELL would otherwise do later, if it hasn't
996+
// run yet.
970997
if (value != NULL) {
971-
// MAKE_CELL must have executed already.
972-
assert(PyCell_Check(value));
973-
value = PyCell_GET(value);
998+
if (PyCell_Check(value) &&
999+
_PyFrame_OpAlreadyRan(f, MAKE_CELL, i)) {
1000+
// (likely) MAKE_CELL must have executed already.
1001+
value = PyCell_GET(value);
1002+
}
1003+
// (unlikely) Otherwise it must be an initial value set
1004+
// by an earlier call to PyFrame_FastToLocals().
9741005
}
9751006
else {
976-
// MAKE_CELL hasn't executed yet. (This is unlikely.)
977-
PyObject *cell = PyCell_New(NULL);
978-
if (cell == NULL) {
979-
PyErr_Clear();
980-
}
981-
else {
982-
if (co->co_cell2arg != NULL) {
983-
int argoffset = co->co_cell2arg[i - co->co_nlocals];
984-
if (argoffset != CO_CELL_NOT_AN_ARG) {
985-
// It will have been set in initialize_locals().
986-
value = fast[argoffset];
987-
assert(value != NULL);
988-
Py_INCREF(value);
989-
PyCell_SET(cell, value);
990-
// Clear the arg slot.
991-
Py_SETREF(fast[argoffset], NULL);
992-
}
993-
}
994-
fast[i] = cell;
1007+
// (unlikely) MAKE_CELL hasn't executed yet.
1008+
if (cellargoffset != CO_CELL_NOT_AN_ARG) {
1009+
// It is an arg that escapes into an inner
1010+
// function so we use the initial value that
1011+
// was already set by _PyEval_MakeFrameVector().
1012+
// Normally the arg value would always be set.
1013+
// However, it can be NULL if it was deleted via
1014+
// PyFrame_LocalsToFast().
1015+
value = fast[cellargoffset];
9951016
}
9961017
}
9971018
}
@@ -1064,36 +1085,66 @@ PyFrame_LocalsToFast(PyFrameObject *f, int clear)
10641085
}
10651086
}
10661087
PyObject *oldvalue = fast[i];
1067-
if (kind & (CO_FAST_CELL | CO_FAST_FREE)) {
1068-
if (oldvalue != NULL) {
1069-
// MAKE_CELL must have executed already.
1088+
int cellargoffset = CO_CELL_NOT_AN_ARG;
1089+
if (co->co_cell2arg != NULL) {
1090+
cellargoffset = co->co_cell2arg[i - co->co_nlocals];
1091+
}
1092+
PyObject *cell = NULL;
1093+
if (kind == CO_FAST_FREE) {
1094+
// The cell was cell by _PyEval_MakeFrameVector() from
1095+
// the function's closure.
1096+
assert(oldvalue != NULL && PyCell_Check(oldvalue));
1097+
cell = oldvalue;
1098+
}
1099+
else if (kind & CO_FAST_CELL && oldvalue != NULL) {
1100+
if (cellargoffset != CO_CELL_NOT_AN_ARG) {
1101+
// (likely) MAKE_CELL must have executed already.
1102+
// It's the cell for an arg.
10701103
assert(PyCell_Check(oldvalue));
1071-
Py_XDECREF(PyCell_GET(oldvalue));
1072-
Py_XINCREF(value);
1073-
PyCell_SET(oldvalue, value);
1104+
cell = oldvalue;
10741105
}
10751106
else {
1076-
// MAKE_CELL hasn't executed yet. (This is unlikely.)
1077-
PyObject *cell = PyCell_New(value);
1078-
if (cell == NULL) {
1079-
PyErr_Clear();
1107+
if (PyCell_Check(oldvalue) &&
1108+
_PyFrame_OpAlreadyRan(f, MAKE_CELL, i)) {
1109+
// (likely) MAKE_CELL must have executed already.
1110+
cell = oldvalue;
10801111
}
1081-
else {
1082-
fast[i] = cell;
1083-
// Clear the corresponding arg if there is one.
1084-
if (co->co_cell2arg != NULL) {
1085-
int argoffset = co->co_cell2arg[i - co->co_nlocals];
1086-
if (argoffset != CO_CELL_NOT_AN_ARG) {
1087-
// It will have been set in initialize_locals().
1088-
assert(fast[argoffset] != NULL);
1089-
Py_SETREF(fast[argoffset], NULL);
1090-
}
1091-
}
1112+
// (unlikely) Otherwise, it must have been set to some
1113+
// initial value by an earlier call to PyFrame_LocalsToFast().
1114+
}
1115+
}
1116+
if (cell != NULL) {
1117+
PyObject *oldvalue = PyCell_GET(cell);
1118+
if (value != oldvalue) {
1119+
Py_XDECREF(oldvalue);
1120+
Py_XINCREF(value);
1121+
PyCell_SET(cell, value);
1122+
}
1123+
}
1124+
else {
1125+
int offset = i;
1126+
if (kind & CO_FAST_CELL) {
1127+
// (unlikely) MAKE_CELL hasn't executed yet.
1128+
// Note that there is no need to create the cell that
1129+
// MAKE_CELL would otherwise create later, since no
1130+
// *_DEREF ops can happen before MAKE_CELL has run.
1131+
if (cellargoffset != CO_CELL_NOT_AN_ARG) {
1132+
// It's the cell for an arg.
1133+
// Replace the initial value that was set by
1134+
// _PyEval_MakeFrameVector().
1135+
// Normally the arg value would always be set.
1136+
// However, it can be NULL if it was deleted
1137+
// via an earlier PyFrame_LocalsToFast() call.
1138+
offset = cellargoffset;
1139+
oldvalue = fast[offset];
10921140
}
1141+
// Otherwise set an initial value for MAKE_CELL to use
1142+
// when it runs later.
1143+
}
1144+
if (value != oldvalue) {
1145+
Py_XINCREF(value);
1146+
Py_XSETREF(fast[offset], value);
10931147
}
1094-
} else if (fast[i] != value) {
1095-
Py_XINCREF(value);
1096-
Py_XSETREF(oldvalue, value);
10971148
}
10981149
Py_XDECREF(value);
10991150
}

Objects/typeobject.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include "pycore_pystate.h" // _PyThreadState_GET()
1212
#include "pycore_unionobject.h" // _Py_Union(), _Py_union_type_or
1313
#include "frameobject.h"
14+
#include "pycore_frame.h" // _PyFrame_OpAlreadyRan
15+
#include "opcode.h" // MAKE_CELL
1416
#include "structmember.h" // PyMemberDef
1517

1618
#include <ctype.h>
@@ -8883,8 +8885,10 @@ super_init_without_args(PyFrameObject *f, PyCodeObject *co,
88838885
for (i = 0; i < co->co_ncellvars; i++) {
88848886
if (co->co_cell2arg[i] == 0) {
88858887
PyObject *cell = f->f_localsptr[co->co_nlocals + i];
8886-
assert(PyCell_Check(cell));
8887-
obj = PyCell_GET(cell);
8888+
if (PyCell_Check(cell) &&
8889+
_PyFrame_OpAlreadyRan(f, MAKE_CELL, 0)) {
8890+
obj = PyCell_GET(cell);
8891+
}
88888892
break;
88898893
}
88908894
}

Python/ceval.c

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3077,27 +3077,27 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
30773077
}
30783078

30793079
case TARGET(MAKE_CELL): {
3080-
if (GETLOCAL(oparg) != NULL) {
3081-
// A cell must have been set in PyFrame_LocalsToFast().
3082-
// (This is unlikely to happen.)
3083-
assert(PyCell_Check(GETLOCAL(oparg)));
3084-
DISPATCH();
3085-
}
3086-
PyObject *cell = PyCell_New(NULL);
3080+
PyObject *initial = GETLOCAL(oparg);
3081+
// Normally initial would be NULL. However, it
3082+
// might have been set to an initial value during
3083+
// a call to PyFrame_LocalsToFast().
3084+
PyObject *cell = PyCell_New(initial);
30873085
if (cell == NULL) {
30883086
goto error;
30893087
}
30903088
/* If it is an arg then copy the arg into the cell. */
3091-
if (co->co_cell2arg != NULL) {
3089+
if (initial == NULL && co->co_cell2arg != NULL) {
30923090
int argoffset = co->co_cell2arg[oparg - co->co_nlocals];
30933091
if (argoffset != CO_CELL_NOT_AN_ARG) {
30943092
PyObject *arg = GETLOCAL(argoffset);
3095-
// It will have been set in initialize_locals().
3096-
assert(arg != NULL);
3097-
Py_INCREF(arg);
3098-
PyCell_SET(cell, arg);
3099-
/* Clear the local copy. */
3100-
SETLOCAL(argoffset, NULL);
3093+
// It will have been set in initialize_locals() but
3094+
// may have been deleted PyFrame_LocalsToFast().
3095+
if (arg != NULL) {;
3096+
Py_INCREF(arg);
3097+
PyCell_SET(cell, arg);
3098+
/* Clear the local copy. */
3099+
SETLOCAL(argoffset, NULL);
3100+
}
31013101
}
31023102
}
31033103
SETLOCAL(oparg, cell);

0 commit comments

Comments
 (0)