Skip to content

gh-105858: Improve AST node constructors #105880

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

Merged
merged 18 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix pickling; fix a refleak
  • Loading branch information
JelleZijlstra committed Oct 11, 2023
commit f941696deb85116c8333efcc9f2b5189fd78d812
69 changes: 66 additions & 3 deletions Parser/asdl_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,8 @@ def visitModule(self, mod):
}
}
}
Py_DECREF(remaining_list);
Py_DECREF(field_types);
}
cleanup:
Py_XDECREF(fields);
Expand All @@ -1041,14 +1043,75 @@ def visitModule(self, mod):
return NULL;
}

PyObject *dict;
PyObject *dict = NULL, *fields = NULL, *remaining_fields = NULL,
*positional_args = NULL;
if (PyObject_GetOptionalAttr(self, state->__dict__, &dict) < 0) {
return NULL;
}
PyObject *result = NULL;
if (dict) {
return Py_BuildValue("O()N", Py_TYPE(self), 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.
Comment on lines +1053 to +1056
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do AST types even need their own custom reduce function? Shouldn't object.__reduce__ (which IIRC uses __new__ to safely create an empty instance, then updates its __dict__) work fine for AST objects as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The __new__ call will trigger DeprecationWarnings. If we don't have our own reducer, unpickling will essentially do node = ast.FunctionDef(); node.__dict__.update(...), and the first line raises warnings because we don't set the required name field.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would the __new__ call raise deprecation warnings? Aren't the deprecation warnings in __init__, not __new__? That's why object.reduce() uses __new__, so that it can bypass whatever is happening in custom __init__ methods (otherwise lots of custom classes would fail to unpickle by default). So object.reduce() is not equivalent to node = ast.FunctionDef(); node.__dict__.update(...) -- it's more like node = ast.FunctionDef.__new__(ast.FunctionDef); node.__dict__.update(...), which is a critical difference. So it seems to me like using the default reducer should work fine here, and not require all this extra work. But I could definitely be missing something; there's probably some reason these nodes had a custom reduce() in the first place.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean. I tried commenting out the __reduce__ definition, and lots of tests fail with:

ERROR: test_pickling (test.test_ast.AST_Tests.test_pickling) (ast=<ast.Module object at 0x102323da0>, protocol=1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/jelle/py/cpython/Lib/test/test_ast.py", line 977, in test_pickling
    ast2 = pickle.loads(pickle.dumps(ast, protocol))
                        ~~~~~~~~~~~~^^^^^^^^^^^^^^^
  File "/Users/jelle/py/cpython/Lib/copyreg.py", line 71, in _reduce_ex
    state = base(self)
            ~~~~^^^^^^
TypeError: AST constructor takes at most 0 positional arguments

I don't entirely understand what _reduce_ex is doing there, but it picks ast.AST as the base, which fails.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Well, I might have just nerd-sniped myself into digging into this more later to understand what's happening, but I don't think it should block the PR; your added code to use positional args works fine.

PyObject *fields = NULL;
if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields) < 0) {
goto cleanup;
}
if (fields) {
Py_ssize_t numfields = PySequence_Size(fields);
if (numfields == -1) {
Py_DECREF(dict);
goto cleanup;
}
PyObject *remaining_dict = PyDict_Copy(dict);
Py_DECREF(dict);
if (!remaining_dict) {
goto cleanup;
}
PyObject *positional_args = PyList_New(0);
if (!positional_args) {
goto cleanup;
}
for (Py_ssize_t i = 0; i < numfields; i++) {
PyObject *name = PySequence_GetItem(fields, i);
if (!name) {
goto cleanup;
}
PyObject *value = PyDict_GetItemWithError(remaining_dict, name);
if (!value) {
if (PyErr_Occurred()) {
goto cleanup;
}
break;
}
if (PyList_Append(positional_args, value) < 0) {
goto cleanup;
}
if (PyDict_DelItem(remaining_dict, name) < 0) {
goto cleanup;
}
Comment on lines +1081 to +1092
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are leaks of name here. They will be fixed in #116438.

Py_DECREF(name);
}
PyObject *args_tuple = PyList_AsTuple(positional_args);
if (!args_tuple) {
goto cleanup;
}
result = Py_BuildValue("ONN", Py_TYPE(self), args_tuple,
remaining_dict);
}
else {
result = Py_BuildValue("O()N", Py_TYPE(self), dict);
}
}
else {
result = Py_BuildValue("O()", Py_TYPE(self));
}
return Py_BuildValue("O()", Py_TYPE(self));
cleanup:
Py_XDECREF(fields);
Py_XDECREF(remaining_fields);
Py_XDECREF(positional_args);
return result;
}

static PyMemberDef ast_type_members[] = {
Expand Down
69 changes: 66 additions & 3 deletions Python/Python-ast.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy