Skip to content

Commit

Permalink
[3.11] pythongh-62260: Fix ctypes.Structure subclassing with multiple…
Browse files Browse the repository at this point in the history
… layers (pythonGH-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.

(cherry picked from commit 5f3cc90)

Co-authored-by: Jeffrey Kintscher <49998481+websurfer5@users.noreply.github.com>
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
  • Loading branch information
websurfer5 and serhiy-storchaka committed Jan 1, 2024
1 parent 5169113 commit f13d772
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 6 deletions.
63 changes: 63 additions & 0 deletions Lib/ctypes/test/test_structures.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import platform
from platform import architecture as _architecture
import struct
import sys
import unittest
from ctypes.test import need_symbol
Expand All @@ -7,6 +9,7 @@
c_uint8, c_uint16, c_uint32,
c_short, c_ushort, c_int, c_uint,
c_long, c_ulong, c_longlong, c_ulonglong, c_float, c_double)
from ctypes.util import find_library
from struct import calcsize
import _ctypes_test
from test import support
Expand Down Expand Up @@ -479,6 +482,66 @@ class X(Structure):
self.assertEqual(s.first, got.first)
self.assertEqual(s.second, got.second)

def _test_issue18060(self, Vector):
# The call to atan2() should succeed if the
# class fields were correctly cloned in the
# subclasses. Otherwise, it will segfault.
if sys.platform == 'win32':
libm = CDLL(find_library('msvcrt.dll'))
else:
libm = CDLL(find_library('m'))

libm.atan2.argtypes = [Vector]
libm.atan2.restype = c_double

arg = Vector(y=0.0, x=-1.0)
self.assertAlmostEqual(libm.atan2(arg), 3.141592653589793)

@unittest.skipIf(_architecture() == ('64bit', 'WindowsPE'), "can't test Windows x64 build")
@unittest.skipUnless(sys.byteorder == 'little', "can't test on this platform")
def test_issue18060_a(self):
# This test case calls
# PyCStructUnionType_update_stgdict() for each
# _fields_ assignment, and PyCStgDict_clone()
# for the Mid and Vector class definitions.
class Base(Structure):
_fields_ = [('y', c_double),
('x', c_double)]
class Mid(Base):
pass
Mid._fields_ = []
class Vector(Mid): pass
self._test_issue18060(Vector)

@unittest.skipIf(_architecture() == ('64bit', 'WindowsPE'), "can't test Windows x64 build")
@unittest.skipUnless(sys.byteorder == 'little', "can't test on this platform")
def test_issue18060_b(self):
# This test case calls
# PyCStructUnionType_update_stgdict() for each
# _fields_ assignment.
class Base(Structure):
_fields_ = [('y', c_double),
('x', c_double)]
class Mid(Base):
_fields_ = []
class Vector(Mid):
_fields_ = []
self._test_issue18060(Vector)

@unittest.skipIf(_architecture() == ('64bit', 'WindowsPE'), "can't test Windows x64 build")
@unittest.skipUnless(sys.byteorder == 'little', "can't test on this platform")
def test_issue18060_c(self):
# This test case calls
# PyCStructUnionType_update_stgdict() for each
# _fields_ assignment.
class Base(Structure):
_fields_ = [('y', c_double)]
class Mid(Base):
_fields_ = []
class Vector(Mid):
_fields_ = [('x', c_double)]
self._test_issue18060(Vector)

def test_array_in_struct(self):
# See bpo-22273

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
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.

10 changes: 5 additions & 5 deletions Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -4387,10 +4387,10 @@ _init_pos_args(PyObject *self, PyTypeObject *type,
return index;
}

for (i = 0;
i < dict->length && (i+index) < PyTuple_GET_SIZE(args);
for (i = index;
i < dict->length && i < PyTuple_GET_SIZE(args);
++i) {
PyObject *pair = PySequence_GetItem(fields, i);
PyObject *pair = PySequence_GetItem(fields, i - index);
PyObject *name, *val;
int res;
if (!pair)
Expand All @@ -4400,7 +4400,7 @@ _init_pos_args(PyObject *self, PyTypeObject *type,
Py_DECREF(pair);
return -1;
}
val = PyTuple_GET_ITEM(args, i + index);
val = PyTuple_GET_ITEM(args, i);
if (kwds) {
res = PyDict_Contains(kwds, name);
if (res != 0) {
Expand All @@ -4421,7 +4421,7 @@ _init_pos_args(PyObject *self, PyTypeObject *type,
if (res == -1)
return -1;
}
return index + dict->length;
return dict->length;
}

static int
Expand Down
2 changes: 1 addition & 1 deletion Modules/_ctypes/stgdict.c
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct

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

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

0 comments on commit f13d772

Please sign in to comment.