Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 13 additions & 15 deletions Lib/ctypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,20 @@

import os as _os
import sys as _sys
import sysconfig as _sysconfig
import types as _types

__version__ = "1.1.0"
lazy import sysconfig as _sysconfig

from _ctypes import Union, Structure, Array
from _ctypes import _Pointer
from _ctypes import CFuncPtr as _CFuncPtr
from _ctypes import __version__ as _ctypes_version
from _ctypes import RTLD_LOCAL, RTLD_GLOBAL
from _ctypes import ArgumentError
from _ctypes import SIZEOF_TIME_T
from _ctypes import CField

from struct import calcsize as _calcsize

if __version__ != _ctypes_version:
raise Exception("Version number mismatch", __version__, _ctypes_version)

if _os.name == "nt":
from _ctypes import COMError, CopyComPointer, FormatError

Expand Down Expand Up @@ -211,13 +206,13 @@ class c_longdouble(_SimpleCData):

try:
class c_double_complex(_SimpleCData):
_type_ = "D"
_type_ = "Zd"
_check_size(c_double_complex)
class c_float_complex(_SimpleCData):
_type_ = "F"
_type_ = "Zf"
_check_size(c_float_complex)
class c_longdouble_complex(_SimpleCData):
_type_ = "G"
_type_ = "Zg"
except AttributeError:
pass

Expand Down Expand Up @@ -381,12 +376,6 @@ def create_unicode_buffer(init, size=None):
return buf
raise TypeError(init)


def SetPointerType(pointer, cls):
import warnings
warnings._deprecated("ctypes.SetPointerType", remove=(3, 15))
pointer.set_type(cls)

def ARRAY(typ, len):
return typ * len

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

_reset_cache()


def __getattr__(name):
if name == "__version__":
from warnings import _deprecated

