Skip to content

Commit

Permalink
Squash 'pythongh-97588: Fix ctypes structs' for backport to 3.10
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiasgoergens committed Mar 17, 2023
1 parent d025b1d commit 7526bb5
Show file tree
Hide file tree
Showing 7 changed files with 488 additions and 131 deletions.
3 changes: 3 additions & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ struct _Py_global_strings {
STRUCT_FOR_ID(_initializing)
STRUCT_FOR_ID(_is_text_encoding)
STRUCT_FOR_ID(_lock_unlock_module)
STRUCT_FOR_ID(_ms_struct_)
// TODO(Matthias): note sure I need _pack_
STRUCT_FOR_ID(_pack_)
STRUCT_FOR_ID(_showwarnmsg)
STRUCT_FOR_ID(_shutdown)
STRUCT_FOR_ID(_slotnames)
Expand Down
207 changes: 204 additions & 3 deletions Lib/ctypes/test/test_bitfields.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from test import support
import unittest
import os
import sys

import _ctypes_test

Expand All @@ -28,6 +29,30 @@ class BITS(Structure):
func = CDLL(_ctypes_test.__file__).unpack_bitfields
func.argtypes = POINTER(BITS), c_char


class BITS_msvc(Structure):
_ms_struct_ = 1
_fields_ = [("A", c_int, 1),
("B", c_int, 2),
("C", c_int, 3),
("D", c_int, 4),
("E", c_int, 5),
("F", c_int, 6),
("G", c_int, 7),
("H", c_int, 8),
("I", c_int, 9),

("M", c_short, 1),
("N", c_short, 2),
("O", c_short, 3),
("P", c_short, 4),
("Q", c_short, 5),
("R", c_short, 6),
("S", c_short, 7)]

func_msvc = CDLL(_ctypes_test.__file__).unpack_bitfields_msvc
func_msvc.argtypes = POINTER(BITS_msvc), c_char

##for n in "ABCDEFGHIMNOPQRS":
## print n, hex(getattr(BITS, n).size), getattr(BITS, n).offset

Expand All @@ -40,18 +65,43 @@ def test_ints(self):
setattr(b, name, i)
self.assertEqual(getattr(b, name), func(byref(b), name.encode('ascii')))

# bpo-46913: _ctypes/cfield.c h_get() has an undefined behavior
@support.skip_if_sanitizer(ub=True)
def test_shorts(self):
b = BITS()
name = "M"
# See Modules/_ctypes/_ctypes_test.c for where the magic 999 comes from.
if func(byref(b), name.encode('ascii')) == 999:
# unpack_bitfields and unpack_bitfields_msvc in
# Modules/_ctypes/_ctypes_test.c return 999 to indicate
# an invalid name. 'M' is only valid, if signed short bitfields
# are supported by the C compiler.
self.skipTest("Compiler does not support signed short bitfields")
for i in range(256):
for name in "MNOPQRS":
b = BITS()
setattr(b, name, i)
self.assertEqual(getattr(b, name), func(byref(b), name.encode('ascii')))
self.assertEqual(
getattr(b, name),
func(byref(b), (name.encode('ascii'))),
(name, i))

def test_shorts_msvc_mode(self):
b = BITS_msvc()
name = "M"
# See Modules/_ctypes/_ctypes_test.c for where the magic 999 comes from.
if func_msvc(byref(b), name.encode('ascii')) == 999:
# unpack_bitfields and unpack_bitfields_msvc in
# Modules/_ctypes/_ctypes_test.c return 999 to indicate
# an invalid name. 'M' is only valid, if signed short bitfields
# are supported by the C compiler.
self.skipTest("Compiler does not support signed short bitfields")
for i in range(256):
for name in "MNOPQRS":
b = BITS_msvc()
setattr(b, name, i)
self.assertEqual(
getattr(b, name),
func_msvc(byref(b), name.encode('ascii')),
(name, i))

signed_int_types = (c_byte, c_short, c_int, c_long, c_longlong)
unsigned_int_types = (c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong)
Expand Down Expand Up @@ -236,6 +286,157 @@ class X(Structure):
else:
self.assertEqual(sizeof(X), sizeof(c_int) * 2)

def test_mixed_5(self):
class X(Structure):
_fields_ = [
('A', c_uint, 1),
('B', c_ushort, 16)]
a = X()
a.A = 0
a.B = 1
self.assertEqual(1, a.B)

def test_mixed_6(self):
class X(Structure):
_fields_ = [
('A', c_ulonglong, 1),
('B', c_uint, 32)]
a = X()
a.A = 0
a.B = 1
self.assertEqual(1, a.B)

def test_mixed_7(self):
class X(Structure):
_fields_ = [
("A", c_uint),
('B', c_uint, 20),
('C', c_ulonglong, 24)]
self.assertEqual(16, sizeof(X))

def test_mixed_8(self):
class Foo(Structure):
_fields_ = [
("A", c_uint),
("B", c_uint, 32),
("C", c_ulonglong, 1),
]

class Bar(Structure):
_fields_ = [
("A", c_uint),
("B", c_uint),
("C", c_ulonglong, 1),
]
self.assertEqual(sizeof(Foo), sizeof(Bar))

def test_mixed_9(self):
class X(Structure):
_fields_ = [
("A", c_uint8),
("B", c_uint, 1),
]
if sys.platform == 'win32':
self.assertEqual(8, sizeof(X))
else:
self.assertEqual(4, sizeof(X))

def test_mixed_10(self):
class X(Structure):
_fields_ = [
("A", c_uint32, 1),
("B", c_uint64, 1),
]
if sys.platform == 'win32':
self.assertEqual(8, alignment(X))
self.assertEqual(16, sizeof(X))
else:
self.assertEqual(8, alignment(X))
self.assertEqual(8, sizeof(X))

def test_gh_95496(self):
for field_width in range(1, 33):
class TestStruct(Structure):
_fields_ = [
("Field1", c_uint32, field_width),
("Field2", c_uint8, 8)
]

cmd = TestStruct()
cmd.Field2 = 1
self.assertEqual(1, cmd.Field2)

def test_gh_84039(self):
class Bad(Structure):
_pack_ = 1
_fields_ = [
("a0", c_uint8, 1),
("a1", c_uint8, 1),
("a2", c_uint8, 1),
("a3", c_uint8, 1),
("a4", c_uint8, 1),
("a5", c_uint8, 1),
("a6", c_uint8, 1),
("a7", c_uint8, 1),
("b0", c_uint16, 4),
("b1", c_uint16, 12),
]


class GoodA(Structure):
_pack_ = 1
_fields_ = [
("a0", c_uint8, 1),
("a1", c_uint8, 1),
("a2", c_uint8, 1),
("a3", c_uint8, 1),
("a4", c_uint8, 1),
("a5", c_uint8, 1),
("a6", c_uint8, 1),
("a7", c_uint8, 1),
]


class Good(Structure):
_pack_ = 1
_fields_ = [
("a", GoodA),
("b0", c_uint16, 4),
("b1", c_uint16, 12),
]

self.assertEqual(3, sizeof(Bad))
self.assertEqual(3, sizeof(Good))

def test_gh_73939(self):
class MyStructure(Structure):
_pack_ = 1
_fields_ = [
("P", c_uint16),
("L", c_uint16, 9),
("Pro", c_uint16, 1),
("G", c_uint16, 1),
("IB", c_uint16, 1),
("IR", c_uint16, 1),
("R", c_uint16, 3),
("T", c_uint32, 10),
("C", c_uint32, 20),
("R2", c_uint32, 2)
]
self.assertEqual(8, sizeof(MyStructure))

def test_gh_86098(self):
class X(Structure):
_fields_ = [
("a", c_uint8, 8),
("b", c_uint8, 8),
("c", c_uint32, 16)
]
if sys.platform == 'win32':
self.assertEqual(8, sizeof(X))
else:
self.assertEqual(4, sizeof(X))

def test_anon_bitfields(self):
# anonymous bit-fields gave a strange error message
class X(Structure):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix ctypes construction of structs from description.
45 changes: 44 additions & 1 deletion Modules/_ctypes/_ctypes_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ struct BITS {
*/
#ifndef __xlc__
#define SIGNED_SHORT_BITFIELDS
short M: 1, N: 2, O: 3, P: 4, Q: 5, R: 6, S: 7;
signed short M: 1, N: 2, O: 3, P: 4, Q: 5, R: 6, S: 7;
#endif
};

Expand Down Expand Up @@ -622,6 +622,49 @@ EXPORT(int) unpack_bitfields(struct BITS *bits, char name)
return 999;
}

