summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVinay Sajip <vinay_sajip@yahoo.co.uk>2017-02-20 00:16:33 (GMT)
committerGitHub <noreply@github.com>2017-02-20 00:16:33 (GMT)
commita86339b83fbd0932e0529a3c91935e997a234582 (patch)
tree34b0c69efbc4097a78b6292050460d8ba1df6265
parent3eea8c67fa870c6e2b7a521d292afe7fe3e95f58 (diff)
downloadcpython-a86339b83fbd0932e0529a3c91935e997a234582.zip
cpython-a86339b83fbd0932e0529a3c91935e997a234582.tar.gz
cpython-a86339b83fbd0932e0529a3c91935e997a234582.tar.bz2
Fixed bpo-29565: Corrected ctypes passing of large structs by value on Windows AMD64. (#168)
* Fixed bpo-29565: Corrected ctypes passing of large structs by value. Added code and test to check that when a structure passed by value is large enough to need to be passed by reference, a copy of the original structure is passed. The callee updates the passed-in value, and the test verifies that the caller's copy is unchanged. A similar change was also added to the test added for bpo-20160 (that test was passing, but the changes should guard against regressions). * Reverted unintended whitespace changes.
-rw-r--r--Lib/ctypes/test/test_callbacks.py11
-rw-r--r--Lib/ctypes/test/test_structures.py23
-rw-r--r--Modules/_ctypes/_ctypes_test.c13
-rw-r--r--Modules/_ctypes/libffi_msvc/ffi.c10
4 files changed, 57 insertions, 0 deletions
diff --git a/Lib/ctypes/test/test_callbacks.py b/Lib/ctypes/test/test_callbacks.py
index 8eac58f..f622093 100644
--- a/Lib/ctypes/test/test_callbacks.py
+++ b/Lib/ctypes/test/test_callbacks.py
@@ -244,6 +244,7 @@ class SampleCallbacksTestCase(unittest.TestCase):
def test_callback_large_struct(self):
class Check: pass
+ # This should mirror the structure in Modules/_ctypes/_ctypes_test.c
class X(Structure):
_fields_ = [
('first', c_ulong),
@@ -255,6 +256,11 @@ class SampleCallbacksTestCase(unittest.TestCase):
check.first = s.first
check.second = s.second
check.third = s.third
+ # See issue #29565.
+ # The structure should be passed by value, so
+ # any changes to it should not be reflected in
+ # the value passed
+ s.first = s.second = s.third = 0x0badf00d
check = Check()
s = X()
@@ -275,6 +281,11 @@ class SampleCallbacksTestCase(unittest.TestCase):
self.assertEqual(check.first, 0xdeadbeef)
self.assertEqual(check.second, 0xcafebabe)
self.assertEqual(check.third, 0x0bad1dea)
+ # See issue #29565.
+ # Ensure that the original struct is unchanged.
+ self.assertEqual(s.first, check.first)
+ self.assertEqual(s.second, check.second)
+ self.assertEqual(s.third, check.third)
################################################################
diff --git a/Lib/ctypes/test/test_structures.py b/Lib/ctypes/test/test_structures.py
index 8f6fe5f..3eded77 100644
--- a/Lib/ctypes/test/test_structures.py
+++ b/Lib/ctypes/test/test_structures.py
@@ -3,6 +3,7 @@ from ctypes import *
from ctypes.test import need_symbol
from struct import calcsize
import _testcapi
+import _ctypes_test
class SubclassesTest(unittest.TestCase):
def test_subclass(self):
@@ -391,6 +392,28 @@ class StructureTestCase(unittest.TestCase):
(1, 0, 0, 0, 0, 0))
self.assertRaises(TypeError, lambda: Z(1, 2, 3, 4, 5, 6, 7))
+ def test_pass_by_value(self):
+ # This should mirror the structure in Modules/_ctypes/_ctypes_test.c
+ class X(Structure):
+ _fields_ = [
+ ('first', c_ulong),
+ ('second', c_ulong),
+ ('third', c_ulong),
+ ]
+
+ s = X()
+ s.first = 0xdeadbeef
+ s.second = 0xcafebabe
+ s.third = 0x0bad1dea
+ dll = CDLL(_ctypes_test.__file__)
+ func = dll._testfunc_large_struct_update_value
+ func.argtypes = (X,)
+ func.restype = None
+ func(s)
+ self.assertEqual(s.first, 0xdeadbeef)
+ self.assertEqual(s.second, 0xcafebabe)
+ self.assertEqual(s.third, 0x0bad1dea)
+
class PointerMemberTestCase(unittest.TestCase):
def test(self):
diff --git a/Modules/_ctypes/_ctypes_test.c b/Modules/_ctypes/_ctypes_test.c
index 92b5adb..9410c7f 100644
--- a/Modules/_ctypes/_ctypes_test.c
+++ b/Modules/_ctypes/_ctypes_test.c
@@ -44,6 +44,19 @@ _testfunc_cbk_large_struct(Test in, void (*func)(Test))
func(in);
}
+/*
+ * See issue 29565. Update a structure passed by value;
+ * the caller should not see any change.
+ */
+
+EXPORT(void)
+_testfunc_large_struct_update_value(Test in)
+{
+ in.first = 0x0badf00d;
+ in.second = 0x0badf00d;
+ in.third = 0x0badf00d;
+}
+
EXPORT(void)testfunc_array(int values[4])
{
printf("testfunc_array %d %d %d %d\n",
diff --git a/Modules/_ctypes/libffi_msvc/ffi.c b/Modules/_ctypes/libffi_msvc/ffi.c
index 1d82929..91a27dc 100644
--- a/Modules/_ctypes/libffi_msvc/ffi.c
+++ b/Modules/_ctypes/libffi_msvc/ffi.c
@@ -239,6 +239,16 @@ ffi_call(/*@dependent@*/ ffi_cif *cif,
break;
#else
case FFI_SYSV:
+ /* If a single argument takes more than 8 bytes,
+ then a copy is passed by reference. */
+ for (unsigned i = 0; i < cif->nargs; i++) {
+ size_t z = cif->arg_types[i]->size;
+ if (z > 8) {
+ void *temp = alloca(z);
+ memcpy(temp, avalue[i], z);
+ avalue[i] = temp;
+ }
+ }
/*@-usedef@*/
return ffi_call_AMD64(ffi_prep_args, &ecif, cif->bytes,
cif->flags, ecif.rvalue, fn);