Skip to content

Commit 59f78d7

Browse 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 <[email protected]>
1 parent f554237 commit 59f78d7

14 files changed

+115
-16
lines changed

Doc/deprecations/index.rst

Lines 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
Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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):

0 commit comments

Comments
 (0)