summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeffrey Kintscher <49998481+websurfer5@users.noreply.github.com>2024-01-01 16:24:24 (GMT)
committerGitHub <noreply@github.com>2024-01-01 16:24:24 (GMT)
commit5f3cc90a12d6df404fd6f48a0df1334902e271f2 (patch)
treeb0939a5ee6d6b2477179fc74f31fe854fcc57091
parent4036e48d59b0f9e8057e01458ab7df3dfd323a10 (diff)
downloadcpython-5f3cc90a12d6df404fd6f48a0df1334902e271f2.zip
cpython-5f3cc90a12d6df404fd6f48a0df1334902e271f2.tar.gz
cpython-5f3cc90a12d6df404fd6f48a0df1334902e271f2.tar.bz2
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 <storchaka@gmail.com>
-rw-r--r--Lib/test/test_ctypes/test_structures.py63
-rw-r--r--Misc/NEWS.d/next/Library/2019-05-17-07-22-33.bpo-18060.5mqTQM.rst2
-rw-r--r--Modules/_ctypes/_ctypes.c10
-rw-r--r--Modules/_ctypes/stgdict.c2
4 files changed, 70 insertions, 7 deletions
diff --git a/Lib/test/test_ctypes/test_structures.py b/Lib/test/test_ctypes/test_structures.py
index 21039f0..3eafc77 100644
--- a/Lib/test/test_ctypes/test_structures.py
+++ b/Lib/test/test_ctypes/test_structures.py
@@ -1,5 +1,5 @@
import _ctypes_test
-import platform
+from platform import architecture as _architecture
import struct
import sys
import unittest
@@ -8,6 +8,7 @@ from ctypes import (CDLL, Array, Structure, Union, POINTER, sizeof, byref, align
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
from collections import namedtuple
from test import support
@@ -472,6 +473,66 @@ class StructureTestCase(unittest.TestCase):
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
diff --git a/Misc/NEWS.d/next/Library/2019-05-17-07-22-33.bpo-18060.5mqTQM.rst b/Misc/NEWS.d/next/Library/2019-05-17-07-22-33.bpo-18060.5mqTQM.rst
new file mode 100644
index 0000000..3fefbc3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-17-07-22-33.bpo-18060.5mqTQM.rst
@@ -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.
+
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index f909a94..fc16b91 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -4354,10 +4354,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)
@@ -4367,7 +4367,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) {
@@ -4388,7 +4388,7 @@ _init_pos_args(PyObject *self, PyTypeObject *type,
if (res == -1)
return -1;
}
- return index + dict->length;
+ return dict->length;
}
static int
diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c
index dfdb96b..fb3e20e 100644
--- a/Modules/_ctypes/stgdict.c
+++ b/Modules/_ctypes/stgdict.c
@@ -695,7 +695,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
stgdict->size = aligned_size;
stgdict->align = total_align;
- stgdict->length = len; /* ADD ffi_ofs? */
+ stgdict->length = ffi_ofs + len;
/*
* The value of MAX_STRUCT_SIZE depends on the platform Python is running on.