struct
#ifndef MS_WIN32
__attribute__ ((ms_struct))
#endif
BITS_msvc
{
signed int A: 1, B:2, C:3, D:4, E: 5, F: 6, G: 7, H: 8, I: 9;
/*
* The test case needs/uses "signed short" bitfields, but the
* IBM XLC compiler does not support this
*/
#ifndef __xlc__
#define SIGNED_SHORT_BITFIELDS
signed short M: 1, N: 2, O: 3, P: 4, Q: 5, R: 6, S: 7;
#endif
};

EXPORT(int) unpack_bitfields_msvc(struct BITS_msvc *bits, char name)
{
switch (name) {
case 'A': return bits->A;
case 'B': return bits->B;
case 'C': return bits->C;
case 'D': return bits->D;
case 'E': return bits->E;
case 'F': return bits->F;
case 'G': return bits->G;
case 'H': return bits->H;
case 'I': return bits->I;

#ifdef SIGNED_SHORT_BITFIELDS
case 'M': return bits->M;
case 'N': return bits->N;
case 'O': return bits->O;
case 'P': return bits->P;
case 'Q': return bits->Q;
case 'R': return bits->R;
case 'S': return bits->S;
#endif
}
return 999;
}

static PyMethodDef module_methods[] = {
/* {"get_last_tf_arg_s", get_last_tf_arg_s, METH_NOARGS},
{"get_last_tf_arg_u", get_last_tf_arg_u, METH_NOARGS},
Expand Down
Loading

0 comments on commit 7526bb5

Please sign in to comment.