Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 59f78d7

Browse filesBrowse files
gh-131747: ctypes: Deprecate _pack_ implicitly setting _layout_ = 'ms' (GH-133205)
On non-Windows, warn when _pack_ implicitly changes default _layout_ to 'ms'. Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
1 parent f554237 commit 59f78d7
Copy full SHA for 59f78d7

14 files changed

+115
-16
lines changed

‎Doc/deprecations/index.rst

Copy file name to clipboardExpand all lines: Doc/deprecations/index.rst
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Deprecations
77

88
.. include:: pending-removal-in-3.17.rst
99

10+
.. include:: pending-removal-in-3.19.rst
11+
1012
.. include:: pending-removal-in-future.rst
1113

1214
C API deprecations
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Pending removal in Python 3.19
2+
------------------------------
3+
4+
* :mod:`ctypes`:
5+
6+
* Implicitly switching to the MSVC-compatible struct layout by setting
7+
:attr:`~ctypes.Structure._pack_` but not :attr:`~ctypes.Structure._layout_`
8+
on non-Windows platforms.

‎Doc/library/ctypes.rst

Copy file name to clipboardExpand all lines: Doc/library/ctypes.rst
+14-1Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2754,6 +2754,16 @@ fields, or any other data types containing pointer type fields.
27542754
when :attr:`_fields_` is assigned, otherwise it will have no effect.
27552755
Setting this attribute to 0 is the same as not setting it at all.
27562756

2757+
This is only implemented for the MSVC-compatible memory layout.
2758+
2759+
.. deprecated-removed:: next 3.19
2760+
2761+
For historical reasons, if :attr:`!_pack_` is non-zero,
2762+
the MSVC-compatible layout will be used by default.
2763+
On non-Windows platforms, this default is deprecated and is slated to
2764+
become an error in Python 3.19.
2765+
If it is intended, set :attr:`~Structure._layout_` to ``'ms'``
2766+
explicitly.
27572767

27582768
.. attribute:: _align_
27592769

@@ -2782,12 +2792,15 @@ fields, or any other data types containing pointer type fields.
27822792
Currently the default will be:
27832793

27842794
- On Windows: ``"ms"``
2785-
- When :attr:`~Structure._pack_` is specified: ``"ms"``
2795+
- When :attr:`~Structure._pack_` is specified: ``"ms"``.
2796+
(This is deprecated; see :attr:`~Structure._pack_` documentation.)
27862797
- Otherwise: ``"gcc-sysv"``
27872798

27882799
:attr:`!_layout_` must already be defined when
27892800
:attr:`~Structure._fields_` is assigned, otherwise it will have no effect.
27902801

2802+
.. versionadded:: next
2803+
27912804
.. attribute:: _anonymous_
27922805

27932806
An optional sequence that lists the names of unnamed (anonymous) fields.

‎Doc/whatsnew/3.14.rst

Copy file name to clipboardExpand all lines: Doc/whatsnew/3.14.rst
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1874,6 +1874,12 @@ Deprecated
18741874
:func:`codecs.open` is now deprecated. Use :func:`open` instead.
18751875
(Contributed by Inada Naoki in :gh:`133036`.)
18761876

1877+
* :mod:`ctypes`:
1878+
On non-Windows platforms, setting :attr:`.Structure._pack_` to use a
1879+
MSVC-compatible default memory layout is deprecated in favor of setting
1880+
:attr:`.Structure._layout_` to ``'ms'``.
1881+
(Contributed by Petr Viktorin in :gh:`131747`.)
1882+
18771883
* :mod:`ctypes`:
18781884
Calling :func:`ctypes.POINTER` on a string is deprecated.
18791885
Use :ref:`ctypes-incomplete-types` for self-referential structures.
@@ -1948,6 +1954,8 @@ Deprecated
19481954

19491955
.. include:: ../deprecations/pending-removal-in-3.17.rst
19501956

1957+
.. include:: ../deprecations/pending-removal-in-3.19.rst
1958+
19511959
.. include:: ../deprecations/pending-removal-in-future.rst
19521960

19531961
Removed

‎Lib/ctypes/_layout.py

Copy file name to clipboardExpand all lines: Lib/ctypes/_layout.py
+19-2Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66

77
import sys
8+
import warnings
89

910
from _ctypes import CField, buffer_info
1011
import ctypes
@@ -66,9 +67,26 @@ def get_layout(cls, input_fields, is_struct, base):
6667

6768
# For clarity, variables that count bits have `bit` in their names.
6869

