Skip to content

Commit 1ba1107

Browse files
authored
Align ctypes call ownership and C error state with Python 3.15 (#9218)
* Align ctypes call ownership and C error state with Python 3.15 Keep native raised errors separate from handled exceptions and preserve them across teardown callbacks. Import the related canonical ctypes call and type contracts. Assisted-by: OpenAI Codex: model version unavailable * Enumerate ctypes shared libraries natively on Linux and Android Copy loader-owned names before Python filesystem decoding and update the related canonical ctypes utility and discovery tests from CPython 3.15. Preserve existing platform paths and dllist test markers. Assisted-by: OpenAI Codex: model version unavailable * Enable canonical dllist system enumeration coverage on Linux Regenerate the pinned test through update_lib patches with reviewed marker data. Preserve other platform conditions and the unavailable extension-fixture guard. Assisted-by: OpenAI Codex: model version unavailable * Match Python float and long-double C argument representations Use the existing Python float formatter for float and double C argument representations. Keep long-double arguments on the canonical generic-address representation path. Preserve the unchanged canonical parameter repr assertions. Assisted-by: OpenAI Codex: model version unavailable
1 parent 1ffba21 commit 1ba1107

26 files changed

Lines changed: 570 additions & 233 deletions

‎Lib/ctypes/__init__.py‎

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,20 @@
22

33
import os as _os
44
import sys as _sys
5-
import sysconfig as _sysconfig
65
import types as _types
76

8-
__version__ = "1.1.0"
7+
lazy import sysconfig as _sysconfig
98

109
from _ctypes import Union, Structure, Array
1110
from _ctypes import _Pointer
1211
from _ctypes import CFuncPtr as _CFuncPtr
13-
from _ctypes import __version__ as _ctypes_version
1412
from _ctypes import RTLD_LOCAL, RTLD_GLOBAL
1513
from _ctypes import ArgumentError
1614
from _ctypes import SIZEOF_TIME_T
1715
from _ctypes import CField
1816

1917
from struct import calcsize as _calcsize
2018

21-
if __version__ != _ctypes_version:
22-
raise Exception("Version number mismatch", __version__, _ctypes_version)
23-
2419
if _os.name == "nt":
2520
from _ctypes import COMError, CopyComPointer, FormatError
2621

@@ -211,13 +206,13 @@ class c_longdouble(_SimpleCData):
211206

212207
try:
213208
class c_double_complex(_SimpleCData):
214-
_type_ = "D"
209+
_type_ = "Zd"
215210
_check_size(c_double_complex)
216211
class c_float_complex(_SimpleCData):
217-
_type_ = "F"
212+
_type_ = "Zf"
218213
_check_size(c_float_complex)
219214
class c_longdouble_complex(_SimpleCData):
220-
_type_ = "G"
215+
_type_ = "Zg"
221216
except AttributeError:
222217
pass
223218

@@ -381,12 +376,6 @@ def create_unicode_buffer(init, size=None):
381376
return buf
382377
raise TypeError(init)
383378

384-
385-
def SetPointerType(pointer, cls):
386-
import warnings
387-
warnings._deprecated("ctypes.SetPointerType", remove=(3, 15))
388-
pointer.set_type(cls)
389-
390379
def ARRAY(typ, len):
391380
return typ * len
392381

@@ -681,3 +670,12 @@ def DllCanUnloadNow():
681670
raise SystemError(f"Unexpected sizeof(time_t): {SIZEOF_TIME_T=}")
682671

683672
_reset_cache()
673+
674+
675+
def __getattr__(name):
676+
if name == "__version__":
677+
from warnings import _deprecated
678+
679+
_deprecated("__version__", remove=(3, 20))
680+
return "1.1.0" # Do not change
681+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

‎Lib/ctypes/_layout.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
"""
66

77
import sys
8-
import warnings
98

109
from _ctypes import CField, buffer_info
1110
import ctypes
1211

12+
lazy import warnings
13+
1314
def round_down(n, multiple):
1415
assert n >= 0
1516
assert multiple > 0

‎Lib/ctypes/util.py‎

Lines changed: 10 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import os
2-
import shutil
3-
import subprocess
42
import sys
53

4+
lazy import shutil
5+
lazy import subprocess
6+
67
# find_library(name) returns the pathname of a library, or None.
78
if os.name == "nt":
89

@@ -421,8 +422,7 @@ def _findLib_ld(name):
421422
result = None
422423
try:
423424
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
424-
stderr=subprocess.PIPE,
425-
universal_newlines=True)
425+
stderr=subprocess.PIPE)
426426
out, _ = p.communicate()
427427
res = re.findall(expr, os.fsdecode(out))
428428
for file in res:
@@ -442,53 +442,12 @@ def find_library(name):
442442
_get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name))
443443

444444

445-
# Listing loaded libraries on other systems will try to use
446-
# functions common to Linux and a few other Unix-like systems.
447-
# See the following for several platforms' documentation of the same API:
448-
# https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html
449-
# https://man.freebsd.org/cgi/man.cgi?query=dl_iterate_phdr
450-
# https://man.openbsd.org/dl_iterate_phdr
451-
# https://docs.oracle.com/cd/E88353_01/html/E37843/dl-iterate-phdr-3c.html
452-
if (os.name == "posix" and
453-
sys.platform not in {"darwin", "ios", "tvos", "watchos"}):
454-
import ctypes
455-
if hasattr((_libc := ctypes.CDLL(None)), "dl_iterate_phdr"):
456-
457-
class _dl_phdr_info(ctypes.Structure):
458-
_fields_ = [
459-
("dlpi_addr", ctypes.c_void_p),
460-
("dlpi_name", ctypes.c_char_p),
461-
("dlpi_phdr", ctypes.c_void_p),
462-
("dlpi_phnum", ctypes.c_ushort),
463-
]
464-
465-
_dl_phdr_callback = ctypes.CFUNCTYPE(
466-
ctypes.c_int,
467-
ctypes.POINTER(_dl_phdr_info),
468-
ctypes.c_size_t,
469-
ctypes.POINTER(ctypes.py_object),
470-
)
471-
472-
@_dl_phdr_callback
473-
def _info_callback(info, _size, data):
474-
libraries = data.contents.value
475-
name = os.fsdecode(info.contents.dlpi_name)
476-
libraries.append(name)
477-
return 0
478-
479-
_dl_iterate_phdr = _libc["dl_iterate_phdr"]
480-
_dl_iterate_phdr.argtypes = [
481-
_dl_phdr_callback,
482-
ctypes.POINTER(ctypes.py_object),
483-
]
484-
_dl_iterate_phdr.restype = ctypes.c_int
485-
486-
def dllist():
487-
"""Return a list of loaded shared libraries in the current process."""
488-
libraries = []
489-
_dl_iterate_phdr(_info_callback,
490-
ctypes.byref(ctypes.py_object(libraries)))
491-
return libraries
445+
# On platforms which provide dl_iterate_phdr(), dllist() is implemented
446+
# in _ctypes.
447+
try:
448+
from _ctypes import dllist
449+
except ImportError:
450+
pass
492451

493452
################################################################
494453
# test code

‎Lib/test/test_ctypes/__init__.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,27 @@
11
import os
2+
import unittest
23
from test import support
34
from test.support import import_helper
45

56

67
# skip tests if the _ctypes extension was not built
78
import_helper.import_module('ctypes')
89

10+
11+
class TestModule(unittest.TestCase):
12+
def test_deprecated__version__(self):
13+
import ctypes
14+
import _ctypes
15+
16+
for mod in (ctypes, _ctypes):
17+
with self.subTest(mod=mod):
18+
with self.assertWarnsRegex(
19+
DeprecationWarning,
20+
"'__version__' is deprecated and slated for removal in Python 3.20",
21+
) as cm:
22+
getattr(mod, "__version__")
23+
self.assertEqual(cm.filename, __file__)
24+
25+
926
def load_tests(*args):
1027
return support.load_package_tests(os.path.dirname(__file__), *args)

‎Lib/test/test_ctypes/_support.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import ctypes
44
from _ctypes import Structure, Union, _Pointer, Array, _SimpleCData, CFuncPtr
55
import sys
6-
from test import support
76

87

98
_CData = Structure.__base__

‎Lib/test/test_ctypes/test_as_parameter.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
c_short, c_int, c_long, c_longlong,
66
c_byte, c_wchar, c_float, c_double,
77
ArgumentError)
8-
from test.support import import_helper, skip_if_sanitizer
8+
from test.support import (import_helper, skip_if_sanitizer,
9+
skip_emscripten_stack_overflow, skip_if_huge_c_stack)
910
_ctypes_test = import_helper.import_module("_ctypes_test")
1011

1112

@@ -193,6 +194,8 @@ class S8I(Structure):
193194
(9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9))
194195

195196
@skip_if_sanitizer('requires deep stack', thread=True)
197+
@skip_if_huge_c_stack()
198+
@skip_emscripten_stack_overflow()
196199
def test_recursive_as_param(self):
197200
class A:
198201
pass

‎Lib/test/test_ctypes/test_c_simple_type_meta.py‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,14 +213,12 @@ def test_bad_type_message(self):
213213
class F(metaclass=PyCSimpleType):
214214
_type_ = "\0"
215215
message = str(cm.exception)
216-
expected_type_chars = list('cbBhHiIlLdDFGfuzZqQPXOv?g')
217-
if not hasattr(ctypes, 'c_float_complex'):
218-
expected_type_chars.remove('F')
219-
expected_type_chars.remove('D')
220-
expected_type_chars.remove('G')
216+
expected_type_chars = list('cbBhHiIlLdfuzZqQPXOv?g')
221217
if not MS_WINDOWS:
222218
expected_type_chars.remove('X')
223219
self.assertIn("'" + ''.join(expected_type_chars) + "'", message)
220+
if hasattr(ctypes, 'c_float_complex'):
221+
self.assertIn("'Zf', 'Zd', 'Zg'", message)
224222

225223
def test_creating_pointer_in_dunder_init_3(self):
226224
"""Check if interfcase subclasses properly creates according internal

‎Lib/test/test_ctypes/test_callbacks.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,21 @@ def func():
328328
f"of ctypes callback function {func!r}")
329329
self.assertIsNone(cm.unraisable.object)
330330