_deprecated("__version__", remove=(3, 20))
return "1.1.0" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
3 changes: 2 additions & 1 deletion Lib/ctypes/_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
"""

import sys
import warnings

from _ctypes import CField, buffer_info
import ctypes

lazy import warnings

def round_down(n, multiple):
assert n >= 0
assert multiple > 0
Expand Down
61 changes: 10 additions & 51 deletions Lib/ctypes/util.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import os
import shutil
import subprocess
import sys

lazy import shutil
lazy import subprocess

# find_library(name) returns the pathname of a library, or None.
if os.name == "nt":

Expand Down Expand Up @@ -421,8 +422,7 @@ def _findLib_ld(name):
result = None
try:
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
stderr=subprocess.PIPE)
out, _ = p.communicate()
res = re.findall(expr, os.fsdecode(out))
for file in res:
Expand All @@ -442,53 +442,12 @@ def find_library(name):
_get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name))


# Listing loaded libraries on other systems will try to use
# functions common to Linux and a few other Unix-like systems.
# See the following for several platforms' documentation of the same API:
# https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html
# https://man.freebsd.org/cgi/man.cgi?query=dl_iterate_phdr
# https://man.openbsd.org/dl_iterate_phdr
# https://docs.oracle.com/cd/E88353_01/html/E37843/dl-iterate-phdr-3c.html
if (os.name == "posix" and
sys.platform not in {"darwin", "ios", "tvos", "watchos"}):
import ctypes
if hasattr((_libc := ctypes.CDLL(None)), "dl_iterate_phdr"):

class _dl_phdr_info(ctypes.Structure):
_fields_ = [
("dlpi_addr", ctypes.c_void_p),
("dlpi_name", ctypes.c_char_p),
("dlpi_phdr", ctypes.c_void_p),
("dlpi_phnum", ctypes.c_ushort),
]

_dl_phdr_callback = ctypes.CFUNCTYPE(
ctypes.c_int,
ctypes.POINTER(_dl_phdr_info),
ctypes.c_size_t,
ctypes.POINTER(ctypes.py_object),
)

@_dl_phdr_callback
def _info_callback(info, _size, data):
libraries = data.contents.value
name = os.fsdecode(info.contents.dlpi_name)
libraries.append(name)
return 0

_dl_iterate_phdr = _libc["dl_iterate_phdr"]
_dl_iterate_phdr.argtypes = [
_dl_phdr_callback,
ctypes.POINTER(ctypes.py_object),
]
_dl_iterate_phdr.restype = ctypes.c_int

def dllist():
"""Return a list of loaded shared libraries in the current process."""
libraries = []
_dl_iterate_phdr(_info_callback,
ctypes.byref(ctypes.py_object(libraries)))
return libraries
# On platforms which provide dl_iterate_phdr(), dllist() is implemented
# in _ctypes.
try:
from _ctypes import dllist
except ImportError:
pass

################################################################
# test code
Expand Down
17 changes: 17 additions & 0 deletions Lib/test/test_ctypes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
import os
import unittest
from test import support
from test.support import import_helper


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


class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
import ctypes
import _ctypes

for mod in (ctypes, _ctypes):
with self.subTest(mod=mod):
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(mod, "__version__")
self.assertEqual(cm.filename, __file__)


def load_tests(*args):
return support.load_package_tests(os.path.dirname(__file__), *args)
1 change: 0 additions & 1 deletion Lib/test/test_ctypes/_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import ctypes
from _ctypes import Structure, Union, _Pointer, Array, _SimpleCData, CFuncPtr
import sys
from test import support


_CData = Structure.__base__
Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_ctypes/test_as_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
c_short, c_int, c_long, c_longlong,
c_byte, c_wchar, c_float, c_double,
ArgumentError)
from test.support import import_helper, skip_if_sanitizer
from test.support import (import_helper, skip_if_sanitizer,
skip_emscripten_stack_overflow, skip_if_huge_c_stack)
_ctypes_test = import_helper.import_module("_ctypes_test")


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

@skip_if_sanitizer('requires deep stack', thread=True)
@skip_if_huge_c_stack()
@skip_emscripten_stack_overflow()
def test_recursive_as_param(self):
class A:
pass
Expand Down
8 changes: 3 additions & 5 deletions Lib/test/test_ctypes/test_c_simple_type_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,14 +213,12 @@ def test_bad_type_message(self):
class F(metaclass=PyCSimpleType):
_type_ = "\0"
message = str(cm.exception)
expected_type_chars = list('cbBhHiIlLdDFGfuzZqQPXOv?g')
if not hasattr(ctypes, 'c_float_complex'):
expected_type_chars.remove('F')
expected_type_chars.remove('D')
expected_type_chars.remove('G')
expected_type_chars = list('cbBhHiIlLdfuzZqQPXOv?g')
if not MS_WINDOWS:
expected_type_chars.remove('X')
self.assertIn("'" + ''.join(expected_type_chars) + "'", message)
if hasattr(ctypes, 'c_float_complex'):
self.assertIn("'Zf', 'Zd', 'Zg'", message)

def test_creating_pointer_in_dunder_init_3(self):
"""Check if interfcase subclasses properly creates according internal
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_ctypes/test_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,21 @@ def func():
f"of ctypes callback function {func!r}")
self.assertIsNone(cm.unraisable.object)

def test_narrow_int_return_widened(self):
# gh-156933: Narrow integers were not widened on s390x
CALLBACK = CFUNCTYPE(c_int)

dll = CDLL(_ctypes_test.__file__)
_testfunc_callback_int_to_longlong = dll._testfunc_callback_int_to_longlong
_testfunc_callback_int_to_longlong.argtypes = [CALLBACK]
_testfunc_callback_int_to_longlong.restype = c_longlong

@CALLBACK
def cb():
return -1

self.assertEqual(_testfunc_callback_int_to_longlong(cb), -1)


if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion Lib/test/test_ctypes/test_dllist.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
)
class ListSharedLibraries(unittest.TestCase):

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

