Skip to content

Commit e5e74ff

Browse files
serhiy-storchakawebsurfer5
authored andcommitted
[3.11] pythongh-62260: Fix ctypes.Structure subclassing with multiple layers (pythonGH-13374) (pythonGH-113624)
The length field of StgDictObject for Structure class contains now the total number of items in ffi_type_pointer.elements (excluding the trailing null). The old behavior of using the number of elements in the parent class can cause the array to be truncated when it is copied, especially when there are multiple layers of subclassing. (cherry picked from commit 5f3cc90) Co-authored-by: Jeffrey Kintscher <[email protected]> Signed-off-by: Michał Górny <[email protected]>
1 parent a025396 commit e5e74ff

File tree

3 files changed

+69
-6
lines changed

3 files changed

+69
-6
lines changed

Lib/ctypes/test/test_structures.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import platform
2+
from platform import architecture as _architecture
3+
import struct
24
import sys
35
import unittest
46
from ctypes.test import need_symbol
@@ -7,6 +9,7 @@
79
c_uint8, c_uint16, c_uint32,
810
c_short, c_ushort, c_int, c_uint,
911
c_long, c_ulong, c_longlong, c_ulonglong, c_float, c_double)
12+
from ctypes.util import find_library
1013
from struct import calcsize
1114
import _ctypes_test
1215
from test import support
@@ -488,6 +491,66 @@ class X(Structure):
488491
self.assertEqual(s.first, got.first)
489492
self.assertEqual(s.second, got.second)
490493

494+
def _test_issue18060(self, Vector):
495+
# The call to atan2() should succeed if the
496+
# class fields were correctly cloned in the
497+
# subclasses. Otherwise, it will segfault.
498+
if sys.platform == 'win32':
499+
libm = CDLL(find_library('msvcrt.dll'))
500+
else:
501+
libm = CDLL(find_library('m'))
502+
503+
libm.atan2.argtypes = [Vector]
504+
libm.atan2.restype = c_double
505+
506+
arg = Vector(y=0.0, x=-1.0)
507+
self.assertAlmostEqual(libm.atan2(arg), 3.141592653589793)
508+
509+
@unittest.skipIf(_architecture() == ('64bit', 'WindowsPE'), "can't test Windows x64 build")
510+
@unittest.skipUnless(sys.byteorder == 'little', "can't test on this platform")
511+
def test_issue18060_a(self):
512+
# This test case calls
513+
# PyCStructUnionType_update_stgdict() for each
514+
# _fields_ assignment, and PyCStgDict_clone()
515+
# for the Mid and Vector class definitions.
516+
class Base(Structure):
517+
_fields_ = [('y', c_double),
518+
('x', c_double)]
519+
class Mid(Base):
520+
pass
521+
Mid._fields_ = []
522+
class Vector(Mid): pass
523+
self._test_issue18060(Vector)
524+
525+
@unittest.skipIf(_architecture() == ('64bit', 'WindowsPE'), "can't test Windows x64 build")
526+
@unittest.skipUnless(sys.byteorder == 'little', "can't test on this platform")
527+
def test_issue18060_b(self):
528+
# This test case calls
529+
# PyCStructUnionType_update_stgdict() for each
530+
# _fields_ assignment.
531+
class Base(Structure):
532+
_fields_ = [('y', c_double),
533+
('x', c_double)]
534+
class Mid(Base):
535+
_fields_ = []
536+
class Vector(Mid):
537+
_fields_ = []
538+
self._test_issue18060(Vector)
539+
540+
@unittest.skipIf(_architecture() == ('64bit', 'WindowsPE'), "can't test Windows x64 build")
541+
@unittest.skipUnless(sys.byteorder == 'little', "can't test on this platform")
542+
def test_issue18060_c(self):
543+
# This test case calls
544+
# PyCStructUnionType_update_stgdict() for each
545+
# _fields_ assignment.
546+
class Base(Structure):
547+
_fields_ = [('y', c_double)]
548+
class Mid(Base):
549+
_fields_ = []
550+
class Vector(Mid):
551+
_fields_ = [('x', c_double)]
552+
self._test_issue18060(Vector)
553+
491554
def test_array_in_struct(self):
492555
# See bpo-22273
493556

Modules/_ctypes/_ctypes.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4387,10 +4387,10 @@ _init_pos_args(PyObject *self, PyTypeObject *type,
43874387
return index;
43884388
}
43894389

4390-
for (i = 0;
4391-
i < dict->length && (i+index) < PyTuple_GET_SIZE(args);
4390+
for (i = index;
4391+
i < dict->length && i < PyTuple_GET_SIZE(args);
43924392
++i) {
4393-
PyObject *pair = PySequence_GetItem(fields, i);
4393+
PyObject *pair = PySequence_GetItem(fields, i - index);
43944394
PyObject *name, *val;
43954395
int res;
43964396
if (!pair)
@@ -4400,7 +4400,7 @@ _init_pos_args(PyObject *self, PyTypeObject *type,
44004400
Py_DECREF(pair);
44014401
return -1;
44024402
}
4403-
val = PyTuple_GET_ITEM(args, i + index);
4403+
val = PyTuple_GET_ITEM(args, i);
44044404
if (kwds) {
44054405
if (PyDict_GetItemWithError(kwds, name)) {
44064406
PyErr_Format(PyExc_TypeError,
@@ -4423,7 +4423,7 @@ _init_pos_args(PyObject *self, PyTypeObject *type,
44234423
if (res == -1)
44244424
return -1;
44254425
}
4426-
return index + dict->length;
4426+
return dict->length;
44274427
}
44284428

44294429
static int

Modules/_ctypes/stgdict.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
651651

652652
stgdict->size = size;
653653
stgdict->align = total_align;
654-
stgdict->length = len; /* ADD ffi_ofs? */
654+
stgdict->length = ffi_ofs + len;
655655

656656
/*
657657
* On Arm platforms, structs with at most 4 elements of any floating point

0 commit comments

Comments
 (0)