]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-100926: Move ctype's pointers cache from _pointer_type_cache to StgInfo (GH-131282)
authorSergey Miryanov <sergey.miryanov@gmail.com>
Fri, 2 May 2025 17:06:37 +0000 (10:06 -0700)
committerGitHub <noreply@github.com>
Fri, 2 May 2025 17:06:37 +0000 (19:06 +0200)
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>
17 files changed:
Doc/library/ctypes.rst
Doc/whatsnew/3.14.rst
Lib/ctypes/__init__.py
Lib/test/test_ctypes/test_byteswap.py
Lib/test/test_ctypes/test_c_simple_type_meta.py
Lib/test/test_ctypes/test_incomplete.py
Lib/test/test_ctypes/test_keeprefs.py
Lib/test/test_ctypes/test_pointers.py
Lib/test/test_ctypes/test_structures.py
Lib/test/test_ctypes/test_values.py
Lib/test/test_ctypes/test_win32.py
Misc/NEWS.d/next/Library/2025-03-17-23-07-57.gh-issue-100926.B8gcbz.rst [new file with mode: 0644]
Modules/_ctypes/_ctypes.c
Modules/_ctypes/callproc.c
Modules/_ctypes/clinic/callproc.c.h
Modules/_ctypes/ctypes.h
Modules/_ctypes/stgdict.c

index 0bf88b0e3b66c6953a819488f4260deed164be99..2825590400c70b336248684627311103ea8a232e 100644 (file)
@@ -2172,10 +2172,20 @@ Utility functions
 
 .. 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, /)
 
@@ -2340,6 +2350,16 @@ Data types
       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_
index 62dd0551483e976011ac70ef69c8aead20f5bb02..8e8578f6a99bf7389f66f5e3b3ae0234f21b6042 100644 (file)
@@ -807,11 +807,16 @@ ctypes
   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
 --------
 
@@ -1679,6 +1684,13 @@ Deprecated
   :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.
index b0e74f679ed18a0e08da29825d116a1573f6dfc0..823a3692fd1bbf9cdc0b60f025f3707132d59c3f 100644 (file)
@@ -266,7 +266,72 @@ _check_size(c_void_p)
 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"
@@ -277,7 +342,7 @@ class c_wchar(_SimpleCData):
     _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()
@@ -285,7 +350,6 @@ def _reset_cache():
     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
@@ -319,13 +383,7 @@ def create_unicode_buffer(init, size=None):
 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
index 072c60d53dd8cb6ec82785956e5b9111141d9003..9f9904282e451a0e18d05c08aa422402a562fcd0 100644 (file)
@@ -232,7 +232,6 @@ class Test(unittest.TestCase, StructCheckMixin):
                 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)
 
@@ -371,7 +370,6 @@ class Test(unittest.TestCase, StructCheckMixin):
                 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)
 
index 2328611856a1a05a8832f5bac257f583df0abd3b..fd261acf49741f639396b8d7fdd73baddcca4bf9 100644 (file)
@@ -1,16 +1,15 @@
 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.
@@ -36,7 +35,7 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
                 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):
@@ -45,20 +44,36 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
         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.
@@ -69,7 +84,7 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
                 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):
@@ -78,15 +93,27 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
         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):
@@ -103,7 +130,7 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
                 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
@@ -111,20 +138,37 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
         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):
@@ -135,7 +179,7 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
                 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
@@ -146,12 +190,21 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
         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
@@ -168,3 +221,164 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
         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))
index 9f859793d88a22942923598223615c5041f790e9..fefdfe9102e6680b3cf198c6951cc618b8b1c9f5 100644 (file)
@@ -3,15 +3,20 @@ import unittest
 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)]
@@ -20,6 +25,8 @@ class TestSetPointerType(unittest.TestCase):
             warnings.simplefilter('ignore', DeprecationWarning)
             ctypes.SetPointerType(lpcell, cell)
 
+        self.assertIs(POINTER(cell), lpcell)
+
         c1 = cell()
         c1.name = b"foo"
         c2 = cell()
@@ -37,7 +44,8 @@ class TestSetPointerType(unittest.TestCase):
         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)]
@@ -45,6 +53,7 @@ class TestSetPointerType(unittest.TestCase):
         with self.assertWarns(DeprecationWarning):
             ctypes.SetPointerType(lpcell, cell)
 
+        self.assertIs(POINTER(cell), lpcell)
 
 if __name__ == '__main__':
     unittest.main()
index 23b03b64b4a7161c17e91f58f76f6e5e9101d1a9..5602460d5ff8e0854938f141d523af0615798f0c 100644 (file)
@@ -1,6 +1,5 @@
 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):
@@ -115,10 +114,6 @@ class PointerToStructure(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()
index ed4541335dfca43e63c986a59710910bc2fc80b8..a8d243a45de0f404c4360f114c819bad381ca4c6 100644 (file)
@@ -1,15 +1,18 @@
 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)
@@ -22,6 +25,9 @@ python_types = [int, int, int, int, int, int,
 
 
 class PointersTestCase(unittest.TestCase):
+    def tearDown(self):
+        _pointer_type_cache_fallback.clear()
+
     def test_inheritance_hierarchy(self):
         self.assertEqual(_Pointer.mro(), [_Pointer, _CData, object])
 
@@ -127,6 +133,14 @@ class PointersTestCase(unittest.TestCase):
         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),
