Skip to content

Commit 7a77907

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 d278d97 commit 7a77907

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
@@ -483,6 +486,66 @@ class X(Structure):
483486
self.assertEqual(s.first, got.first)
484487
self.assertEqual(s.second, got.second)
485488

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

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)