Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit 3243f8c

Browse files
authored
bpo-29565: Corrected ctypes passing of large structs by value on Windows AMD64 (GH-168) (pythonGH-8625)
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). (cherry picked from commit a86339b)
1 parent 894940b commit 3243f8c

File tree

4 files changed

+57
-0
lines changed

4 files changed

+57
-0
lines changed

Lib/ctypes/test/test_callbacks.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ def callback(a, b, c, d, e):
250250
def test_callback_large_struct(self):
251251
class Check: pass
252252

253+
# This should mirror the structure in Modules/_ctypes/_ctypes_test.c
253254
class X(Structure):
254255
_fields_ = [
255256
('first', c_ulong),
@@ -261,6 +262,11 @@ def callback(check, s):
261262
check.first = s.first
262263
check.second = s.second
263264
check.third = s.third
265+
# See issue #29565.
266+
# The structure should be passed by value, so
267+
# any changes to it should not be reflected in
268+
# the value passed
269+
s.first = s.second = s.third = 0x0badf00d
264270

265271
check = Check()
266272
s = X()
@@ -281,6 +287,11 @@ def callback(check, s):
281287
self.assertEqual(check.first, 0xdeadbeef)
282288
self.assertEqual(check.second, 0xcafebabe)
283289
self.assertEqual(check.third, 0x0bad1dea)
290+
# See issue #29565.
291+
# Ensure that the original struct is unchanged.
292+
self.assertEqual(s.first, check.first)
293+
self.assertEqual(s.second, check.second)
294+
self.assertEqual(s.third, check.third)
284295

285296
################################################################
286297

Lib/ctypes/test/test_structures.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from ctypes.test import need_symbol
44
from struct import calcsize
55
import _testcapi
6+
import _ctypes_test
67

78
class SubclassesTest(unittest.TestCase):
89
def test_subclass(self):
@@ -401,6 +402,28 @@ class Z(Y):
401402
(1, 0, 0, 0, 0, 0))
402403
self.assertRaises(TypeError, lambda: Z(1, 2, 3, 4, 5, 6, 7))
403404

405+
def test_pass_by_value(self):
406+
# This should mirror the structure in Modules/_ctypes/_ctypes_test.c
407+
class X(Structure):
408+
_fields_ = [
409+
('first', c_ulong),
410+
('second', c_ulong),
411+
('third', c_ulong),
412+
]
413+
414+
s = X()
415+
s.first = 0xdeadbeef
416+
s.second = 0xcafebabe
417+
s.third = 0x0bad1dea
418+
dll = CDLL(_ctypes_test.__file__)
419+
func = dll._testfunc_large_struct_update_value
420+
func.argtypes = (X,)
421+
func.restype = None
422+
func(s)
423+
self.assertEqual(s.first, 0xdeadbeef)
424+
self.assertEqual(s.second, 0xcafebabe)
425+
self.assertEqual(s.third, 0x0bad1dea)
426+
404427
class PointerMemberTestCase(unittest.TestCase):
405428

406429
def test(self):

Modules/_ctypes/_ctypes_test.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ _testfunc_cbk_large_struct(Test in, void (*func)(Test))
5252
func(in);
5353
}
5454

55+
/*
56+
* See issue 29565. Update a structure passed by value;
57+
* the caller should not see any change.
58+
*/
59+
60+
EXPORT(void)
61+
_testfunc_large_struct_update_value(Test in)
62+
{
63+
in.first = 0x0badf00d;
64+
in.second = 0x0badf00d;
65+
in.third = 0x0badf00d;
66+
}
67+
5568
EXPORT(void)testfunc_array(int values[4])
5669
{
5770
printf("testfunc_array %d %d %d %d\n",

Modules/_ctypes/libffi_msvc/ffi.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,16 @@ ffi_call(/*@dependent@*/ ffi_cif *cif,
220220
break;
221221
#else
222222
case FFI_SYSV:
223+
/* If a single argument takes more than 8 bytes,
224+
then a copy is passed by reference. */
225+
for (unsigned i = 0; i < cif->nargs; i++) {
226+
size_t z = cif->arg_types[i]->size;
227+
if (z > 8) {
228+
void *temp = alloca(z);
229+
memcpy(temp, avalue[i], z);
230+
avalue[i] = temp;
231+
}
232+
}
223233
/*@-usedef@*/
224234
return ffi_call_AMD64(ffi_prep_args, &ecif, cif->bytes,
225235
cif->flags, ecif.rvalue, fn);

0 commit comments

Comments
 (0)