70+
pack = getattr(cls, '_pack_', None)
71+
6972
layout = getattr(cls, '_layout_', None)
7073
if layout is None:
71-
if sys.platform == 'win32' or getattr(cls, '_pack_', None):
74+
if sys.platform == 'win32':
75+
gcc_layout = False
76+
elif pack:
77+
if is_struct:
78+
base_type_name = 'Structure'
79+
else:
80+
base_type_name = 'Union'
81+
warnings._deprecated(
82+
'_pack_ without _layout_',
83+
f"Due to '_pack_', the '{cls.__name__}' {base_type_name} will "
84+
+ "use memory layout compatible with MSVC (Windows). "
85+
+ "If this is intended, set _layout_ to 'ms'. "
86+
+ "The implicit default is deprecated and slated to become "
87+
+ "an error in Python {remove}.",
88+
remove=(3, 19),
89+
)
7290
gcc_layout = False
7391
else:
7492
gcc_layout = True
@@ -95,7 +113,6 @@ def get_layout(cls, input_fields, is_struct, base):
95113
else:
96114
big_endian = sys.byteorder == 'big'
97115

98-
pack = getattr(cls, '_pack_', None)
99116
if pack is not None:
100117
try:
101118
pack = int(pack)

‎Lib/test/test_ctypes/test_aligned_structures.py

Copy file name to clipboardExpand all lines: Lib/test/test_ctypes/test_aligned_structures.py
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ class Inner(sbase):
316316

