Skip to content

Commit be76e3f

Browse files
gh-100980: ctypes: Test, document, and fix finalizing _fields_ (GH-124292)
- If setting `_fields_` fails, e.g. with AttributeError, don't set the attribute in `__dict__` - Document the “finalization” behaviour - Beef up tests: add `getattr`, test Union as well as Structure - Put common functionality in a common function Co-authored-by: Peter Bierma <[email protected]>
1 parent e256a75 commit be76e3f

File tree

4 files changed

+67
-55
lines changed

4 files changed

+67
-55
lines changed

Doc/library/ctypes.rst

+14-7
Original file line numberDiff line numberDiff line change
@@ -2499,6 +2499,8 @@ Structured data types
24992499

25002500
Abstract base class for unions in native byte order.
25012501

2502+
Unions share common attributes and behavior with structures;
2503+
see :class:`Structure` documentation for details.
25022504

25032505
.. class:: BigEndianUnion(*args, **kw)
25042506

@@ -2558,14 +2560,19 @@ fields, or any other data types containing pointer type fields.
25582560
...
25592561
]
25602562

2561-
The :attr:`_fields_` class variable must, however, be defined before the
2562-
type is first used (an instance is created, :func:`sizeof` is called on it,
2563-
and so on). Later assignments to the :attr:`_fields_` class variable will
2564-
raise an AttributeError.
2563+
The :attr:`!_fields_` class variable can only be set once.
2564+
Later assignments will raise an :exc:`AttributeError`.
25652565

2566-
It is possible to define sub-subclasses of structure types, they inherit
2567-
the fields of the base class plus the :attr:`_fields_` defined in the
2568-
sub-subclass, if any.
2566+
Additionally, the :attr:`!_fields_` class variable must be defined before
2567+
the structure or union type is first used: an instance or subclass is
2568+
created, :func:`sizeof` is called on it, and so on.
2569+
Later assignments to :attr:`!_fields_` will raise an :exc:`AttributeError`.
2570+
If :attr:`!_fields_` has not been set before such use,
2571+
the structure or union will have no own fields, as if :attr:`!_fields_`
2572+
was empty.
2573+
2574+
Sub-subclasses of structure types inherit the fields of the base class
2575+
plus the :attr:`_fields_` defined in the sub-subclass, if any.
25692576

25702577

25712578
.. attribute:: _pack_

Lib/test/test_ctypes/test_struct_fields.py

+35-32
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
Py_TPFLAGS_IMMUTABLETYPE)
55

66

7-
class StructFieldsTestCase(unittest.TestCase):
7+
NOTHING = object()
8+
9+
class FieldsTestBase:
810
# Structure/Union classes must get 'finalized' sooner or
911
# later, when one of these things happen:
1012
#
@@ -14,42 +16,47 @@ class StructFieldsTestCase(unittest.TestCase):
1416
# 4. The type is subclassed
1517
#
1618
# When they are finalized, assigning _fields_ is no longer allowed.
19+
20+
def assert_final_fields(self, cls, expected=NOTHING):
21+
self.assertRaises(AttributeError, setattr, cls, "_fields_", [])
22+
self.assertEqual(getattr(cls, "_fields_", NOTHING), expected)
23+
1724
def test_1_A(self):
18-
class X(Structure):
25+
class X(self.cls):
1926
pass
2027
self.assertEqual(sizeof(X), 0) # not finalized
2128
X._fields_ = [] # finalized
22-
self.assertRaises(AttributeError, setattr, X, "_fields_", [])
29+
self.assert_final_fields(X, expected=[])
2330

2431
def test_1_B(self):
25-
class X(Structure):
32+
class X(self.cls):
2633
_fields_ = [] # finalized
27-
self.assertRaises(AttributeError, setattr, X, "_fields_", [])
34+
self.assert_final_fields(X, expected=[])
2835

2936
def test_2(self):
30-
class X(Structure):
37+
class X(self.cls):
3138
pass
3239
X()
33-
self.assertRaises(AttributeError, setattr, X, "_fields_", [])
40+
self.assert_final_fields(X)
3441

3542
def test_3(self):
36-
class X(Structure):
43+
class X(self.cls):
3744
pass
38-
class Y(Structure):
45+
class Y(self.cls):
3946
_fields_ = [("x", X)] # finalizes X
40-
self.assertRaises(AttributeError, setattr, X, "_fields_", [])
47+
self.assert_final_fields(X)
4148

4249
def test_4(self):
43-
class X(Structure):
50+
class X(self.cls):
4451
pass
4552
class Y(X):
4653
pass
47-
self.assertRaises(AttributeError, setattr, X, "_fields_", [])
54+
self.assert_final_fields(X)
4855
Y._fields_ = []
49-
self.assertRaises(AttributeError, setattr, X, "_fields_", [])
56+
self.assert_final_fields(X)
5057

