Skip to content

Commit 5f3cc90

Browse files
gh-62260: Fix ctypes.Structure subclassing with multiple layers (GH-13374)
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. Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 4036e48 commit 5f3cc90

File tree

4 files changed

+70
-7
lines changed

4 files changed

+70
-7
lines changed

Lib/test/test_ctypes/test_structures.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import _ctypes_test
2-
import platform
2+
from platform import architecture as _architecture
33
import struct
44
import sys
55
import unittest
@@ -8,6 +8,7 @@
88
c_uint8, c_uint16, c_uint32,
99
c_short, c_ushort, c_int, c_uint,
1010
c_long, c_ulong, c_longlong, c_ulonglong, c_float, c_double)
11+
from ctypes.util import find_library
1112
from struct import calcsize
1213
from collections import namedtuple
1314
from test import support
@@ -472,6 +473,66 @@ class X(Structure):
472473
self.assertEqual(s.first, got.first)
473474
self.assertEqual(s.second, got.second)
474475

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed a class inheritance issue that can cause segfaults when deriving two or more levels of subclasses from a base class of Structure or Union.
2+

Modules/_ctypes/_ctypes.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4354,10 +4354,10 @@ _init_pos_args(PyObject *self, PyTypeObject *type,
43544354
return index;
43554355
}
43564356

4357-
for (i = 0;
4358-
i < dict->length && (i+index) < PyTuple_GET_SIZE(args);
4357+
for (i = index;
4358+
i < dict->length && i < PyTuple_GET_SIZE(args);
43594359
++i) {
4360-
PyObject *pair = PySequence_GetItem(fields, i);
4360+
PyObject *pair = PySequence_GetItem(fields, i - index);
43614361
PyObject *name, *val;
43624362
int res;
43634363
if (!pair)
@@ -4367,7 +4367,7 @@ _init_pos_args(PyObject *self, PyTypeObject *type,
43674367
Py_DECREF(pair);
43684368
return -1;
43694369
}
4370-
val = PyTuple_GET_ITEM(args, i + index);
4370+
val = PyTuple_GET_ITEM(args, i);
43714371
if (kwds) {
43724372
res = PyDict_Contains(kwds, name);
43734373
if (res != 0) {
@@ -4388,7 +4388,7 @@ _init_pos_args(PyObject *self, PyTypeObject *type,
43884388
if (res == -1)
43894389
return -1;
43904390
}
4391-
return index + dict->length;
4391+
return dict->length;
43924392
}
43934393

43944394
static int

Modules/_ctypes/stgdict.c

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

696696
stgdict->size = aligned_size;
697697
stgdict->align = total_align;
698-
stgdict->length = len; /* ADD ffi_ofs? */
698+
stgdict->length = ffi_ofs + len;
699699

700700
/*
701701
* The value of MAX_STRUCT_SIZE depends on the platform Python is running on.

0 commit comments

Comments
 (0)