317317
class Main(sbase):
318318
_pack_ = 1
319+
_layout_ = "ms"
319320
_fields_ = [
320321
("a", c_ubyte),
321322
("b", Inner),

‎Lib/test/test_ctypes/test_bitfields.py

Copy file name to clipboardExpand all lines: Lib/test/test_ctypes/test_bitfields.py
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ class TestStruct(Structure):
430430
def test_gh_84039(self):
431431
class Bad(Structure):
432432
_pack_ = 1
433+
_layout_ = "ms"
433434
_fields_ = [
434435
("a0", c_uint8, 1),
435436
("a1", c_uint8, 1),
@@ -443,9 +444,9 @@ class Bad(Structure):
443444
("b1", c_uint16, 12),
444445
]
445446

446-
447447
class GoodA(Structure):
448448
_pack_ = 1
449+
_layout_ = "ms"
449450
_fields_ = [
450451
("a0", c_uint8, 1),
451452
("a1", c_uint8, 1),
@@ -460,6 +461,7 @@ class GoodA(Structure):
460461

461462
class Good(Structure):
462463
_pack_ = 1
464+
_layout_ = "ms"
463465
_fields_ = [
464466
("a", GoodA),
465467
("b0", c_uint16, 4),
@@ -475,6 +477,7 @@ class Good(Structure):
475477
def test_gh_73939(self):
476478
class MyStructure(Structure):
477479
_pack_ = 1
480+
_layout_ = "ms"
478481
_fields_ = [
479482
("P", c_uint16),
480483
("L", c_uint16, 9),

‎Lib/test/test_ctypes/test_byteswap.py

Copy file name to clipboardExpand all lines: Lib/test/test_ctypes/test_byteswap.py
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ def test_unaligned_nonnative_struct_fields(self):
269269

270270
class S(base):
271271
_pack_ = 1
272+
_layout_ = "ms"
272273
_fields_ = [("b", c_byte),
273274
("h", c_short),
274275

@@ -296,6 +297,7 @@ def test_unaligned_native_struct_fields(self):
296297

297298
class S(Structure):
298299
_pack_ = 1
300+
_layout_ = "ms"
299301
_fields_ = [("b", c_byte),
300302

301303
("h", c_short),

‎Lib/test/test_ctypes/test_generated_structs.py

Copy file name to clipboardExpand all lines: Lib/test/test_ctypes/test_generated_structs.py
+10-1Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,21 @@ class Nested(Structure):
125125
class Packed1(Structure):
126126
_fields_ = [('a', c_int8), ('b', c_int64)]
127127
_pack_ = 1
128+
_layout_ = 'ms'
128129

129130

130131
@register()
131132
class Packed2(Structure):
132133
_fields_ = [('a', c_int8), ('b', c_int64)]
133134
_pack_ = 2
135+
_layout_ = 'ms'
134136

135137

136138
@register()
137139
class Packed3(Structure):
138140
_fields_ = [('a', c_int8), ('b', c_int64)]
139141
_pack_ = 4
142+
_layout_ = 'ms'
140143

141144

142145
@register()
@@ -155,6 +158,7 @@ def _maybe_skip():
155158

156159
_fields_ = [('a', c_int8), ('b', c_int64)]
157160
_pack_ = 8
161+
_layout_ = 'ms'
158162

159163
@register()
160164
class X86_32EdgeCase(Structure):
@@ -366,6 +370,7 @@ class Example_gh_95496(Structure):
366370
@register()
367371
class Example_gh_84039_bad(Structure):
368372
_pack_ = 1
373+
_layout_ = 'ms'
369374
_fields_ = [("a0", c_uint8, 1),
370375
("a1", c_uint8, 1),
371376
("a2", c_uint8, 1),
@@ -380,6 +385,7 @@ class Example_gh_84039_bad(Structure):
380385
@register()
381386
class Example_gh_84039_good_a(Structure):
382387
_pack_ = 1
388+
_layout_ = 'ms'
383389
_fields_ = [("a0", c_uint8, 1),
384390
("a1", c_uint8, 1),
385391
("a2", c_uint8, 1),
@@ -392,13 +398,15 @@ class Example_gh_84039_good_a(Structure):
392398
@register()
393399
class Example_gh_84039_good(Structure):
394400
_pack_ = 1
401+
_layout_ = 'ms'
395402
_fields_ = [("a", Example_gh_84039_good_a),
396403
("b0", c_uint16, 4),
397404
("b1", c_uint16, 12)]
398405

399406
@register()
400407
class Example_gh_73939(Structure):
401408
_pack_ = 1
409+
_layout_ = 'ms'
402410
_fields_ = [("P", c_uint16),
403411
("L", c_uint16, 9),
404412
("Pro", c_uint16, 1),
@@ -419,6 +427,7 @@ class Example_gh_86098(Structure):
419427
@register()
420428
class Example_gh_86098_pack(Structure):
421429
_pack_ = 1
430+
_layout_ = 'ms'
422431
_fields_ = [("a", c_uint8, 8),
423432
("b", c_uint8, 8),
424433
("c", c_uint32, 16)]
@@ -528,7 +537,7 @@ def dump_ctype(tp, struct_or_union_tag='', variable_name='', semi=''):
528537
pushes.append(f'#pragma pack(push, {pack})')
529538
pops.append(f'#pragma pack(pop)')
530539
layout = getattr(tp, '_layout_', None)
531-
if layout == 'ms' or pack:
540+
if layout == 'ms':
532541
# The 'ms_struct' attribute only works on x86 and PowerPC
533542
requires.add(
534543
'defined(MS_WIN32) || ('

‎Lib/test/test_ctypes/test_pep3118.py

Copy file name to clipboardExpand all lines: Lib/test/test_ctypes/test_pep3118.py
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,23 @@ class Point(Structure):
8181

8282
class PackedPoint(Structure):
8383
_pack_ = 2
84+
_layout_ = 'ms'
8485
_fields_ = [("x", c_long), ("y", c_long)]
8586

8687
class PointMidPad(Structure):
8788
_fields_ = [("x", c_byte), ("y", c_uint)]
8889

8990
class PackedPointMidPad(Structure):
9091
_pack_ = 2
92+
_layout_ = 'ms'
9193
_fields_ = [("x", c_byte), ("y", c_uint64)]
9294

9395
class PointEndPad(Structure):
9496
_fields_ = [("x", c_uint), ("y", c_byte)]
9597

9698
class PackedPointEndPad(Structure):
9799
_pack_ = 2
100+
_layout_ = 'ms'
98101
_fields_ = [("x", c_uint64), ("y", c_byte)]
99102

100103
class Point2(Structure):

‎Lib/test/test_ctypes/test_structunion.py

Copy file name to clipboardExpand all lines: Lib/test/test_ctypes/test_structunion.py
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
Py_TPFLAGS_DISALLOW_INSTANTIATION,
1212
Py_TPFLAGS_IMMUTABLETYPE)
1313
from struct import calcsize
14+
import contextlib
15+
from test.support import MS_WINDOWS
1416

1517

1618
class StructUnionTestBase:
@@ -335,6 +337,22 @@ def test_methods(self):
335337
self.assertIn("from_address", dir(type(self.cls)))
336338
self.assertIn("in_dll", dir(type(self.cls)))
337339

340+
def test_pack_layout_switch(self):
341+
# Setting _pack_ implicitly sets default layout to MSVC;
342+
# this is deprecated on non-Windows platforms.
343+
if MS_WINDOWS:
344+
warn_context = contextlib.nullcontext()
345+
else:
346+
warn_context = self.assertWarns(DeprecationWarning)
347+
with warn_context:
348+
class X(self.cls):
349+
_pack_ = 1
350+
# _layout_ missing
351+
_fields_ = [('a', c_int8, 1), ('b', c_int16, 2)]
352+
353+
# Check MSVC layout (bitfields of different types aren't combined)
354+
self.check_sizeof(X, struct_size=3, union_size=2)
355+
338356

339357
class StructureTestCase(unittest.TestCase, StructUnionTestBase):
340358
cls = Structure

‎Lib/test/test_ctypes/test_structures.py

Copy file name to clipboardExpand all lines: Lib/test/test_ctypes/test_structures.py
+20-11Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class X(Structure):
2525
_fields_ = [("a", c_byte),
2626
("b", c_longlong)]
2727
_pack_ = 1
28+
_layout_ = 'ms'
2829
self.check_struct(X)
2930

3031
self.assertEqual(sizeof(X), 9)
@@ -34,6 +35,7 @@ class X(Structure):
3435
_fields_ = [("a", c_byte),
3536
("b", c_longlong)]
3637
_pack_ = 2
38+
_layout_ = 'ms'
3739
self.check_struct(X)
3840
self.assertEqual(sizeof(X), 10)
3941
self.assertEqual(X.b.offset, 2)
@@ -45,6 +47,7 @@ class X(Structure):
4547
_fields_ = [("a", c_byte),
4648
("b", c_longlong)]
4749
_pack_ = 4
50+
_layout_ = 'ms'
4851
self.check_struct(X)
4952
self.assertEqual(sizeof(X), min(4, longlong_align) + longlong_size)
5053
self.assertEqual(X.b.offset, min(4, longlong_align))
@@ -53,27 +56,33 @@ class X(Structure):
5356
_fields_ = [("a", c_byte),
5457
("b", c_longlong)]
5558
_pack_ = 8
59+
_layout_ = 'ms'
5660
self.check_struct(X)
5761

5862
self.assertEqual(sizeof(X), min(8, longlong_align) + longlong_size)
5963
self.assertEqual(X.b.offset, min(8, longlong_align))
6064

61-
62-
d = {"_fields_": [("a", "b"),
63-
("b", "q")],
64-
"_pack_": -1}
65-
self.assertRaises(ValueError, type(Structure), "X", (Structure,), d)
65+
with self.assertRaises(ValueError):
66+
class X(Structure):
67+
_fields_ = [("a", "b"), ("b", "q")]
68+
_pack_ = -1
69+
_layout_ = "ms"
6670

6771
@support.cpython_only
6872
def test_packed_c_limits(self):
6973
# Issue 15989
7074
import _testcapi
71-
d = {"_fields_": [("a", c_byte)],
72-
"_pack_": _testcapi.INT_MAX + 1}
73-
self.assertRaises(ValueError, type(Structure), "X", (Structure,), d)
74-
d = {"_fields_": [("a", c_byte)],
75-
"_pack_": _testcapi.UINT_MAX + 2}
76-
self.assertRaises(ValueError, type(Structure), "X", (Structure,), d)
75+
with self.assertRaises(ValueError):
76+
class X(Structure):
77+
_fields_ = [("a", c_byte)]
78+
_pack_ = _testcapi.INT_MAX + 1
79+
_layout_ = "ms"
80+
81+
with self.assertRaises(ValueError):
82+
class X(Structure):
83+
_fields_ = [("a", c_byte)]
84+
_pack_ = _testcapi.UINT_MAX + 2
85+
_layout_ = "ms"
7786

7887
def test_initializers(self):
7988
class Person(Structure):

‎Lib/test/test_ctypes/test_unaligned_structures.py

Copy file name to clipboardExpand all lines: Lib/test/test_ctypes/test_unaligned_structures.py
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
c_ushort, c_uint, c_ulong, c_ulonglong]:
2020
class X(Structure):
2121
_pack_ = 1
22+
_layout_ = 'ms'
2223
_fields_ = [("pad", c_byte),
2324
("value", typ)]
2425
class Y(SwappedStructure):
2526
_pack_ = 1
27+
_layout_ = 'ms'
2628
_fields_ = [("pad", c_byte),
2729
("value", typ)]
2830
structures.append(X)
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
On non-Windows platforms, deprecate using :attr:`ctypes.Structure._pack_` to
2+
use a Windows-compatible layout on non-Windows platforms. The layout should
3+
be specified explicitly by setting :attr:`ctypes.Structure._layout_` to
4+
``'ms'``.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.