331+
def test_narrow_int_return_widened(self):
332+
# gh-156933: Narrow integers were not widened on s390x
333+
CALLBACK = CFUNCTYPE(c_int)
334+
335+
dll = CDLL(_ctypes_test.__file__)
336+
_testfunc_callback_int_to_longlong = dll._testfunc_callback_int_to_longlong
337+
_testfunc_callback_int_to_longlong.argtypes = [CALLBACK]
338+
_testfunc_callback_int_to_longlong.restype = c_longlong
339+
340+
@CALLBACK
341+
def cb():
342+
return -1
343+
344+
self.assertEqual(_testfunc_callback_int_to_longlong(cb), -1)
345+
331346

332347
if __name__ == '__main__':
333348
unittest.main()

‎Lib/test/test_ctypes/test_dllist.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
)
2727
class ListSharedLibraries(unittest.TestCase):
2828

29-
@unittest.skipIf(not APPLE, "TODO: RUSTPYTHON")
29+
@unittest.skipIf(not APPLE and sys.platform != "linux", "TODO: RUSTPYTHON")
3030
def test_lists_system(self):
3131
dlls = ctypes.util.dllist()
3232

‎Lib/test/test_ctypes/test_find.py‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os.path
2+
import subprocess
23
import sys
34
import test.support
45
import unittest
@@ -78,9 +79,24 @@ def test_shell_injection(self):
7879
@unittest.skipUnless(sys.platform.startswith('linux'),
7980
'Test only valid for Linux')
8081
class FindLibraryLinux(unittest.TestCase):
82+
@classmethod
83+
def setUpClass(cls):
84+
try:
85+
p = subprocess.run(['ld', '--version'],
86+
stdout=subprocess.PIPE,
87+
stderr=subprocess.DEVNULL,
88+
text=True)
89+
except OSError:
90+
pass
91+
else:
92+
if p.stdout.startswith('mold '):
93+
# The mold linker is known to be incompatible with
94+
# the soft-deprecated ctypes.util.find_library
95+
# (which uses `ld -t`).
96+
raise unittest.SkipTest('Fails when ld is mold')
97+
8198
@thread_unsafe('uses setenv')
8299
def test_find_on_libpath(self):
83-
import subprocess
84100
import tempfile
85101

86102
try:

0 commit comments

Comments
 (0)