https://github.com/python/cpython/commit/5f3cc90a12d6df404fd6f48a0df1334902e271f2
commit: 5f3cc90a12d6df404fd6f48a0df1334902e271f2
branch: main
author: Jeffrey Kintscher <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2024-01-01T18:24:24+02:00
summary:

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]>

files:
A Misc/NEWS.d/next/Library/2019-05-17-07-22-33.bpo-18060.5mqTQM.rst
M Lib/test/test_ctypes/test_structures.py
M Modules/_ctypes/_ctypes.c
M Modules/_ctypes/stgdict.c

diff --git a/Lib/test/test_ctypes/test_structures.py 
b/Lib/test/test_ctypes/test_structures.py
index 21039f04947507..3eafc77ca70aea 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 @@
                     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 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
 
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 00000000000000..3fefbc3efb63c0
--- /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 f909a9496b6526..fc16b9176fd1c0 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 dfdb96b0e7258a..fb3e20e8db3e27 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.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to