diff options
author | Jelle Zijlstra <jelle.zijlstra@gmail.com> | 2024-06-25 15:12:11 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-25 15:12:11 (GMT) |
commit | 42b2c9d78da7ebd6bd5925a4d4c78aec3c9e78e6 (patch) | |
tree | 81f094d81e73f1c2729528984b9ec47b0712d2d0 /Python/Python-ast.c | |
parent | ead676516d4687b5e6d947a5c0808b5b93ba87e3 (diff) | |
download | cpython-42b2c9d78da7ebd6bd5925a4d4c78aec3c9e78e6.zip cpython-42b2c9d78da7ebd6bd5925a4d4c78aec3c9e78e6.tar.gz cpython-42b2c9d78da7ebd6bd5925a4d4c78aec3c9e78e6.tar.bz2 |
gh-120108: Fix deepcopying of AST trees with .parent attributes (#120114)
Diffstat (limited to 'Python/Python-ast.c')
-rw-r--r-- | Python/Python-ast.c | 31 |
1 files changed, 14 insertions, 17 deletions
diff --git a/Python/Python-ast.c b/Python/Python-ast.c index 7aa1c51..01ffea1 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -5263,17 +5263,22 @@ ast_type_reduce(PyObject *self, PyObject *unused) return NULL; } - PyObject *dict = NULL, *fields = NULL, *remaining_fields = NULL, - *remaining_dict = NULL, *positional_args = NULL; + PyObject *dict = NULL, *fields = NULL, *positional_args = NULL; if (PyObject_GetOptionalAttr(self, state->__dict__, &dict) < 0) { return NULL; } PyObject *result = NULL; if (dict) { - // Serialize the fields as positional args if possible, because if we - // serialize them as a dict, during unpickling they are set only *after* - // the object is constructed, which will now trigger a DeprecationWarning - // if the AST type has required fields. + // Unpickling (or copying) works as follows: + // - Construct the object with only positional arguments + // - Set the fields from the dict + // We have two constraints: + // - We must set all the required fields in the initial constructor call, + // or the unpickling or deepcopying of the object will trigger DeprecationWarnings. + // - We must not include child nodes in the positional args, because + // that may trigger runaway recursion during copying (gh-120108). + // To satisfy both constraints, we set all the fields to None in the + // initial list of positional args, and then set the fields from the dict. if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields) < 0) { goto cleanup; } @@ -5283,11 +5288,6 @@ ast_type_reduce(PyObject *self, PyObject *unused) Py_DECREF(dict); goto cleanup; } - remaining_dict = PyDict_Copy(dict); - Py_DECREF(dict); - if (!remaining_dict) { - goto cleanup; - } positional_args = PyList_New(0); if (!positional_args) { goto cleanup; @@ -5298,7 +5298,7 @@ ast_type_reduce(PyObject *self, PyObject *unused) goto cleanup; } PyObject *value; - int rc = PyDict_Pop(remaining_dict, name, &value); + int rc = PyDict_GetItemRef(dict, name, &value); Py_DECREF(name); if (rc < 0) { goto cleanup; @@ -5306,7 +5306,7 @@ ast_type_reduce(PyObject *self, PyObject *unused) if (!value) { break; } - rc = PyList_Append(positional_args, value); + rc = PyList_Append(positional_args, Py_None); Py_DECREF(value); if (rc < 0) { goto cleanup; @@ -5316,8 +5316,7 @@ ast_type_reduce(PyObject *self, PyObject *unused) if (!args_tuple) { goto cleanup; } - result = Py_BuildValue("ONO", Py_TYPE(self), args_tuple, - remaining_dict); + result = Py_BuildValue("ONN", Py_TYPE(self), args_tuple, dict); } else { result = Py_BuildValue("O()N", Py_TYPE(self), dict); @@ -5328,8 +5327,6 @@ ast_type_reduce(PyObject *self, PyObject *unused) } cleanup: Py_XDECREF(fields); - Py_XDECREF(remaining_fields); - Py_XDECREF(remaining_dict); Py_XDECREF(positional_args); return result; } |