-
-
Notifications
You must be signed in to change notification settings - Fork 31.3k
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
Crash in OrderedDict similiar to #83771 #119004
Comments
this seems to work correctly in 3.12.3+ and Python 3.13.0a6+ for me (ubuntu Jammy)
it also works correctly on main: Python 3.14.0a0 (heads/main:7d7eec595a, May 13 2024, 17:38:47) |
I can reproduce locally on 7d7eec5 with Output on macos:
The problem happens here: Lines 819 to 822 in 7d7eec5
|
I think we can add something like if (di->di_odict->od_state != di->di_state) {
PyErr_SetString(PyExc_RuntimeError,
"OrderedDict mutated during iteration");
goto done;
} 🤔 |
I came up with something like » git patch
diff --git Objects/odictobject.c Objects/odictobject.c
index 53f64fc81e7..df5fe259d39 100644
--- Objects/odictobject.c
+++ Objects/odictobject.c
@@ -806,6 +806,11 @@ _odict_keys_equal(PyODictObject *a, PyODictObject *b)
{
_ODictNode *node_a, *node_b;
+ size_t state_a = a->od_state;
+ size_t state_b = b->od_state;
+ Py_ssize_t size_a = PyODict_SIZE(a);
+ Py_ssize_t size_b = PyODict_SIZE(b);
+
node_a = _odict_FIRST(a);
node_b = _odict_FIRST(b);
while (1) {
@@ -820,10 +825,22 @@ _odict_keys_equal(PyODictObject *a, PyODictObject *b)
(PyObject *)_odictnode_KEY(node_a),
(PyObject *)_odictnode_KEY(node_b),
Py_EQ);
- if (res < 0)
+ if (res < 0) {
return res;
- else if (res == 0)
+ }
+ else if (a->od_state != state_a || b->od_state != state_b) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "OrderedDict mutated during iteration");
+ return -1;
+ }
+ else if (size_a != PyODict_SIZE(a) || size_b != PyODict_SIZE(b)) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "OrderedDict changed size during iteration");
+ return -1;
+ }
+ else if (res == 0) {
return 0;
+ }
/* otherwise it must match, so move on to the next one */
node_a = _odictnode_NEXT(node_a);
I am not sure that this is a correct and/or optimal solution though. It raises an error like so: » PYTHONFAULTHANDLER=1 ./python.exe ex.py
0
1
cleared l
Traceback (most recent call last):
File "/Users/sobolev/Desktop/cpython2/ex.py", line 24, in <module>
print(l == r)
^^^^^^
RuntimeError: OrderedDict changed size during iteration |
@sobolevn I think your solution is the only one that is correct. while (1) {
if (node_a == NULL && node_b == NULL)
/* success: hit the end of each at the same time */
return 1;
else if (node_a == NULL || node_b == NULL)
/* unequal length */
return 0;
else {
Py_INCREF(_odictnode_KEY(node_a));
Py_INCREF(_odictnode_KEY(node_b));
int res = PyObject_RichCompareBool(
(PyObject *)_odictnode_KEY(node_a),
(PyObject *)_odictnode_KEY(node_b),
Py_EQ);
if (res < 0)
return res;
else if (res == 0)
return 0;
/* otherwise it must match, so move on to the next one */
node_a = _odictnode_NEXT(node_a);
node_b = _odictnode_NEXT(node_b);
}
} The first iteration of this loop will complete without any errors (actually clearing our OrderedDict). So, after this iteration our |
@Eclips4 yeap, this was the first thing I tried. |
pythonGH-121329) (cherry picked from commit 38a887d) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
pythonGH-121329) (cherry picked from commit 38a887d) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Bug report
Bug description:
CPython versions tested on:
3.10, 3.13
Operating systems tested on:
Linux
Linked PRs
OrderedDict
#121329OrderedDict
(GH-121329) #124507OrderedDict
(GH-121329) #124508The text was updated successfully, but these errors were encountered: