Deprecate _pointer_type_cache and calling POINTER on a string.
Co-authored-by: neonene <53406459+neonene@users.noreply.github.com>
Co-authored-by: Jun Komoda <45822440+junkmd@users.noreply.github.com>
Co-authored-by: Petr Viktorin <encukou@gmail.com>
.. function:: POINTER(type, /)
- Create and return a new ctypes pointer type. Pointer types are cached and
+ Create or return a ctypes pointer type. Pointer types are cached and
reused internally, so calling this function repeatedly is cheap.
*type* must be a ctypes type.
+ .. impl-detail::
+
+ The resulting pointer type is cached in the ``__pointer_type__``
+ attribute of *type*.
+ It is possible to set this attribute before the first call to
+ ``POINTER`` in order to set a custom pointer type.
+ However, doing this is discouraged: manually creating a suitable
+ pointer type is difficult without relying on implementation
+ details that may change in future Python versions.
+
.. function:: pointer(obj, /)
library. *name* is the name of the symbol that exports the data, *library*
is the loaded shared library.
+ Common class variables of ctypes data types:
+
+ .. attribute:: __pointer_type__
+
+ The pointer type that was created by calling
+ :func:`POINTER` for corresponding ctypes data type. If a pointer type
+ was not yet created, the attribute is missing.
+
+ .. versionadded:: next
+
Common instance variables of ctypes data types:
.. attribute:: _b_base_
loaded by the current process.
(Contributed by Brian Ward in :gh:`119349`.)
+* Move :func:`ctypes.POINTER` types cache from a global internal cache
+ (``_pointer_type_cache``) to the :attr:`ctypes._CData.__pointer_type__`
+ attribute of the corresponding :mod:`ctypes` types.
+ This will stop the cache from growing without limits in some situations.
+ (Contributed by Sergey Miryanov in :gh:`100926`).
+
* The :class:`ctypes.py_object` type now supports subscription,
making it a :term:`generic type`.
(Contributed by Brian Schubert in :gh:`132168`.)
-
datetime
--------
:func:`codecs.open` is now deprecated. Use :func:`open` instead.
(Contributed by Inada Naoki in :gh:`133036`.)
+* :mod:`ctypes`:
+ Calling :func:`ctypes.POINTER` on a string is deprecated.
+ Use :ref:`ctypes-incomplete-types` for self-referential structures.
+ Also, the internal ``ctypes._pointer_type_cache`` is deprecated.
+ See :func:`ctypes.POINTER` for updated implementation details.
+ (Contributed by Sergey Myrianov in :gh:`100926`.)
+
* :mod:`functools`:
Calling the Python implementation of :func:`functools.reduce` with *function*
or *sequence* as keyword arguments is now deprecated.
class c_bool(_SimpleCData):
_type_ = "?"
-from _ctypes import POINTER, pointer, _pointer_type_cache
+def POINTER(cls):
+ """Create and return a new ctypes pointer type.
+
+ Pointer types are cached and reused internally,
+ so calling this function repeatedly is cheap.
+ """
+ if cls is None:
+ return c_void_p
+ try:
+ return cls.__pointer_type__
+ except AttributeError:
+ pass
+ if isinstance(cls, str):
+ # handle old-style incomplete types (see test_ctypes.test_incomplete)
+ import warnings
+ warnings._deprecated("ctypes.POINTER with string", remove=(3, 19))
+ try:
+ return _pointer_type_cache_fallback[cls]
+ except KeyError:
+ result = type(f'LP_{cls}', (_Pointer,), {})
+ _pointer_type_cache_fallback[cls] = result
+ return result
+
+ # create pointer type and set __pointer_type__ for cls
+ return type(f'LP_{cls.__name__}', (_Pointer,), {'_type_': cls})
+
+def pointer(obj):
+ """Create a new pointer instance, pointing to 'obj'.
+
+ The returned object is of the type POINTER(type(obj)). Note that if you
+ just want to pass a pointer to an object to a foreign function call, you
+ should use byref(obj) which is much faster.
+ """
+ typ = POINTER(type(obj))
+ return typ(obj)
+
+class _PointerTypeCache:
+ def __setitem__(self, cls, pointer_type):
+ import warnings
+ warnings._deprecated("ctypes._pointer_type_cache", remove=(3, 19))
+ try:
+ cls.__pointer_type__ = pointer_type
+ except AttributeError:
+ _pointer_type_cache_fallback[cls] = pointer_type
+
+ def __getitem__(self, cls):
+ import warnings
+ warnings._deprecated("ctypes._pointer_type_cache", remove=(3, 19))
+ try:
+ return cls.__pointer_type__
+ except AttributeError:
+ return _pointer_type_cache_fallback[cls]
+
+ def get(self, cls, default=None):
+ import warnings
+ warnings._deprecated("ctypes._pointer_type_cache", remove=(3, 19))
+ try:
+ return cls.__pointer_type__
+ except AttributeError:
+ return _pointer_type_cache_fallback.get(cls, default)
+
+ def __contains__(self, cls):
+ return hasattr(cls, '__pointer_type__')
+
+_pointer_type_cache_fallback = {}
+_pointer_type_cache = _PointerTypeCache()
class c_wchar_p(_SimpleCData):
_type_ = "Z"
_type_ = "u"
def _reset_cache():
- _pointer_type_cache.clear()
+ _pointer_type_cache_fallback.clear()
_c_functype_cache.clear()
if _os.name == "nt":
_win_functype_cache.clear()
POINTER(c_wchar).from_param = c_wchar_p.from_param
# _SimpleCData.c_char_p_from_param
POINTER(c_char).from_param = c_char_p.from_param
- _pointer_type_cache[None] = c_void_p
def create_unicode_buffer(init, size=None):
"""create_unicode_buffer(aString) -> character array
def SetPointerType(pointer, cls):
import warnings
warnings._deprecated("ctypes.SetPointerType", remove=(3, 15))
- if _pointer_type_cache.get(cls, None) is not None:
- raise RuntimeError("This type already exists in the cache")
- if id(pointer) not in _pointer_type_cache:
- raise RuntimeError("What's this???")
pointer.set_type(cls)
- _pointer_type_cache[cls] = pointer
- del _pointer_type_cache[id(pointer)]
def ARRAY(typ, len):
return typ * len
self.assertEqual(len(data), sizeof(TestStructure))
ptr = POINTER(TestStructure)
s = cast(data, ptr)[0]
- del ctypes._pointer_type_cache[TestStructure]
self.assertEqual(s.point.x, 1)
self.assertEqual(s.point.y, 2)
self.assertEqual(len(data), sizeof(TestUnion))
ptr = POINTER(TestUnion)
s = cast(data, ptr)[0]
- del ctypes._pointer_type_cache[TestUnion]
self.assertEqual(s.point.x, 1)
self.assertEqual(s.point.y, 2)
import unittest
from test.support import MS_WINDOWS
import ctypes
-from ctypes import POINTER, c_void_p
+from ctypes import POINTER, Structure, c_void_p
-from ._support import PyCSimpleType
+from ._support import PyCSimpleType, PyCPointerType, PyCStructType
-class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
- def tearDown(self):
- # to not leak references, we must clean _pointer_type_cache
- ctypes._reset_cache()
+def set_non_ctypes_pointer_type(cls, pointer_type):
+ cls.__pointer_type__ = pointer_type
+class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
def test_creating_pointer_in_dunder_new_1(self):
# Test metaclass whose instances are C types; when the type is
# created it automatically creates a pointer type for itself.
else:
ptr_bases = (self, POINTER(bases[0]))
p = p_meta(f"POINTER({self.__name__})", ptr_bases, {})
- ctypes._pointer_type_cache[self] = p
+ set_non_ctypes_pointer_type(self, p)
return self
class p_meta(PyCSimpleType, ct_meta):
class PtrBase(c_void_p, metaclass=p_meta):
pass
+ ptr_base_pointer = POINTER(PtrBase)
+
class CtBase(object, metaclass=ct_meta):
pass
+ ct_base_pointer = POINTER(CtBase)
+
class Sub(CtBase):
pass
+ sub_pointer = POINTER(Sub)
+
class Sub2(Sub):
pass
+ sub2_pointer = POINTER(Sub2)
+
+ self.assertIsNot(ptr_base_pointer, ct_base_pointer)
+ self.assertIsNot(ct_base_pointer, sub_pointer)
+ self.assertIsNot(sub_pointer, sub2_pointer)
+
self.assertIsInstance(POINTER(Sub2), p_meta)
self.assertIsSubclass(POINTER(Sub2), Sub2)
self.assertIsSubclass(POINTER(Sub2), POINTER(Sub))
self.assertIsSubclass(POINTER(Sub), POINTER(CtBase))
+ self.assertIs(POINTER(Sub2), sub2_pointer)
+ self.assertIs(POINTER(Sub), sub_pointer)
+ self.assertIs(POINTER(CtBase), ct_base_pointer)
+
def test_creating_pointer_in_dunder_new_2(self):
# A simpler variant of the above, used in `CoClass` of the `comtypes`
# project.
if isinstance(self, p_meta):
return self
p = p_meta(f"POINTER({self.__name__})", (self, c_void_p), {})
- ctypes._pointer_type_cache[self] = p
+ set_non_ctypes_pointer_type(self, p)
return self
class p_meta(PyCSimpleType, ct_meta):
class Core(object):
pass
+ with self.assertRaisesRegex(TypeError, "must have storage info"):
+ POINTER(Core)
+
class CtBase(Core, metaclass=ct_meta):
pass
+ ct_base_pointer = POINTER(CtBase)
+
class Sub(CtBase):
pass
+ sub_pointer = POINTER(Sub)
+
+ self.assertIsNot(ct_base_pointer, sub_pointer)
+
self.assertIsInstance(POINTER(Sub), p_meta)
self.assertIsSubclass(POINTER(Sub), Sub)
+ self.assertIs(POINTER(Sub), sub_pointer)
+ self.assertIs(POINTER(CtBase), ct_base_pointer)
+
def test_creating_pointer_in_dunder_init_1(self):
class ct_meta(type):
def __init__(self, name, bases, namespace):
else:
ptr_bases = (self, POINTER(bases[0]))
p = p_meta(f"POINTER({self.__name__})", ptr_bases, {})
- ctypes._pointer_type_cache[self] = p
+ set_non_ctypes_pointer_type(self, p)
class p_meta(PyCSimpleType, ct_meta):
pass
class PtrBase(c_void_p, metaclass=p_meta):
pass
+ ptr_base_pointer = POINTER(PtrBase)
+
class CtBase(object, metaclass=ct_meta):
pass
+ ct_base_pointer = POINTER(CtBase)
+
class Sub(CtBase):
pass
+ sub_pointer = POINTER(Sub)
+
class Sub2(Sub):
pass
+ sub2_pointer = POINTER(Sub2)
+
+ self.assertIsNot(ptr_base_pointer, ct_base_pointer)
+ self.assertIsNot(ct_base_pointer, sub_pointer)
+ self.assertIsNot(sub_pointer, sub2_pointer)
+
self.assertIsInstance(POINTER(Sub2), p_meta)
self.assertIsSubclass(POINTER(Sub2), Sub2)
self.assertIsSubclass(POINTER(Sub2), POINTER(Sub))
self.assertIsSubclass(POINTER(Sub), POINTER(CtBase))
+ self.assertIs(POINTER(PtrBase), ptr_base_pointer)
+ self.assertIs(POINTER(CtBase), ct_base_pointer)
+ self.assertIs(POINTER(Sub), sub_pointer)
+ self.assertIs(POINTER(Sub2), sub2_pointer)
+
def test_creating_pointer_in_dunder_init_2(self):
class ct_meta(type):
def __init__(self, name, bases, namespace):
if isinstance(self, p_meta):
return
p = p_meta(f"POINTER({self.__name__})", (self, c_void_p), {})
- ctypes._pointer_type_cache[self] = p
+ set_non_ctypes_pointer_type(self, p)
class p_meta(PyCSimpleType, ct_meta):
pass
class CtBase(Core, metaclass=ct_meta):
pass
+ ct_base_pointer = POINTER(CtBase)
+
class Sub(CtBase):
pass
+ sub_pointer = POINTER(Sub)
+
+ self.assertIsNot(ct_base_pointer, sub_pointer)
+
self.assertIsInstance(POINTER(Sub), p_meta)
self.assertIsSubclass(POINTER(Sub), Sub)
+ self.assertIs(POINTER(CtBase), ct_base_pointer)
+ self.assertIs(POINTER(Sub), sub_pointer)
+
def test_bad_type_message(self):
"""Verify the error message that lists all available type codes"""
# (The string is generated at runtime, so this checks the underlying
if not MS_WINDOWS:
expected_type_chars.remove('X')
self.assertIn("'" + ''.join(expected_type_chars) + "'", message)
+
+ def test_creating_pointer_in_dunder_init_3(self):
+ """Check if interfcase subclasses properly creates according internal
+ pointer types. But not the same as external pointer types.
+ """
+
+ class StructureMeta(PyCStructType):
+ def __new__(cls, name, bases, dct, /, create_pointer_type=True):
+ assert len(bases) == 1, bases
+ return super().__new__(cls, name, bases, dct)
+
+ def __init__(self, name, bases, dct, /, create_pointer_type=True):
+
+ super().__init__(name, bases, dct)
+ if create_pointer_type:
+ p_bases = (POINTER(bases[0]),)
+ ns = {'_type_': self}
+ internal_pointer_type = PointerMeta(f"p{name}", p_bases, ns)
+ assert isinstance(internal_pointer_type, PyCPointerType)
+ assert self.__pointer_type__ is internal_pointer_type
+
+ class PointerMeta(PyCPointerType):
+ def __new__(cls, name, bases, dct):
+ target = dct.get('_type_', None)
+ if target is None:
+
+ # Create corresponding interface type and then set it as target
+ target = StructureMeta(
+ f"_{name}_",
+ (bases[0]._type_,),
+ {},
+ create_pointer_type=False
+ )
+ dct['_type_'] = target
+
+ pointer_type = super().__new__(cls, name, bases, dct)
+ assert not hasattr(target, '__pointer_type__')
+
+ return pointer_type
+
+ def __init__(self, name, bases, dct, /, create_pointer_type=True):
+ target = dct.get('_type_', None)
+ assert not hasattr(target, '__pointer_type__')
+ super().__init__(name, bases, dct)
+ assert target.__pointer_type__ is self
+
+
+ class Interface(Structure, metaclass=StructureMeta, create_pointer_type=False):
+ pass
+
+ class pInterface(POINTER(c_void_p), metaclass=PointerMeta):
+ _type_ = Interface
+
+ class IUnknown(Interface):
+ pass
+
+ class pIUnknown(pInterface):
+ pass
+
+ self.assertTrue(issubclass(POINTER(IUnknown), pInterface))
+
+ self.assertIs(POINTER(Interface), pInterface)
+ self.assertIsNot(POINTER(IUnknown), pIUnknown)
+
+ def test_creating_pointer_in_dunder_init_4(self):
+ """Check if interfcase subclasses properly creates according internal
+ pointer types, the same as external pointer types.
+ """
+ class StructureMeta(PyCStructType):
+ def __new__(cls, name, bases, dct, /, create_pointer_type=True):
+ assert len(bases) == 1, bases
+
+ return super().__new__(cls, name, bases, dct)
+
+ def __init__(self, name, bases, dct, /, create_pointer_type=True):
+
+ super().__init__(name, bases, dct)
+ if create_pointer_type:
+ p_bases = (POINTER(bases[0]),)
+ ns = {'_type_': self}
+ internal_pointer_type = PointerMeta(f"p{name}", p_bases, ns)
+ assert isinstance(internal_pointer_type, PyCPointerType)
+ assert self.__pointer_type__ is internal_pointer_type
+
+ class PointerMeta(PyCPointerType):
+ def __new__(cls, name, bases, dct):
+ target = dct.get('_type_', None)
+ assert target is not None
+ pointer_type = getattr(target, '__pointer_type__', None)
+
+ if pointer_type is None:
+ pointer_type = super().__new__(cls, name, bases, dct)
+
+ return pointer_type
+
+ def __init__(self, name, bases, dct, /, create_pointer_type=True):
+ target = dct.get('_type_', None)
+ if not hasattr(target, '__pointer_type__'):
+ # target.__pointer_type__ was created by super().__new__
+ super().__init__(name, bases, dct)
+
+ assert target.__pointer_type__ is self
+
+
+ class Interface(Structure, metaclass=StructureMeta, create_pointer_type=False):
+ pass
+
+ class pInterface(POINTER(c_void_p), metaclass=PointerMeta):
+ _type_ = Interface
+
+ class IUnknown(Interface):
+ pass
+
+ class pIUnknown(pInterface):
+ _type_ = IUnknown
+
+ self.assertTrue(issubclass(POINTER(IUnknown), pInterface))
+
+ self.assertIs(POINTER(Interface), pInterface)
+ self.assertIs(POINTER(IUnknown), pIUnknown)
+
+ def test_custom_pointer_cache_for_ctypes_type1(self):
+ # Check if PyCPointerType.__init__() caches a pointer type
+ # customized in the metatype's __new__().
+ class PointerMeta(PyCPointerType):
+ def __new__(cls, name, bases, namespace):
+ namespace["_type_"] = C
+ return super().__new__(cls, name, bases, namespace)
+
+ def __init__(self, name, bases, namespace):
+ assert not hasattr(C, '__pointer_type__')
+ super().__init__(name, bases, namespace)
+ assert C.__pointer_type__ is self
+
+ class C(c_void_p): # ctypes type
+ pass
+
+ class P(ctypes._Pointer, metaclass=PointerMeta):
+ pass
+
+ self.assertIs(P._type_, C)
+ self.assertIs(P, POINTER(C))
+
+ def test_custom_pointer_cache_for_ctypes_type2(self):
+ # Check if PyCPointerType.__init__() caches a pointer type
+ # customized in the metatype's __init__().
+ class PointerMeta(PyCPointerType):
+ def __init__(self, name, bases, namespace):
+ self._type_ = namespace["_type_"] = C
+ assert not hasattr(C, '__pointer_type__')
+ super().__init__(name, bases, namespace)
+ assert C.__pointer_type__ is self
+
+ class C(c_void_p): # ctypes type
+ pass
+
+ class P(ctypes._Pointer, metaclass=PointerMeta):
+ pass
+
+ self.assertIs(P._type_, C)
+ self.assertIs(P, POINTER(C))
import warnings
from ctypes import Structure, POINTER, pointer, c_char_p
+# String-based "incomplete pointers" were implemented in ctypes 0.6.3 (2003, when
+# ctypes was an external project). They made obsolete by the current
+# incomplete *types* (setting `_fields_` late) in 0.9.5 (2005).
+# ctypes was added to Python 2.5 (2006), without any mention in docs.
-# The incomplete pointer example from the tutorial
+# This tests incomplete pointer example from the old tutorial
+# (https://svn.python.org/projects/ctypes/tags/release_0_6_3/ctypes/docs/tutorial.stx)
class TestSetPointerType(unittest.TestCase):
def tearDown(self):
- # to not leak references, we must clean _pointer_type_cache
- ctypes._reset_cache()
+ ctypes._pointer_type_cache_fallback.clear()
def test_incomplete_example(self):
- lpcell = POINTER("cell")
+ with self.assertWarns(DeprecationWarning):
+ lpcell = POINTER("cell")
class cell(Structure):
_fields_ = [("name", c_char_p),
("next", lpcell)]
warnings.simplefilter('ignore', DeprecationWarning)
ctypes.SetPointerType(lpcell, cell)
+ self.assertIs(POINTER(cell), lpcell)
+
c1 = cell()
c1.name = b"foo"
c2 = cell()
self.assertEqual(result, [b"foo", b"bar"] * 4)
def test_deprecation(self):
- lpcell = POINTER("cell")
+ with self.assertWarns(DeprecationWarning):
+ lpcell = POINTER("cell")
class cell(Structure):
_fields_ = [("name", c_char_p),
("next", lpcell)]
with self.assertWarns(DeprecationWarning):
ctypes.SetPointerType(lpcell, cell)
+ self.assertIs(POINTER(cell), lpcell)
if __name__ == '__main__':
unittest.main()
import unittest
-from ctypes import (Structure, POINTER, pointer, _pointer_type_cache,
- c_char_p, c_int)
+from ctypes import (Structure, POINTER, pointer, c_char_p, c_int)
class SimpleTestCase(unittest.TestCase):
r.a[0].x = 42
r.a[0].y = 99
- # to avoid leaking when tests are run several times
- # clean up the types left in the cache.
- del _pointer_type_cache[POINT]
-
if __name__ == "__main__":
unittest.main()
import array
import ctypes
+import gc
import sys
import unittest
from ctypes import (CDLL, CFUNCTYPE, Structure,
- POINTER, pointer, _Pointer, _pointer_type_cache,
+ POINTER, pointer, _Pointer,
byref, sizeof,
c_void_p, c_char_p,
c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint,
c_long, c_ulong, c_longlong, c_ulonglong,
c_float, c_double)
+from ctypes import _pointer_type_cache, _pointer_type_cache_fallback
from test.support import import_helper
+from weakref import WeakSet
_ctypes_test = import_helper.import_module("_ctypes_test")
from ._support import (_CData, PyCPointerType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)
class PointersTestCase(unittest.TestCase):
+ def tearDown(self):
+ _pointer_type_cache_fallback.clear()
+
def test_inheritance_hierarchy(self):
self.assertEqual(_Pointer.mro(), [_Pointer, _CData, object])
addr = a.buffer_info()[0]
p = POINTER(POINTER(c_int))
+ def test_pointer_from_pointer(self):
+ p1 = POINTER(c_int)
+ p2 = POINTER(p1)
+
+ self.assertIsNot(p1, p2)
+ self.assertIs(p1.__pointer_type__, p2)
+ self.assertIs(p2._type_, p1)
+
def test_other(self):
class Table(Structure):
_fields_ = [("a", c_int),
pt.contents.c = 33
- del _pointer_type_cache[Table]
-
def test_basic(self):
p = pointer(c_int(42))
# Although a pointer can be indexed, it has no length
q = pointer(y)
pp[0] = q # <==
self.assertEqual(p[0], 6)
+
def test_c_void_p(self):
# http://sourceforge.net/tracker/?func=detail&aid=1518190&group_id=5470&atid=105470
if sizeof(c_void_p) == 4:
self.assertRaises(TypeError, c_void_p, 3.14) # make sure floats are NOT accepted
self.assertRaises(TypeError, c_void_p, object()) # nor other objects
+ def test_read_null_pointer(self):
+ null_ptr = POINTER(c_int)()
+ with self.assertRaisesRegex(ValueError, "NULL pointer access"):
+ null_ptr[0]
+
+ def test_write_null_pointer(self):
+ null_ptr = POINTER(c_int)()
+ with self.assertRaisesRegex(ValueError, "NULL pointer access"):
+ null_ptr[0] = 1
+
+ def test_set_pointer_to_null_and_read(self):
+ class Bar(Structure):
+ _fields_ = [("values", POINTER(c_int))]
+
+ bar = Bar()
+ bar.values = (c_int * 3)(1, 2, 3)
+
+ values = [bar.values[0], bar.values[1], bar.values[2]]
+ self.assertEqual(values, [1, 2, 3])
+
+ bar.values = None
+ with self.assertRaisesRegex(ValueError, "NULL pointer access"):
+ bar.values[0]
+
def test_pointers_bool(self):
# NULL pointers have a boolean False value, non-NULL pointers True.
self.assertEqual(bool(POINTER(c_int)()), False)
LargeNamedType = type('T' * 2 ** 25, (Structure,), {})
self.assertTrue(POINTER(LargeNamedType))
- # to not leak references, we must clean _pointer_type_cache
- del _pointer_type_cache[LargeNamedType]
-
def test_pointer_type_str_name(self):
large_string = 'T' * 2 ** 25
- P = POINTER(large_string)
+ with self.assertWarns(DeprecationWarning):
+ P = POINTER(large_string)
self.assertTrue(P)
- # to not leak references, we must clean _pointer_type_cache
- del _pointer_type_cache[id(P)]
-
def test_abstract(self):
self.assertRaises(TypeError, _Pointer.set_type, 42)
+ def test_pointer_types_equal(self):
+ t1 = POINTER(c_int)
+ t2 = POINTER(c_int)
+
+ self.assertIs(t1, t2)
+
+ p1 = t1(c_int(1))
+ p2 = pointer(c_int(1))
+
+ self.assertIsInstance(p1, t1)
+ self.assertIsInstance(p2, t1)
+
+ self.assertIs(type(p1), t1)
+ self.assertIs(type(p2), t1)
+
+ def test_incomplete_pointer_types_still_equal(self):
+ with self.assertWarns(DeprecationWarning):
+ t1 = POINTER("LP_C")
+ with self.assertWarns(DeprecationWarning):
+ t2 = POINTER("LP_C")
+
+ self.assertIs(t1, t2)
+
+ def test_incomplete_pointer_types_cannot_instantiate(self):
+ with self.assertWarns(DeprecationWarning):
+ t1 = POINTER("LP_C")
+ with self.assertRaisesRegex(TypeError, "has no _type_"):
+ t1()
+
+ def test_pointer_set_type_twice(self):
+ t1 = POINTER(c_int)
+ self.assertIs(c_int.__pointer_type__, t1)
+ self.assertIs(t1._type_, c_int)
+
+ t1.set_type(c_int)
+ self.assertIs(c_int.__pointer_type__, t1)
+ self.assertIs(t1._type_, c_int)
+
+ def test_pointer_set_wrong_type(self):
+ int_ptr = POINTER(c_int)
+ float_ptr = POINTER(c_float)
+ try:
+ class C(c_int):
+ pass
+
+ t1 = POINTER(c_int)
+ t2 = POINTER(c_float)
+ t1.set_type(c_float)
+ self.assertEqual(t1(c_float(1.5))[0], 1.5)
+ self.assertIs(t1._type_, c_float)
+ self.assertIs(c_int.__pointer_type__, t1)
+ self.assertIs(c_float.__pointer_type__, float_ptr)
+
+ t1.set_type(C)
+ self.assertEqual(t1(C(123))[0].value, 123)
+ self.assertIs(c_int.__pointer_type__, t1)
+ self.assertIs(c_float.__pointer_type__, float_ptr)
+ finally:
+ POINTER(c_int).set_type(c_int)
+ self.assertIs(POINTER(c_int), int_ptr)
+ self.assertIs(POINTER(c_int)._type_, c_int)
+ self.assertIs(c_int.__pointer_type__, int_ptr)
+
+ def test_pointer_not_ctypes_type(self):
+ with self.assertRaisesRegex(TypeError, "must have storage info"):
+ POINTER(int)
+
+ with self.assertRaisesRegex(TypeError, "must have storage info"):
+ pointer(int)
+
+ with self.assertRaisesRegex(TypeError, "must have storage info"):
+ pointer(int(1))
+
+ def test_pointer_set_python_type(self):
+ p1 = POINTER(c_int)
+ with self.assertRaisesRegex(TypeError, "must have storage info"):
+ p1.set_type(int)
+
+ def test_pointer_type_attribute_is_none(self):
+ class Cls(Structure):
+ _fields_ = (
+ ('a', c_int),
+ ('b', c_float),
+ )
+
+ with self.assertRaisesRegex(AttributeError, ".Cls'> has no attribute '__pointer_type__'"):
+ Cls.__pointer_type__
+
+ p = POINTER(Cls)
+ self.assertIs(Cls.__pointer_type__, p)
+
+ def test_arbitrary_pointer_type_attribute(self):
+ class Cls(Structure):
+ _fields_ = (
+ ('a', c_int),
+ ('b', c_float),
+ )
+
+ garbage = 'garbage'
+
+ P = POINTER(Cls)
+ self.assertIs(Cls.__pointer_type__, P)
+ Cls.__pointer_type__ = garbage
+ self.assertIs(Cls.__pointer_type__, garbage)
+ self.assertIs(POINTER(Cls), garbage)
+ self.assertIs(P._type_, Cls)
+
+ instance = Cls(1, 2.0)
+ pointer = P(instance)
+ self.assertEqual(pointer[0].a, 1)
+ self.assertEqual(pointer[0].b, 2)
+
+ del Cls.__pointer_type__
+
+ NewP = POINTER(Cls)
+ self.assertIsNot(NewP, P)
+ self.assertIs(Cls.__pointer_type__, NewP)
+ self.assertIs(P._type_, Cls)
+
+ def test_pointer_types_factory(self):
+ """Shouldn't leak"""
+ def factory():
+ class Cls(Structure):
+ _fields_ = (
+ ('a', c_int),
+ ('b', c_float),
+ )
+
+ return Cls
+
+ ws_typ = WeakSet()
+ ws_ptr = WeakSet()
+ for _ in range(10):
+ typ = factory()
+ ptr = POINTER(typ)
+
+ ws_typ.add(typ)
+ ws_ptr.add(ptr)
+
+ typ = None
+ ptr = None
+
+ gc.collect()
+
+ self.assertEqual(len(ws_typ), 0, ws_typ)
+ self.assertEqual(len(ws_ptr), 0, ws_ptr)
+
+
+class PointerTypeCacheTestCase(unittest.TestCase):
+ # dummy tests to check warnings and base behavior
+ def tearDown(self):
+ _pointer_type_cache_fallback.clear()
+
+ def test_deprecated_cache_with_not_ctypes_type(self):
+ class C:
+ pass
+
+ with self.assertWarns(DeprecationWarning):
+ P = POINTER("C")
+
+ with self.assertWarns(DeprecationWarning):
+ self.assertIs(_pointer_type_cache["C"], P)
+
+ with self.assertWarns(DeprecationWarning):
+ _pointer_type_cache[C] = P
+ self.assertIs(C.__pointer_type__, P)
+ with self.assertWarns(DeprecationWarning):
+ self.assertIs(_pointer_type_cache[C], P)
+
+ def test_deprecated_cache_with_ints(self):
+ with self.assertWarns(DeprecationWarning):
+ _pointer_type_cache[123] = 456
+
+ with self.assertWarns(DeprecationWarning):
+ self.assertEqual(_pointer_type_cache[123], 456)
+
+ def test_deprecated_cache_with_ctypes_type(self):
+ class C(Structure):
+ _fields_ = [("a", c_int),
+ ("b", c_int),
+ ("c", c_int)]
+
+ P1 = POINTER(C)
+ with self.assertWarns(DeprecationWarning):
+ P2 = POINTER("C")
+
+ with self.assertWarns(DeprecationWarning):
+ _pointer_type_cache[C] = P2
+
+ self.assertIs(C.__pointer_type__, P2)
+ self.assertIsNot(C.__pointer_type__, P1)
+
+ with self.assertWarns(DeprecationWarning):
+ self.assertIs(_pointer_type_cache[C], P2)
+
+ with self.assertWarns(DeprecationWarning):
+ self.assertIs(_pointer_type_cache.get(C), P2)
+
+ def test_get_not_registered(self):
+ with self.assertWarns(DeprecationWarning):
+ self.assertIsNone(_pointer_type_cache.get(str))
+
+ with self.assertWarns(DeprecationWarning):
+ self.assertIsNone(_pointer_type_cache.get(str, None))
+
def test_repeated_set_type(self):
# Regression test for gh-133290
class C(Structure):
self.assertEqual(ctx.exception.args[0], 'item 1 in _argtypes_ passes '
'a union by value, which is unsupported.')
+ def test_do_not_share_pointer_type_cache_via_stginfo_clone(self):
+ # This test case calls PyCStgInfo_clone()
+ # for the Mid and Vector class definitions
+ # and checks that pointer_type cache not shared
+ # between subclasses.
+ class Base(Structure):
+ _fields_ = [('y', c_double),
+ ('x', c_double)]
+ base_ptr = POINTER(Base)
+
+ class Mid(Base):
+ pass
+ Mid._fields_ = []
+ mid_ptr = POINTER(Mid)
+
+ class Vector(Mid):
+ pass
+
+ vector_ptr = POINTER(Vector)
+
+ self.assertIsNot(base_ptr, mid_ptr)
+ self.assertIsNot(base_ptr, vector_ptr)
+ self.assertIsNot(mid_ptr, vector_ptr)
+
if __name__ == '__main__':
unittest.main()
import sys
import unittest
from ctypes import (Structure, CDLL, POINTER, pythonapi,
- _pointer_type_cache,
c_ubyte, c_char_p, c_int)
from test.support import import_helper, thread_unsafe
"_PyImport_FrozenBootstrap example "
"in Doc/library/ctypes.rst may be out of date")
- del _pointer_type_cache[struct_frozen]
-
def test_undefined(self):
self.assertRaises(ValueError, c_int.in_dll, pythonapi,
"Undefined_Symbol")
import sys
import unittest
from ctypes import (CDLL, Structure, POINTER, pointer, sizeof, byref,
- _pointer_type_cache,
c_void_p, c_char, c_int, c_long)
from test import support
from test.support import import_helper
self.assertEqual(ret.top, top.value)
self.assertEqual(ret.bottom, bottom.value)
- # to not leak references, we must clean _pointer_type_cache
- del _pointer_type_cache[RECT]
+ self.assertIs(PointInRect.argtypes[0], ReturnRect.argtypes[2])
+ self.assertIs(PointInRect.argtypes[0], ReturnRect.argtypes[5])
if __name__ == '__main__':
--- /dev/null
+Move :func:`ctypes.POINTER` types cache from a global internal cache
+(``_pointer_type_cache``) to the :attr:`ctypes._CData.__pointer_type__`
+attribute of the corresponding :mod:`ctypes` types.
+This will stop the cache from growing without limits in some situations.
Py_VISIT(info->converters);
Py_VISIT(info->restype);
Py_VISIT(info->checker);
+ Py_VISIT(info->pointer_type);
Py_VISIT(info->module);
}
Py_VISIT(Py_TYPE(self));
Py_CLEAR(info->converters);
Py_CLEAR(info->restype);
Py_CLEAR(info->checker);
- Py_CLEAR(info->module);
+ Py_CLEAR(info->pointer_type);
+ Py_CLEAR(info->module); // decref the module last
+}
+
+void
+ctype_free_stginfo_members(StgInfo *info)
+{
+ assert(info);
+
+ PyMem_Free(info->ffi_type_pointer.elements);
+ info->ffi_type_pointer.elements = NULL;
+ PyMem_Free(info->format);
+ info->format = NULL;
+ PyMem_Free(info->shape);
+ info->shape = NULL;
+ ctype_clear_stginfo(info);
}
static int
"deallocating ctypes %R", self);
}
if (info) {
- PyMem_Free(info->ffi_type_pointer.elements);
- info->ffi_type_pointer.elements = NULL;
- PyMem_Free(info->format);
- info->format = NULL;
- PyMem_Free(info->shape);
- info->shape = NULL;
- ctype_clear_stginfo(info);
+ ctype_free_stginfo_members(info);
}
PyTypeObject *tp = Py_TYPE(self);
return PyLong_FromSsize_t(size);
}
+static PyObject *
+ctype_get_pointer_type(PyObject *self, void *Py_UNUSED(ignored))
+{
+ ctypes_state *st = get_module_state_by_def(Py_TYPE(self));
+ StgInfo *info;
+ if (PyStgInfo_FromType(st, self, &info) < 0) {
+ return NULL;
+ }
+ if (!info) {
+ PyErr_Format(PyExc_TypeError, "%R must have storage info", self);
+ return NULL;
+ }
+
+ if (info->pointer_type) {
+ return Py_NewRef(info->pointer_type);
+ }
+
+ PyErr_Format(PyExc_AttributeError,
+ "%R has no attribute '__pointer_type__'",
+ self);
+ return NULL;
+}
+
+static int
+ctype_set_pointer_type(PyObject *self, PyObject *tp, void *Py_UNUSED(ignored))
+{
+ ctypes_state *st = get_module_state_by_def(Py_TYPE(self));
+ StgInfo *info;
+ if (PyStgInfo_FromType(st, self, &info) < 0) {
+ return -1;
+ }
+ if (!info) {
+ PyErr_Format(PyExc_TypeError, "%R must have storage info", self);
+ return -1;
+ }
+
+ Py_XSETREF(info->pointer_type, Py_XNewRef(tp));
+ return 0;
+}
+
static PyObject *
CType_Type_repeat(PyObject *self, Py_ssize_t length);
{0},
};
+static PyGetSetDef ctype_getsets[] = {
+ { "__pointer_type__", ctype_get_pointer_type, ctype_set_pointer_type,
+ "pointer type", NULL },
+ { NULL, NULL }
+};
+
static PyType_Slot ctype_type_slots[] = {
{Py_tp_token, Py_TP_USE_SPEC},
{Py_tp_traverse, CType_Type_traverse},
{Py_tp_clear, CType_Type_clear},
{Py_tp_dealloc, CType_Type_dealloc},
{Py_tp_methods, ctype_methods},
+ {Py_tp_getset, ctype_getsets},
// Sequence protocol.
{Py_sq_repeat, CType_Type_repeat},
{0, NULL},
static int
-PyCPointerType_SetProto(ctypes_state *st, StgInfo *stginfo, PyObject *proto)
+PyCPointerType_SetProto(ctypes_state *st, PyObject *self, StgInfo *stginfo, PyObject *proto)
{
if (!proto || !PyType_Check(proto)) {
PyErr_SetString(PyExc_TypeError,
return -1;
}
if (!info) {
- PyErr_SetString(PyExc_TypeError,
- "_type_ must have storage info");
+ PyErr_Format(PyExc_TypeError, "%R must have storage info", proto);
return -1;
}
- Py_INCREF(proto);
- Py_XSETREF(stginfo->proto, proto);
+ Py_XSETREF(stginfo->proto, Py_NewRef(proto));
+ if (info->pointer_type == NULL) {
+ Py_XSETREF(info->pointer_type, Py_NewRef(self));
+ }
return 0;
}
}
if (proto) {
const char *current_format;
- if (PyCPointerType_SetProto(st, stginfo, proto) < 0) {
+ if (PyCPointerType_SetProto(st, self, stginfo, proto) < 0) {
Py_DECREF(proto);
return -1;
}
return NULL;
}
- if (PyCPointerType_SetProto(st, info, type) < 0) {
+ if (PyCPointerType_SetProto(st, (PyObject *)self, info, type) < 0) {
return NULL;
}
-
if (PyObject_SetAttr((PyObject *)self, &_Py_ID(_type_), type) < 0) {
return NULL;
}
} while (0)
ctypes_state *st = get_module_state(mod);
- MOD_ADD("_pointer_type_cache", Py_NewRef(st->_ctypes_ptrtype_cache));
#ifdef MS_WIN32
MOD_ADD("COMError", Py_NewRef(st->PyComError_Type));
return -1;
}
- st->_ctypes_ptrtype_cache = PyDict_New();
- if (st->_ctypes_ptrtype_cache == NULL) {
- return -1;
- }
-
st->PyExc_ArgError = PyErr_NewException("ctypes.ArgumentError", NULL, NULL);
if (!st->PyExc_ArgError) {
return -1;
static int
module_traverse(PyObject *module, visitproc visit, void *arg) {
ctypes_state *st = get_module_state(module);
- Py_VISIT(st->_ctypes_ptrtype_cache);
Py_VISIT(st->_unpickle);
Py_VISIT(st->array_cache);
Py_VISIT(st->error_object_name);
static int
module_clear(PyObject *module) {
ctypes_state *st = get_module_state(module);
- Py_CLEAR(st->_ctypes_ptrtype_cache);
Py_CLEAR(st->_unpickle);
Py_CLEAR(st->array_cache);
Py_CLEAR(st->error_object_name);
return NULL;
}
-/*[clinic input]
-_ctypes.POINTER as create_pointer_type
-
- type as cls: object
- A ctypes type.
- /
-
-Create and return a new ctypes pointer type.
-
-Pointer types are cached and reused internally,
-so calling this function repeatedly is cheap.
-[clinic start generated code]*/
-
-static PyObject *
-create_pointer_type(PyObject *module, PyObject *cls)
-/*[clinic end generated code: output=98c3547ab6f4f40b input=3b81cff5ff9b9d5b]*/
-{
- PyObject *result;
- PyTypeObject *typ;
- PyObject *key;
-
- assert(module);
- ctypes_state *st = get_module_state(module);
- if (PyDict_GetItemRef(st->_ctypes_ptrtype_cache, cls, &result) != 0) {
- // found or error
- return result;
- }
- // not found
- if (PyUnicode_CheckExact(cls)) {
- PyObject *name = PyUnicode_FromFormat("LP_%U", cls);
- result = PyObject_CallFunction((PyObject *)Py_TYPE(st->PyCPointer_Type),
- "N(O){}",
- name,
- st->PyCPointer_Type);
- if (result == NULL)
- return result;
- key = PyLong_FromVoidPtr(result);
- if (key == NULL) {
- Py_DECREF(result);
- return NULL;
- }
- } else if (PyType_Check(cls)) {
- typ = (PyTypeObject *)cls;
- PyObject *name = PyUnicode_FromFormat("LP_%s", typ->tp_name);
- result = PyObject_CallFunction((PyObject *)Py_TYPE(st->PyCPointer_Type),
- "N(O){sO}",
- name,
- st->PyCPointer_Type,
- "_type_", cls);
- if (result == NULL)
- return result;
- key = Py_NewRef(cls);
- } else {
- PyErr_SetString(PyExc_TypeError, "must be a ctypes type");
- return NULL;
- }
- if (PyDict_SetItem(st->_ctypes_ptrtype_cache, key, result) < 0) {
- Py_DECREF(result);
- Py_DECREF(key);
- return NULL;
- }
- Py_DECREF(key);
- return result;
-}
-
-/*[clinic input]
-_ctypes.pointer as create_pointer_inst
-
- obj as arg: object
- /
-
-Create a new pointer instance, pointing to 'obj'.
-
-The returned object is of the type POINTER(type(obj)). Note that if you
-just want to pass a pointer to an object to a foreign function call, you
-should use byref(obj) which is much faster.
-[clinic start generated code]*/
-
-static PyObject *
-create_pointer_inst(PyObject *module, PyObject *arg)
-/*[clinic end generated code: output=3b543bc9f0de2180 input=713685fdb4d9bc27]*/
-{
- PyObject *result;
- PyObject *typ;
-
- ctypes_state *st = get_module_state(module);
- if (PyDict_GetItemRef(st->_ctypes_ptrtype_cache, (PyObject *)Py_TYPE(arg), &typ) < 0) {
- return NULL;
- }
- if (typ == NULL) {
- typ = create_pointer_type(module, (PyObject *)Py_TYPE(arg));
- if (typ == NULL)
- return NULL;
- }
- result = PyObject_CallOneArg(typ, arg);
- Py_DECREF(typ);
- return result;
-}
-
static PyObject *
buffer_info(PyObject *self, PyObject *arg)
{
PyMethodDef _ctypes_module_methods[] = {
{"get_errno", get_errno, METH_NOARGS},
{"set_errno", set_errno, METH_VARARGS},
- CREATE_POINTER_TYPE_METHODDEF
- CREATE_POINTER_INST_METHODDEF
{"_unpickle", unpickle, METH_VARARGS },
{"buffer_info", buffer_info, METH_O, "Return buffer interface information"},
_CTYPES_RESIZE_METHODDEF
exit:
return return_value;
}
-
-PyDoc_STRVAR(create_pointer_type__doc__,
-"POINTER($module, type, /)\n"
-"--\n"
-"\n"
-"Create and return a new ctypes pointer type.\n"
-"\n"
-" type\n"
-" A ctypes type.\n"
-"\n"
-"Pointer types are cached and reused internally,\n"
-"so calling this function repeatedly is cheap.");
-
-#define CREATE_POINTER_TYPE_METHODDEF \
- {"POINTER", (PyCFunction)create_pointer_type, METH_O, create_pointer_type__doc__},
-
-PyDoc_STRVAR(create_pointer_inst__doc__,
-"pointer($module, obj, /)\n"
-"--\n"
-"\n"
-"Create a new pointer instance, pointing to \'obj\'.\n"
-"\n"
-"The returned object is of the type POINTER(type(obj)). Note that if you\n"
-"just want to pass a pointer to an object to a foreign function call, you\n"
-"should use byref(obj) which is much faster.");
-
-#define CREATE_POINTER_INST_METHODDEF \
- {"pointer", (PyCFunction)create_pointer_inst, METH_O, create_pointer_inst__doc__},
-/*[clinic end generated code: output=46a3841cbe5ddc96 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=23c74aced603977d input=a9049054013a1b77]*/
#ifdef MS_WIN32
PyTypeObject *PyComError_Type;
#endif
- /* This dict maps ctypes types to POINTER types */
- PyObject *_ctypes_ptrtype_cache;
/* a callable object used for unpickling:
strong reference to _ctypes._unpickle() function */
PyObject *_unpickle;
PyObject *converters; /* tuple([t.from_param for t in argtypes]) */
PyObject *restype; /* CDataObject or NULL */
PyObject *checker;
+ PyObject *pointer_type; /* __pointer_type__ attribute;
+ arbitrary object or NULL */
PyObject *module;
int flags; /* calling convention and such */
#ifdef Py_GIL_DISABLED
extern int PyCStgInfo_clone(StgInfo *dst_info, StgInfo *src_info);
extern void ctype_clear_stginfo(StgInfo *info);
+extern void ctype_free_stginfo_members(StgInfo *info);
typedef int(* PPROC)(void);
if (!module) {
return NULL;
}
+ info->pointer_type = NULL;
info->module = Py_NewRef(module);
info->initialized = 1;
{
Py_ssize_t size;
- ctype_clear_stginfo(dst_info);
- PyMem_Free(dst_info->ffi_type_pointer.elements);
- PyMem_Free(dst_info->format);
- dst_info->format = NULL;
- PyMem_Free(dst_info->shape);
- dst_info->shape = NULL;
- dst_info->ffi_type_pointer.elements = NULL;
+ ctype_free_stginfo_members(dst_info);
memcpy(dst_info, src_info, sizeof(StgInfo));
#ifdef Py_GIL_DISABLED
Py_XINCREF(dst_info->restype);
Py_XINCREF(dst_info->checker);
Py_XINCREF(dst_info->module);
+ dst_info->pointer_type = NULL; // the cache cannot be shared
if (src_info->format) {
dst_info->format = PyMem_Malloc(strlen(src_info->format) + 1);