5158
def test_5(self):
52-
class X(Structure):
59+
class X(self.cls):
5360
_fields_ = (("char", c_char * 5),)
5461

5562
x = X(b'#' * 5)
@@ -59,14 +66,8 @@ class X(Structure):
5966
def test_6(self):
6067
self.assertRaises(TypeError, CField)
6168

62-
def test_cfield_type_flags(self):
63-
self.assertTrue(CField.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
64-
65-
def test_cfield_inheritance_hierarchy(self):
66-
self.assertEqual(CField.mro(), [CField, object])
67-
6869
def test_gh99275(self):
69-
class BrokenStructure(Structure):
70+
class BrokenStructure(self.cls):
7071
def __init_subclass__(cls, **kwargs):
7172
cls._fields_ = [] # This line will fail, `stginfo` is not ready
7273

@@ -77,26 +78,28 @@ class Subclass(BrokenStructure): ...
7778
# __set__ and __get__ should raise a TypeError in case their self
7879
# argument is not a ctype instance.
7980
def test___set__(self):
80-
class MyCStruct(Structure):
81+
class MyCStruct(self.cls):
8182
_fields_ = (("field", c_int),)
8283
self.assertRaises(TypeError,
8384
MyCStruct.field.__set__, 'wrong type self', 42)
8485

85-
class MyCUnion(Union):
86-
_fields_ = (("field", c_int),)
87-
self.assertRaises(TypeError,
88-
MyCUnion.field.__set__, 'wrong type self', 42)
89-
9086
def test___get__(self):
91-
class MyCStruct(Structure):
87+
class MyCStruct(self.cls):
9288
_fields_ = (("field", c_int),)
9389
self.assertRaises(TypeError,
9490
MyCStruct.field.__get__, 'wrong type self', 42)
9591

96-
class MyCUnion(Union):
97-
_fields_ = (("field", c_int),)
98-
self.assertRaises(TypeError,
99-
MyCUnion.field.__get__, 'wrong type self', 42)
92+
class StructFieldsTestCase(unittest.TestCase, FieldsTestBase):
93+
cls = Structure
94+
95+
def test_cfield_type_flags(self):
96+
self.assertTrue(CField.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
97+
98+
def test_cfield_inheritance_hierarchy(self):
99+
self.assertEqual(CField.mro(), [CField, object])
100+
101+
class UnionFieldsTestCase(unittest.TestCase, FieldsTestBase):
102+
cls = Union
100103

101104

102105
if __name__ == "__main__":
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The :attr:`~ctypes.Structure._fields_` attribute of
2+
:class:`ctypes.Structure` and :class:`~ctypes.Union` is no longer set if
3+
the setattr operation raises an error.

Modules/_ctypes/_ctypes.c

+15-16
Original file line numberDiff line numberDiff line change
@@ -1067,32 +1067,31 @@ CType_Type_repeat(PyObject *self, Py_ssize_t length)
10671067
return PyCArrayType_from_ctype(st, self, length);
10681068
}
10691069

1070-
10711070
static int
1072-
PyCStructType_setattro(PyObject *self, PyObject *key, PyObject *value)
1071+
_structunion_setattro(PyObject *self, PyObject *key, PyObject *value, int is_struct)
10731072
{
10741073
/* XXX Should we disallow deleting _fields_? */
1075-
if (-1 == PyType_Type.tp_setattro(self, key, value))
1076-
return -1;
1074+
if (PyUnicode_Check(key)
1075+
&& _PyUnicode_EqualToASCIIString(key, "_fields_"))
1076+
{
1077+
if (PyCStructUnionType_update_stginfo(self, value, is_struct) < 0) {
1078+
return -1;
1079+
}
1080+
}
10771081

1078-
if (value && PyUnicode_Check(key) &&
1079-
_PyUnicode_EqualToASCIIString(key, "_fields_"))
1080-
return PyCStructUnionType_update_stginfo(self, value, 1);
1081-
return 0;
1082+
return PyType_Type.tp_setattro(self, key, value);
10821083
}
10831084

1085+
static int
1086+
PyCStructType_setattro(PyObject *self, PyObject *key, PyObject *value)
1087+
{
1088+
return _structunion_setattro(self, key, value, 1);
1089+
}
10841090

10851091
static int
10861092
UnionType_setattro(PyObject *self, PyObject *key, PyObject *value)
10871093
{
1088-
/* XXX Should we disallow deleting _fields_? */
1089-
if (-1 == PyType_Type.tp_setattro(self, key, value))
1090-
return -1;
1091-
1092-
if (PyUnicode_Check(key) &&
1093-
_PyUnicode_EqualToASCIIString(key, "_fields_"))
1094-
return PyCStructUnionType_update_stginfo(self, value, 0);
1095-
return 0;
1094+
return _structunion_setattro(self, key, value, 0);
10961095
}
10971096

10981097
static PyType_Slot pycstruct_type_slots[] = {

0 commit comments

Comments
 (0)