Expand Down
18 changes: 17 additions & 1 deletion Lib/test/test_ctypes/test_find.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os.path
import subprocess
import sys
import test.support
import unittest
Expand Down Expand Up @@ -78,9 +79,24 @@ def test_shell_injection(self):
@unittest.skipUnless(sys.platform.startswith('linux'),
'Test only valid for Linux')
class FindLibraryLinux(unittest.TestCase):
@classmethod
def setUpClass(cls):
try:
p = subprocess.run(['ld', '--version'],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True)
except OSError:
pass
else:
if p.stdout.startswith('mold '):
# The mold linker is known to be incompatible with
# the soft-deprecated ctypes.util.find_library
# (which uses `ld -t`).
raise unittest.SkipTest('Fails when ld is mold')

@thread_unsafe('uses setenv')
def test_find_on_libpath(self):
import subprocess
import tempfile

try:
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_ctypes/test_generated_structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"""

import unittest
from test.support import import_helper, verbose
from test.support import import_helper
import re
from dataclasses import dataclass
from functools import cached_property
Expand Down
33 changes: 26 additions & 7 deletions Lib/test/test_ctypes/test_incomplete.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import ctypes
import unittest
import warnings
from ctypes import Structure, POINTER, pointer, c_char_p
from ctypes import Structure, POINTER, pointer, c_char_p, c_int

# String-based "incomplete pointers" were implemented in ctypes 0.6.3 (2003, when
# ctypes was an external project). They made obsolete by the current
Expand All @@ -21,9 +20,7 @@ class cell(Structure):
_fields_ = [("name", c_char_p),
("next", lpcell)]

with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
ctypes.SetPointerType(lpcell, cell)
lpcell.set_type(cell)

self.assertIs(POINTER(cell), lpcell)

Expand All @@ -50,10 +47,32 @@ class cell(Structure):
_fields_ = [("name", c_char_p),
("next", lpcell)]

lpcell.set_type(cell)
self.assertIs(POINTER(cell), lpcell)

def test_set_type_updates_format(self):
# gh-142966: set_type should update StgInfo.format
# to match the element type's format
with self.assertWarns(DeprecationWarning):
ctypes.SetPointerType(lpcell, cell)
lp = POINTER("node")

class node(Structure):
_fields_ = [("value", c_int)]

# Get the expected format before set_type
node_format = memoryview(node()).format
expected_format = "&" + node_format

lp.set_type(node)

# Create instance to check format via memoryview
n = node(42)
p = lp(n)
actual_format = memoryview(p).format

# After set_type, the pointer's format should be "&<element_format>"
self.assertEqual(actual_format, expected_format)

self.assertIs(POINTER(cell), lpcell)

if __name__ == '__main__':
unittest.main()
13 changes: 11 additions & 2 deletions Lib/test/test_ctypes/test_numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,24 @@ def test_floats(self):
@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
"requires C11 complex type")
def test_complex(self):
for t in [ctypes.c_double_complex, ctypes.c_float_complex,
ctypes.c_longdouble_complex]:
for format, t in [
('Zd', ctypes.c_double_complex),
('Zf', ctypes.c_float_complex),
('Zg', ctypes.c_longdouble_complex),
]:
self.assertEqual(t(1).value, 1+0j)
self.assertEqual(t(1.0).value, 1+0j)
self.assertEqual(t(1+0.125j).value, 1+0.125j)
self.assertEqual(t(IndexLike()).value, 2+0j)
self.assertEqual(t(FloatLike()).value, 2+0j)
self.assertEqual(t(ComplexLike()).value, 1+1j)

prefix = '>' if sys.byteorder == 'big' else '<'
num = t(1.0)
self.assertEqual(memoryview(num).format, prefix + format)
array = (t * 3)()
self.assertEqual(memoryview(array).format, prefix + format)

@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
"requires C11 complex type")
def test_complex_round_trip(self):
Expand Down
Loading
Loading