-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
gh-120906: Support arbitrary hashable keys in FrameLocalsProxy #122309
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
56b7a2d
791e4fc
03dc148
ddc4384
4bba239
d37d8f2
f5df751
34c3d24
7d3e8b5
6927ae0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
:attr:`frame.f_locals` now supports arbitrary hashable objects as keys. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,22 +53,27 @@ static int | |
framelocalsproxy_getkeyindex(PyFrameObject *frame, PyObject* key, bool read) | ||
{ | ||
/* | ||
* Returns the fast locals index of the key | ||
* Returns -2 (!) if an error occurred; exception will be set. | ||
* Returns the fast locals index of the key on success: | ||
* - if read == true, returns the index if the value is not NULL | ||
* - if read == false, returns the index if the value is not hidden | ||
* Otherwise returns -1. | ||
*/ | ||
|
||
assert(PyUnicode_CheckExact(key)); | ||
|
||
PyCodeObject *co = _PyFrame_GetCode(frame->f_frame); | ||
int found_key = false; | ||
|
||
// Ensure that the key is hashable. | ||
Py_hash_t key_hash = PyObject_Hash(key); | ||
if (key_hash == -1) { | ||
return -2; | ||
} | ||
bool found = false; | ||
|
||
// We do 2 loops here because it's highly possible the key is interned | ||
// and we can do a pointer comparison. | ||
for (int i = 0; i < co->co_nlocalsplus; i++) { | ||
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i); | ||
if (name == key) { | ||
found_key = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you removed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The rest of the PR LGTM. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, thanks for the catch! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you check the change? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The change looks correct to me (with the search flag restored, the I did suggest an explanatory comment on the early return, but that's definitely not a merge blocker. So even if @gaogaotiantian doesn't get time to take a look, I think this is good to go for rc1. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Capturing my understanding of the
Future versions could revisit that design if debugger developers ask for it to change, but live editing loop variables seems sufficiently fraught with peril that I'll be genuinely surprised if anyone ever notices this small discrepancy between module level comprehensions and function level ones. |
||
if (read) { | ||
if (framelocalsproxy_getval(frame->f_frame, co, i) != NULL) { | ||
return i; | ||
|
@@ -78,23 +83,35 @@ framelocalsproxy_getkeyindex(PyFrameObject *frame, PyObject* key, bool read) | |
return i; | ||
} | ||
} | ||
found = true; | ||
} | ||
} | ||
|
||
if (!found_key) { | ||
// This is unlikely, but we need to make sure. This means the key | ||
// is not interned. | ||
for (int i = 0; i < co->co_nlocalsplus; i++) { | ||
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i); | ||
if (_PyUnicode_EQ(name, key)) { | ||
if (read) { | ||
if (framelocalsproxy_getval(frame->f_frame, co, i) != NULL) { | ||
return i; | ||
} | ||
} else { | ||
if (!(_PyLocals_GetKind(co->co_localspluskinds, i) & CO_FAST_HIDDEN)) { | ||
return i; | ||
} | ||
if (found) { | ||
// This is an attempt to read an unset local variable or | ||
// write to a variable that is hidden from regular write operations | ||
return -1; | ||
encukou marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
// This is unlikely, but we need to make sure. This means the key | ||
// is not interned. | ||
for (int i = 0; i < co->co_nlocalsplus; i++) { | ||
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i); | ||
Py_hash_t name_hash = PyObject_Hash(name); | ||
assert(name_hash != -1); // keys are exact unicode | ||
if (name_hash != key_hash) { | ||
continue; | ||
} | ||
int same = PyObject_RichCompareBool(name, key, Py_EQ); | ||
if (same < 0) { | ||
return -2; | ||
} | ||
if (same) { | ||
if (read) { | ||
if (framelocalsproxy_getval(frame->f_frame, co, i) != NULL) { | ||
return i; | ||
} | ||
} else { | ||
if (!(_PyLocals_GetKind(co->co_localspluskinds, i) & CO_FAST_HIDDEN)) { | ||
return i; | ||
} | ||
} | ||
} | ||
|
@@ -109,13 +126,14 @@ framelocalsproxy_getitem(PyObject *self, PyObject *key) | |
PyFrameObject* frame = ((PyFrameLocalsProxyObject*)self)->frame; | ||
PyCodeObject* co = _PyFrame_GetCode(frame->f_frame); | ||
|
||
if (PyUnicode_CheckExact(key)) { | ||
int i = framelocalsproxy_getkeyindex(frame, key, true); | ||
if (i >= 0) { | ||
PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i); | ||
assert(value != NULL); | ||
return Py_NewRef(value); | ||
} | ||
int i = framelocalsproxy_getkeyindex(frame, key, true); | ||
if (i == -2) { | ||
return NULL; | ||
} | ||
if (i >= 0) { | ||
PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i); | ||
assert(value != NULL); | ||
return Py_NewRef(value); | ||
} | ||
|
||
// Okay not in the fast locals, try extra locals | ||
|
@@ -145,37 +163,38 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value) | |
return -1; | ||
} | ||
|
||
if (PyUnicode_CheckExact(key)) { | ||
int i = framelocalsproxy_getkeyindex(frame, key, false); | ||
if (i >= 0) { | ||
_Py_Executors_InvalidateDependency(PyInterpreterState_Get(), co, 1); | ||
|
||
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i); | ||
_PyStackRef oldvalue = fast[i]; | ||
PyObject *cell = NULL; | ||
if (kind == CO_FAST_FREE) { | ||
// The cell was set when the frame was created from | ||
// the function's closure. | ||
assert(oldvalue.bits != 0 && PyCell_Check(PyStackRef_AsPyObjectBorrow(oldvalue))); | ||
cell = PyStackRef_AsPyObjectBorrow(oldvalue); | ||
} else if (kind & CO_FAST_CELL && oldvalue.bits != 0) { | ||
PyObject *as_obj = PyStackRef_AsPyObjectBorrow(oldvalue); | ||
if (PyCell_Check(as_obj)) { | ||
cell = as_obj; | ||
} | ||
int i = framelocalsproxy_getkeyindex(frame, key, false); | ||
if (i == -2) { | ||
return -1; | ||
} | ||
if (i >= 0) { | ||
_Py_Executors_InvalidateDependency(PyInterpreterState_Get(), co, 1); | ||
|
||
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i); | ||
_PyStackRef oldvalue = fast[i]; | ||
PyObject *cell = NULL; | ||
if (kind == CO_FAST_FREE) { | ||
// The cell was set when the frame was created from | ||
// the function's closure. | ||
assert(oldvalue.bits != 0 && PyCell_Check(PyStackRef_AsPyObjectBorrow(oldvalue))); | ||
cell = PyStackRef_AsPyObjectBorrow(oldvalue); | ||
} else if (kind & CO_FAST_CELL && oldvalue.bits != 0) { | ||
PyObject *as_obj = PyStackRef_AsPyObjectBorrow(oldvalue); | ||
if (PyCell_Check(as_obj)) { | ||
cell = as_obj; | ||
} | ||
if (cell != NULL) { | ||
PyObject *oldvalue_o = PyCell_GET(cell); | ||
if (value != oldvalue_o) { | ||
PyCell_SET(cell, Py_XNewRef(value)); | ||
Py_XDECREF(oldvalue_o); | ||
} | ||
} else if (value != PyStackRef_AsPyObjectBorrow(oldvalue)) { | ||
PyStackRef_XCLOSE(fast[i]); | ||
fast[i] = PyStackRef_FromPyObjectNew(value); | ||
} | ||
if (cell != NULL) { | ||
PyObject *oldvalue_o = PyCell_GET(cell); | ||
if (value != oldvalue_o) { | ||
PyCell_SET(cell, Py_XNewRef(value)); | ||
Py_XDECREF(oldvalue_o); | ||
} | ||
return 0; | ||
} else if (value != PyStackRef_AsPyObjectBorrow(oldvalue)) { | ||
PyStackRef_XCLOSE(fast[i]); | ||
fast[i] = PyStackRef_FromPyObjectNew(value); | ||
} | ||
return 0; | ||
} | ||
|
||
// Okay not in the fast locals, try extra locals | ||
|
@@ -545,11 +564,12 @@ framelocalsproxy_contains(PyObject *self, PyObject *key) | |
{ | ||
PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame; | ||
|
||
if (PyUnicode_CheckExact(key)) { | ||
int i = framelocalsproxy_getkeyindex(frame, key, true); | ||
if (i >= 0) { | ||
return 1; | ||
} | ||
int i = framelocalsproxy_getkeyindex(frame, key, true); | ||
if (i == -2) { | ||
return -1; | ||
} | ||
if (i >= 0) { | ||
return 1; | ||
} | ||
|
||
PyObject *extra = ((PyFrameObject*)frame)->f_extra_locals; | ||
|
Uh oh!
There was an error while loading. Please reload this page.