diff options
author | Victor Stinner <vstinner@redhat.com> | 2019-08-30 12:30:33 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-30 12:30:33 (GMT) |
commit | 96b4087ce784ee7434dffdf69c475f5b40543982 (patch) | |
tree | ae440df23a43a6bbfd6e6f00d6c353ae3c88dd52 /Lib/ctypes/test | |
parent | 6a650aaf7735e30636db2721247f317064c2cfd4 (diff) | |
download | cpython-96b4087ce784ee7434dffdf69c475f5b40543982.zip cpython-96b4087ce784ee7434dffdf69c475f5b40543982.tar.gz cpython-96b4087ce784ee7434dffdf69c475f5b40543982.tar.bz2 |
bpo-37140: Fix StructUnionType_paramfunc() (GH-15612)
Fix a ctypes regression of Python 3.8. When a ctypes.Structure is
passed by copy to a function, ctypes internals created a temporary
object which had the side effect of calling the structure finalizer
(__del__) twice. The Python semantics requires a finalizer to be
called exactly once. Fix ctypes internals to no longer call the
finalizer twice.
Create a new internal StructParam_Type which is only used by
_ctypes_callproc() to call PyMem_Free(ptr) on Py_DECREF(argument).
StructUnionType_paramfunc() creates such object.
Diffstat (limited to 'Lib/ctypes/test')
-rw-r--r-- | Lib/ctypes/test/test_structures.py | 51 |
1 files changed, 45 insertions, 6 deletions
diff --git a/Lib/ctypes/test/test_structures.py b/Lib/ctypes/test/test_structures.py index d1ea43b..fda1045 100644 --- a/Lib/ctypes/test/test_structures.py +++ b/Lib/ctypes/test/test_structures.py @@ -3,7 +3,7 @@ from ctypes import * from ctypes.test import need_symbol from struct import calcsize import _ctypes_test -import test.support +from test import support class SubclassesTest(unittest.TestCase): def test_subclass(self): @@ -202,7 +202,7 @@ class StructureTestCase(unittest.TestCase): "_pack_": -1} self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) - @test.support.cpython_only + @support.cpython_only def test_packed_c_limits(self): # Issue 15989 import _testcapi @@ -396,27 +396,66 @@ class StructureTestCase(unittest.TestCase): 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): + # This should mirror the Test structure + # in Modules/_ctypes/_ctypes_test.c + class Test(Structure): _fields_ = [ ('first', c_ulong), ('second', c_ulong), ('third', c_ulong), ] - s = X() + s = Test() 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.argtypes = (Test,) func.restype = None func(s) self.assertEqual(s.first, 0xdeadbeef) self.assertEqual(s.second, 0xcafebabe) self.assertEqual(s.third, 0x0bad1dea) + def test_pass_by_value_finalizer(self): + # bpo-37140: Similar to test_pass_by_value(), but the Python structure + # has a finalizer (__del__() method): the finalizer must only be called + # once. + + finalizer_calls = [] + + class Test(Structure): + _fields_ = [ + ('first', c_ulong), + ('second', c_ulong), + ('third', c_ulong), + ] + def __del__(self): + finalizer_calls.append("called") + + s = Test(1, 2, 3) + # Test the StructUnionType_paramfunc() code path which copies the + # structure: if the stucture is larger than sizeof(void*). + self.assertGreater(sizeof(s), sizeof(c_void_p)) + + dll = CDLL(_ctypes_test.__file__) + func = dll._testfunc_large_struct_update_value + func.argtypes = (Test,) + func.restype = None + func(s) + # bpo-37140: Passing the structure by refrence must not call + # its finalizer! + self.assertEqual(finalizer_calls, []) + self.assertEqual(s.first, 1) + self.assertEqual(s.second, 2) + self.assertEqual(s.third, 3) + + # The finalizer must be called exactly once + s = None + support.gc_collect() + self.assertEqual(finalizer_calls, ["called"]) + def test_pass_by_value_in_register(self): class X(Structure): _fields_ = [ |