Skip to content

Commit 1ef26c5

Browse files
committed
Create new references to fast locals overwritten via f_locals
These may provide support for borrowed references contained in frames closer to the top of the call stack. Add them to a list attached to the frame when they are overwritten, to be destroyed when the frame is destroyed.
1 parent a12ccd9 commit 1ef26c5

File tree

4 files changed

+38
-2
lines changed

4 files changed

+38
-2
lines changed

Include/internal/pycore_frame.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ struct _frame {
3131
PyEval_GetLocals requires a borrowed reference so the actual reference
3232
is stored here */
3333
PyObject *f_locals_cache;
34+
/* A list containing strong references to fast locals that were overwritten
35+
* via f_locals. Borrowed references to these locals may exist in frames
36+
* closer to the top of the stack. The references in this list act as
37+
* "support" for the borrowed references, ensuring that they remain valid.
38+
*/
39+
PyObject *f_overwritten_fast_locals;
3440
/* The frame data, if this frame object owns the frame */
3541
PyObject *_f_frame_data[1];
3642
};

Lib/test/test_frame.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,20 @@ def make_frame():
597597
with self.assertRaises(TypeError):
598598
FrameLocalsProxy(frame=sys._getframe()) # no keyword arguments
599599

600+
def test_overwrite_locals(self):
601+
# Verify we do not crash if we overwrite a local passed as an argument
602+
# from an ancestor in the call stack.
603+
def f():
604+
xs = [1, 2, 3]
605+
return g(xs)
606+
607+
def g(xs):
608+
f = sys._getframe()
609+
f.f_back.f_locals["xs"] = None
610+
return xs[1]
611+
612+
self.assertEqual(f(), 2)
613+
600614

601615
class FrameLocalsProxyMappingTests(mapping_tests.TestHashMappingProtocol):
602616
"""Test that FrameLocalsProxy behaves like a Mapping (with exceptions)"""

Lib/test/test_sys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1643,7 +1643,7 @@ def func():
16431643
INTERPRETER_FRAME = '9PihcP'
16441644
else:
16451645
INTERPRETER_FRAME = '9PhcP'
1646-
check(x, size('3PiccPP' + INTERPRETER_FRAME + 'P'))
1646+
check(x, size('3PiccPPP' + INTERPRETER_FRAME + 'P'))
16471647
# function
16481648
def func(): pass
16491649
check(func, size('16Pi'))

Objects/frameobject.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,19 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value)
243243
Py_XINCREF(value);
244244
PyCell_SetTakeRef((PyCellObject *)cell, value);
245245
} else if (value != PyStackRef_AsPyObjectBorrow(oldvalue)) {
246-
PyStackRef_XCLOSE(fast[i]);
246+
if (!PyStackRef_IsNull(fast[i])) {
247+
if (frame->f_overwritten_fast_locals == NULL) {
248+
frame->f_overwritten_fast_locals = PyList_New(0);
249+
if (frame->f_overwritten_fast_locals == NULL) {
250+
return -1;
251+
}
252+
}
253+
PyObject *obj = PyStackRef_AsPyObjectBorrow(fast[i]);
254+
if (PyList_Append(frame->f_overwritten_fast_locals, obj) < 0) {
255+
return -1;
256+
}
257+
PyStackRef_CLOSE(fast[i]);
258+
}
247259
fast[i] = PyStackRef_FromPyObjectNew(value);
248260
}
249261
return 0;
@@ -1806,6 +1818,7 @@ frame_dealloc(PyObject *op)
18061818
Py_CLEAR(f->f_trace);
18071819
Py_CLEAR(f->f_extra_locals);
18081820
Py_CLEAR(f->f_locals_cache);
1821+
Py_CLEAR(f->f_overwritten_fast_locals);
18091822
PyObject_GC_Del(f);
18101823
Py_TRASHCAN_END;
18111824
}
@@ -1818,6 +1831,7 @@ frame_traverse(PyObject *op, visitproc visit, void *arg)
18181831
Py_VISIT(f->f_trace);
18191832
Py_VISIT(f->f_extra_locals);
18201833
Py_VISIT(f->f_locals_cache);
1834+
Py_VISIT(f->f_overwritten_fast_locals);
18211835
if (f->f_frame->owner != FRAME_OWNED_BY_FRAME_OBJECT) {
18221836
return 0;
18231837
}
@@ -1832,6 +1846,7 @@ frame_tp_clear(PyObject *op)
18321846
Py_CLEAR(f->f_trace);
18331847
Py_CLEAR(f->f_extra_locals);
18341848
Py_CLEAR(f->f_locals_cache);
1849+
Py_CLEAR(f->f_overwritten_fast_locals);
18351850

18361851
/* locals and stack */
18371852
_PyStackRef *locals = _PyFrame_GetLocalsArray(f->f_frame);
@@ -1973,6 +1988,7 @@ _PyFrame_New_NoTrack(PyCodeObject *code)
19731988
f->f_lineno = 0;
19741989
f->f_extra_locals = NULL;
19751990
f->f_locals_cache = NULL;
1991+
f->f_overwritten_fast_locals = NULL;
19761992
return f;
19771993
}
19781994

0 commit comments

Comments
 (0)