gh-117266: Fix crashes on user-created AST subclasses (GH-117276)

Fix crashes on user-created AST subclasses
This commit is contained in:
Jelle Zijlstra 2024-03-28 04:30:31 -06:00 committed by GitHub
parent 8cb7d7ff86
commit 4c71d51a4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 69 additions and 4 deletions

View File

@ -2916,6 +2916,47 @@ class ASTConstructorTests(unittest.TestCase):
self.assertEqual(node.name, 'foo') self.assertEqual(node.name, 'foo')
self.assertEqual(node.decorator_list, []) self.assertEqual(node.decorator_list, [])
def test_custom_subclass(self):
class NoInit(ast.AST):
pass
obj = NoInit()
self.assertIsInstance(obj, NoInit)
self.assertEqual(obj.__dict__, {})
class Fields(ast.AST):
_fields = ('a',)
with self.assertWarnsRegex(DeprecationWarning,
r"Fields provides _fields but not _field_types."):
obj = Fields()
with self.assertRaises(AttributeError):
obj.a
obj = Fields(a=1)
self.assertEqual(obj.a, 1)
class FieldsAndTypes(ast.AST):
_fields = ('a',)
_field_types = {'a': int | None}
a: int | None = None
obj = FieldsAndTypes()
self.assertIs(obj.a, None)
obj = FieldsAndTypes(a=1)
self.assertEqual(obj.a, 1)
class FieldsAndTypesNoDefault(ast.AST):
_fields = ('a',)
_field_types = {'a': int}
with self.assertWarnsRegex(DeprecationWarning,
r"FieldsAndTypesNoDefault\.__init__ missing 1 required positional argument: 'a'\."):
obj = FieldsAndTypesNoDefault()
with self.assertRaises(AttributeError):
obj.a
obj = FieldsAndTypesNoDefault(a=1)
self.assertEqual(obj.a, 1)
@support.cpython_only @support.cpython_only
class ModuleStateTests(unittest.TestCase): class ModuleStateTests(unittest.TestCase):

View File

@ -0,0 +1,2 @@
Fix crashes for certain user-created subclasses of :class:`ast.AST`. Such
classes are now expected to set the ``_field_types`` attribute.

View File

@ -973,11 +973,22 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw)
Py_ssize_t size = PySet_Size(remaining_fields); Py_ssize_t size = PySet_Size(remaining_fields);
PyObject *field_types = NULL, *remaining_list = NULL; PyObject *field_types = NULL, *remaining_list = NULL;
if (size > 0) { if (size > 0) {
if (!PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), &_Py_ID(_field_types), if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), &_Py_ID(_field_types),
&field_types)) { &field_types) < 0) {
res = -1; res = -1;
goto cleanup; goto cleanup;
} }
if (field_types == NULL) {
if (PyErr_WarnFormat(
PyExc_DeprecationWarning, 1,
"%.400s provides _fields but not _field_types. "
"This will become an error in Python 3.15.",
Py_TYPE(self)->tp_name
) < 0) {
res = -1;
}
goto cleanup;
}
remaining_list = PySequence_List(remaining_fields); remaining_list = PySequence_List(remaining_fields);
if (!remaining_list) { if (!remaining_list) {
goto set_remaining_cleanup; goto set_remaining_cleanup;

15
Python/Python-ast.c generated
View File

@ -5119,11 +5119,22 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw)
Py_ssize_t size = PySet_Size(remaining_fields); Py_ssize_t size = PySet_Size(remaining_fields);
PyObject *field_types = NULL, *remaining_list = NULL; PyObject *field_types = NULL, *remaining_list = NULL;
if (size > 0) { if (size > 0) {
if (!PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), &_Py_ID(_field_types), if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), &_Py_ID(_field_types),
&field_types)) { &field_types) < 0) {
res = -1; res = -1;
goto cleanup; goto cleanup;
} }
if (field_types == NULL) {
if (PyErr_WarnFormat(
PyExc_DeprecationWarning, 1,
"%.400s provides _fields but not _field_types. "
"This will become an error in Python 3.15.",
Py_TYPE(self)->tp_name
) < 0) {
res = -1;
}
goto cleanup;
}
remaining_list = PySequence_List(remaining_fields); remaining_list = PySequence_List(remaining_fields);
if (!remaining_list) { if (!remaining_list) {
goto set_remaining_cleanup; goto set_remaining_cleanup;