@@ -141,8 +155,6 @@ class PointersTestCase(unittest.TestCase):
 
         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
@@ -175,6 +187,7 @@ class PointersTestCase(unittest.TestCase):
         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:
@@ -193,6 +206,30 @@ class PointersTestCase(unittest.TestCase):
         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)
@@ -210,20 +247,220 @@ class PointersTestCase(unittest.TestCase):
         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):
index bd7aba6376d167ac6284f6452bb782053fbee5e8..221319642e8f3b77791e0cf0f4e511611b238e99 100644 (file)
@@ -685,6 +685,30 @@ class StructureTestCase(unittest.TestCase, StructCheckMixin):
         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()
index 1e20979760627515f70521126024d05b7e421b85..8d1ee25ace5479b97ef90b6cce6d22ed0726c79e 100644 (file)
@@ -7,7 +7,6 @@ import importlib.util
 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
 
@@ -98,8 +97,6 @@ class PythonValuesTestCase(unittest.TestCase):
                          "_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")
index 54b47dc28fbc735be12bde618f74d53d62217dc5..7d5133221906bbdfd8d5867008e074a350bbc79b 100644 (file)
@@ -5,7 +5,6 @@ import errno
 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
@@ -145,8 +144,8 @@ class Structures(unittest.TestCase):
             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__':
diff --git a/Misc/NEWS.d/next/Library/2025-03-17-23-07-57.gh-issue-100926.B8gcbz.rst b/Misc/NEWS.d/next/Library/2025-03-17-23-07-57.gh-issue-100926.B8gcbz.rst
new file mode 100644 (file)
index 0000000..6a71415
--- /dev/null
@@ -0,0 +1,4 @@
+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.
index 3605ca9007c9f82ce8f0ca97e5f525f835e70e7f..1bb65e0a64920d0c9e477b1d4a6c1ddbedc644f3 100644 (file)
@@ -478,6 +478,7 @@ CType_Type_traverse(PyObject *self, visitproc visit, void *arg)
         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));
@@ -493,7 +494,22 @@ ctype_clear_stginfo(StgInfo *info)
     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
@@ -519,13 +535,7 @@ CType_Type_dealloc(PyObject *self)
                                "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);
@@ -566,6 +576,46 @@ _ctypes_CType_Type___sizeof___impl(PyObject *self, PyTypeObject *cls)
     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);
 
@@ -575,12 +625,19 @@ static PyMethodDef ctype_methods[] = {
     {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},
@@ -1181,7 +1238,7 @@ class _ctypes.PyCPointerType "PyObject *" "clinic_state()->PyCPointerType_Type"
 
 
 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,
@@ -1193,12 +1250,13 @@ PyCPointerType_SetProto(ctypes_state *st, StgInfo *stginfo, PyObject *proto)
         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;
 }
 
@@ -1251,7 +1309,7 @@ PyCPointerType_init(PyObject *self, PyObject *args, PyObject *kwds)
     }
     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;
         }
@@ -1309,10 +1367,9 @@ PyCPointerType_set_type_impl(PyTypeObject *self, PyTypeObject *cls,
         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;
     }
@@ -6206,7 +6263,6 @@ _ctypes_add_objects(PyObject *mod)
     } 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));
@@ -6269,11 +6325,6 @@ _ctypes_mod_exec(PyObject *mod)
         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;
@@ -6312,7 +6363,6 @@ _ctypes_mod_exec(PyObject *mod)
 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);
@@ -6347,7 +6397,6 @@ module_traverse(PyObject *module, visitproc visit, void *arg) {
 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);
index 83471aa3a42ad21f345bc4a5e80580413389396b..fc7265cb63ed9e5e20936880f3b9ca6149bc54b1 100644 (file)
@@ -1972,105 +1972,6 @@ error:
     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)
 {
@@ -2105,8 +2006,6 @@ 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
index 8a5c8f6427b7f0d9af4f9b3f2060fd8de9821b61..e0cfcc6f38def7ef3a344ef21f4f7b73787c17a7 100644 (file)
@@ -142,32 +142,4 @@ _ctypes_resize(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
 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]*/
index 3b6d390728a07f580128d0e3c8221a31cbefef32..9aceeceb88a49fad3a106196b877c0dd827fc7c7 100644 (file)
@@ -83,8 +83,6 @@ typedef struct {
 #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;
@@ -390,6 +388,8 @@ typedef struct {
     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
@@ -452,6 +452,7 @@ stginfo_set_dict_final(StgInfo *info)
 
 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);
 
@@ -640,6 +641,7 @@ PyStgInfo_Init(ctypes_state *state, PyTypeObject *type)
     if (!module) {
         return NULL;
     }
+    info->pointer_type = NULL;
     info->module = Py_NewRef(module);
 
     info->initialized = 1;
index c4c4ec2cc5266a3efdb8062780f04c08fe40ae1f..f208d2956e429ec3160d31da4ba8cbe6973d90a9 100644 (file)
@@ -25,13 +25,7 @@ PyCStgInfo_clone(StgInfo *dst_info, StgInfo *src_info)
 {
     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
@@ -45,6 +39,7 @@ PyCStgInfo_clone(StgInfo *dst_info, StgInfo *src_info)
     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);