Merge pull request #7165 from youknowone/ctypes

Update ctypes from v3.14.3 and make _ctypes compatible
This commit is contained in:
Jeong, YunWon
2026-02-18 08:21:27 +09:00
committed by GitHub
59 changed files with 5540 additions and 917 deletions

100
Lib/ctypes/__init__.py vendored
View File

@@ -14,6 +14,7 @@ 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
@@ -21,7 +22,7 @@ if __version__ != _ctypes_version:
raise Exception("Version number mismatch", __version__, _ctypes_version)
if _os.name == "nt":
from _ctypes import FormatError
from _ctypes import COMError, CopyComPointer, FormatError
DEFAULT_MODE = RTLD_LOCAL
if _os.name == "posix" and _sys.platform == "darwin":
@@ -208,6 +209,18 @@ class c_longdouble(_SimpleCData):
if sizeof(c_longdouble) == sizeof(c_double):
c_longdouble = c_double
try:
class c_double_complex(_SimpleCData):
_type_ = "D"
_check_size(c_double_complex)
class c_float_complex(_SimpleCData):
_type_ = "F"
_check_size(c_float_complex)
class c_longdouble_complex(_SimpleCData):
_type_ = "G"
except AttributeError:
pass
if _calcsize("l") == _calcsize("q"):
# if long and long long have the same size, make c_longlong an alias for c_long
c_longlong = c_long
@@ -255,7 +268,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"
@@ -266,7 +344,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()
@@ -274,7 +352,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
@@ -308,13 +385,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
@@ -521,6 +592,7 @@ elif sizeof(c_ulonglong) == sizeof(c_void_p):
# functions
from _ctypes import _memmove_addr, _memset_addr, _string_at_addr, _cast_addr
from _ctypes import _memoryview_at_addr
## void *memmove(void *, const void *, size_t);
memmove = CFUNCTYPE(c_void_p, c_void_p, c_void_p, c_size_t)(_memmove_addr)
@@ -546,6 +618,14 @@ def string_at(ptr, size=-1):
Return the byte string at void *ptr."""
return _string_at(ptr, size)
_memoryview_at = PYFUNCTYPE(
py_object, c_void_p, c_ssize_t, c_int)(_memoryview_at_addr)
def memoryview_at(ptr, size, readonly=False):
"""memoryview_at(ptr, size[, readonly]) -> memoryview
Return a memoryview representing the memory at void *ptr."""
return _memoryview_at(ptr, size, bool(readonly))
try:
from _ctypes import _wstring_at_addr
except ImportError:

330
Lib/ctypes/_layout.py vendored Normal file
View File

@@ -0,0 +1,330 @@
"""Python implementation of computing the layout of a struct/union
This code is internal and tightly coupled to the C part. The interface
may change at any time.
"""
import sys
import warnings
from _ctypes import CField, buffer_info
import ctypes
def round_down(n, multiple):
assert n >= 0
assert multiple > 0
return (n // multiple) * multiple
def round_up(n, multiple):
assert n >= 0
assert multiple > 0
return ((n + multiple - 1) // multiple) * multiple
_INT_MAX = (1 << (ctypes.sizeof(ctypes.c_int) * 8) - 1) - 1
class StructUnionLayout:
def __init__(self, fields, size, align, format_spec):
# sequence of CField objects
self.fields = fields
# total size of the aggregate (rounded up to alignment)
self.size = size
# total alignment requirement of the aggregate
self.align = align
# buffer format specification (as a string, UTF-8 but bes
# kept ASCII-only)
self.format_spec = format_spec
def get_layout(cls, input_fields, is_struct, base):
"""Return a StructUnionLayout for the given class.
Called by PyCStructUnionType_update_stginfo when _fields_ is assigned
to a class.
"""
# Currently there are two modes, selectable using the '_layout_' attribute:
#
# 'gcc-sysv' mode places fields one after another, bit by bit.
# But "each bit field must fit within a single object of its specified
# type" (GCC manual, section 15.8 "Bit Field Packing"). When it doesn't,
# we insert a few bits of padding to avoid that.
#
# 'ms' mode works similar except for bitfield packing. Adjacent
# bit-fields are packed into the same 1-, 2-, or 4-byte allocation unit
# if the integral types are the same size and if the next bit-field fits
# into the current allocation unit without crossing the boundary imposed
# by the common alignment requirements of the bit-fields.
#
# See https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html#index-mms-bitfields
# for details.
# We do not support zero length bitfields (we use bitsize != 0
# elsewhere to indicate a bitfield). Here, non-bitfields have bit_size
# set to size*8.
# For clarity, variables that count bits have `bit` in their names.
pack = getattr(cls, '_pack_', None)
layout = getattr(cls, '_layout_', None)
if layout is None:
if sys.platform == 'win32':
gcc_layout = False
elif pack:
if is_struct:
base_type_name = 'Structure'
else:
base_type_name = 'Union'
warnings._deprecated(
'_pack_ without _layout_',
f"Due to '_pack_', the '{cls.__name__}' {base_type_name} will "
+ "use memory layout compatible with MSVC (Windows). "
+ "If this is intended, set _layout_ to 'ms'. "
+ "The implicit default is deprecated and slated to become "
+ "an error in Python {remove}.",
remove=(3, 19),
)
gcc_layout = False
else:
gcc_layout = True
elif layout == 'ms':
gcc_layout = False
elif layout == 'gcc-sysv':
gcc_layout = True
else:
raise ValueError(f'unknown _layout_: {layout!r}')
align = getattr(cls, '_align_', 1)
if align < 0:
raise ValueError('_align_ must be a non-negative integer')
elif align == 0:
# Setting `_align_ = 0` amounts to using the default alignment
align = 1
if base:
align = max(ctypes.alignment(base), align)
swapped_bytes = hasattr(cls, '_swappedbytes_')
if swapped_bytes:
big_endian = sys.byteorder == 'little'
else:
big_endian = sys.byteorder == 'big'
if pack is not None:
try:
pack = int(pack)
except (TypeError, ValueError):
raise ValueError("_pack_ must be an integer")
if pack < 0:
raise ValueError("_pack_ must be a non-negative integer")
if pack > _INT_MAX:
raise ValueError("_pack_ too big")
if gcc_layout:
raise ValueError('_pack_ is not compatible with gcc-sysv layout')
result_fields = []
if is_struct:
format_spec_parts = ["T{"]
else:
format_spec_parts = ["B"]
last_field_bit_size = 0 # used in MS layout only
# `8 * next_byte_offset + next_bit_offset` points to where the
# next field would start.
next_bit_offset = 0
next_byte_offset = 0
# size if this was a struct (sum of field sizes, plus padding)
struct_size = 0
# max of field sizes; only meaningful for unions
union_size = 0
if base:
struct_size = ctypes.sizeof(base)
if gcc_layout:
next_bit_offset = struct_size * 8
else:
next_byte_offset = struct_size
last_size = struct_size
for i, field in enumerate(input_fields):
if not is_struct:
# Unions start fresh each time
last_field_bit_size = 0
next_bit_offset = 0
next_byte_offset = 0
# Unpack the field
field = tuple(field)
try:
name, ctype = field
except (ValueError, TypeError):
try:
name, ctype, bit_size = field
except (ValueError, TypeError) as exc:
raise ValueError(
'_fields_ must be a sequence of (name, C type) pairs '
+ 'or (name, C type, bit size) triples') from exc
is_bitfield = True
if bit_size <= 0:
raise ValueError(
f'number of bits invalid for bit field {name!r}')
type_size = ctypes.sizeof(ctype)
if bit_size > type_size * 8:
raise ValueError(
f'number of bits invalid for bit field {name!r}')
else:
is_bitfield = False
type_size = ctypes.sizeof(ctype)
bit_size = type_size * 8
type_bit_size = type_size * 8
type_align = ctypes.alignment(ctype) or 1
type_bit_align = type_align * 8
if gcc_layout:
# We don't use next_byte_offset here
assert pack is None
assert next_byte_offset == 0
# Determine whether the bit field, if placed at the next
# free bit, fits within a single object of its specified type.
# That is: determine a "slot", sized & aligned for the
# specified type, which contains the bitfield's beginning:
slot_start_bit = round_down(next_bit_offset, type_bit_align)
slot_end_bit = slot_start_bit + type_bit_size
# And see if it also contains the bitfield's last bit:
field_end_bit = next_bit_offset + bit_size
if field_end_bit > slot_end_bit:
# It doesn't: add padding (bump up to the next
# alignment boundary)
next_bit_offset = round_up(next_bit_offset, type_bit_align)
offset = round_down(next_bit_offset, type_bit_align) // 8
if is_bitfield:
bit_offset = next_bit_offset - 8 * offset
assert bit_offset <= type_bit_size
else:
assert offset == next_bit_offset / 8
next_bit_offset += bit_size
struct_size = round_up(next_bit_offset, 8) // 8
else:
if pack:
type_align = min(pack, type_align)
# next_byte_offset points to end of current bitfield.
# next_bit_offset is generally non-positive,
# and 8 * next_byte_offset + next_bit_offset points just behind
# the end of the last field we placed.
if (
(0 < next_bit_offset + bit_size)
or (type_bit_size != last_field_bit_size)
):
# Close the previous bitfield (if any)
# and start a new bitfield
next_byte_offset = round_up(next_byte_offset, type_align)
next_byte_offset += type_size
last_field_bit_size = type_bit_size
# Reminder: 8 * (next_byte_offset) + next_bit_offset
# points to where we would start a new field, namely
# just behind where we placed the last field plus an
# allowance for alignment.
next_bit_offset = -last_field_bit_size
assert type_bit_size == last_field_bit_size
offset = next_byte_offset - last_field_bit_size // 8
if is_bitfield:
assert 0 <= (last_field_bit_size + next_bit_offset)
bit_offset = last_field_bit_size + next_bit_offset
if type_bit_size:
assert (last_field_bit_size + next_bit_offset) < type_bit_size
next_bit_offset += bit_size
struct_size = next_byte_offset
if is_bitfield and big_endian:
# On big-endian architectures, bit fields are also laid out
# starting with the big end.
bit_offset = type_bit_size - bit_size - bit_offset
# Add the format spec parts
if is_struct:
padding = offset - last_size
format_spec_parts.append(padding_spec(padding))
fieldfmt, bf_ndim, bf_shape = buffer_info(ctype)
if bf_shape:
format_spec_parts.extend((
"(",
','.join(str(n) for n in bf_shape),
")",
))
if fieldfmt is None:
fieldfmt = "B"
if isinstance(name, bytes):
# a bytes name would be rejected later, but we check early
# to avoid a BytesWarning with `python -bb`
raise TypeError(
f"field {name!r}: name must be a string, not bytes")
format_spec_parts.append(f"{fieldfmt}:{name}:")
result_fields.append(CField(
name=name,
type=ctype,
byte_size=type_size,
byte_offset=offset,
bit_size=bit_size if is_bitfield else None,
bit_offset=bit_offset if is_bitfield else None,
index=i,
# Do not use CField outside ctypes, yet.
# The constructor is internal API and may change without warning.
_internal_use=True,
))
if is_bitfield and not gcc_layout:
assert type_bit_size > 0
align = max(align, type_align)
last_size = struct_size
if not is_struct:
union_size = max(struct_size, union_size)
if is_struct:
total_size = struct_size
else:
total_size = union_size
# Adjust the size according to the alignment requirements
aligned_size = round_up(total_size, align)
# Finish up the format spec
if is_struct:
padding = aligned_size - total_size
format_spec_parts.append(padding_spec(padding))
format_spec_parts.append("}")
return StructUnionLayout(
fields=result_fields,
size=aligned_size,
align=align,
format_spec="".join(format_spec_parts),
)
def padding_spec(padding):
if padding <= 0:
return ""
if padding == 1:
return "x"
return f"{padding}x"

150
Lib/ctypes/util.py vendored
View File

@@ -67,6 +67,65 @@ if os.name == "nt":
return fname
return None
# Listing loaded DLLs on Windows relies on the following APIs:
# https://learn.microsoft.com/windows/win32/api/psapi/nf-psapi-enumprocessmodules
# https://learn.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew
import ctypes
from ctypes import wintypes
_kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
_get_current_process = _kernel32["GetCurrentProcess"]
_get_current_process.restype = wintypes.HANDLE
_k32_get_module_file_name = _kernel32["GetModuleFileNameW"]
_k32_get_module_file_name.restype = wintypes.DWORD
_k32_get_module_file_name.argtypes = (
wintypes.HMODULE,
wintypes.LPWSTR,
wintypes.DWORD,
)
_psapi = ctypes.WinDLL('psapi', use_last_error=True)
_enum_process_modules = _psapi["EnumProcessModules"]
_enum_process_modules.restype = wintypes.BOOL
_enum_process_modules.argtypes = (
wintypes.HANDLE,
ctypes.POINTER(wintypes.HMODULE),
wintypes.DWORD,
wintypes.LPDWORD,
)
def _get_module_filename(module: wintypes.HMODULE):
name = (wintypes.WCHAR * 32767)() # UNICODE_STRING_MAX_CHARS
if _k32_get_module_file_name(module, name, len(name)):
return name.value
return None
def _get_module_handles():
process = _get_current_process()
space_needed = wintypes.DWORD()
n = 1024
while True:
modules = (wintypes.HMODULE * n)()
if not _enum_process_modules(process,
modules,
ctypes.sizeof(modules),
ctypes.byref(space_needed)):
err = ctypes.get_last_error()
msg = ctypes.FormatError(err).strip()
raise ctypes.WinError(err, f"EnumProcessModules failed: {msg}")
n = space_needed.value // ctypes.sizeof(wintypes.HMODULE)
if n <= len(modules):
return modules[:n]
def dllist():
"""Return a list of loaded shared libraries in the current process."""
modules = _get_module_handles()
libraries = [name for h in modules
if (name := _get_module_filename(h)) is not None]
return libraries
elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos"}:
from ctypes.macholib.dyld import dyld_find as _dyld_find
def find_library(name):
@@ -80,6 +139,22 @@ elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos"}
continue
return None
# Listing loaded libraries on Apple systems relies on the following API:
# https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dyld.3.html
import ctypes
_libc = ctypes.CDLL(find_library("c"))
_dyld_get_image_name = _libc["_dyld_get_image_name"]
_dyld_get_image_name.restype = ctypes.c_char_p
def dllist():
"""Return a list of loaded shared libraries in the current process."""
num_images = _libc._dyld_image_count()
libraries = [os.fsdecode(name) for i in range(num_images)
if (name := _dyld_get_image_name(i)) is not None]
return libraries
elif sys.platform.startswith("aix"):
# AIX has two styles of storing shared libraries
# GNU auto_tools refer to these as svr4 and aix
@@ -98,6 +173,25 @@ elif sys.platform == "android":
fname = f"{directory}/lib{name}.so"
return fname if os.path.isfile(fname) else None
elif sys.platform == "emscripten":
def _is_wasm(filename):
# Return True if the given file is an WASM module
wasm_header = b"\x00asm"
with open(filename, 'br') as thefile:
return thefile.read(4) == wasm_header
def find_library(name):
candidates = [f"lib{name}.so", f"lib{name}.wasm"]
paths = os.environ.get("LD_LIBRARY_PATH", "")
for libdir in paths.split(":"):
for name in candidates:
libfile = os.path.join(libdir, name)
if os.path.isfile(libfile) and _is_wasm(libfile):
return libfile
return None
elif os.name == "posix":
# Andreas Degert's find functions, using gcc, /sbin/ldconfig, objdump
import re, tempfile
@@ -341,6 +435,55 @@ elif os.name == "posix":
return _findSoname_ldconfig(name) or \
_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
################################################################
# test code
@@ -384,5 +527,12 @@ def test():
print(cdll.LoadLibrary("libcrypt.so"))
print(find_library("crypt"))
try:
dllist
except NameError:
print('dllist() not available')
else:
print(dllist())
if __name__ == "__main__":
test()

View File

@@ -63,10 +63,16 @@ HACCEL = HANDLE
HBITMAP = HANDLE
HBRUSH = HANDLE
HCOLORSPACE = HANDLE
HCONV = HANDLE
HCONVLIST = HANDLE
HCURSOR = HANDLE
HDC = HANDLE
HDDEDATA = HANDLE
HDESK = HANDLE
HDROP = HANDLE
HDWP = HANDLE
HENHMETAFILE = HANDLE
HFILE = INT
HFONT = HANDLE
HGDIOBJ = HANDLE
HGLOBAL = HANDLE
@@ -82,9 +88,11 @@ HMODULE = HANDLE
HMONITOR = HANDLE
HPALETTE = HANDLE
HPEN = HANDLE
HRESULT = LONG
HRGN = HANDLE
HRSRC = HANDLE
HSTR = HANDLE
HSZ = HANDLE
HTASK = HANDLE
HWINSTA = HANDLE
HWND = HANDLE

View File

@@ -2,15 +2,13 @@
import ctypes
from _ctypes import Structure, Union, _Pointer, Array, _SimpleCData, CFuncPtr
import sys
from test import support
_CData = Structure.__base__
assert _CData.__name__ == "_CData"
class _X(Structure):
_fields_ = [("x", ctypes.c_int)]
CField = type(_X.x)
# metaclasses
PyCStructType = type(Structure)
UnionType = type(Union)
@@ -22,3 +20,132 @@ PyCFuncPtrType = type(CFuncPtr)
# type flags
Py_TPFLAGS_DISALLOW_INSTANTIATION = 1 << 7
Py_TPFLAGS_IMMUTABLETYPE = 1 << 8
def is_underaligned(ctype):
"""Return true when type's alignment is less than its size.
A famous example is 64-bit int on 32-bit x86.
"""
return ctypes.alignment(ctype) < ctypes.sizeof(ctype)
class StructCheckMixin:
def check_struct(self, structure):
"""Assert that a structure is well-formed"""
self._check_struct_or_union(structure, is_struct=True)
def check_union(self, union):
"""Assert that a union is well-formed"""
self._check_struct_or_union(union, is_struct=False)
def check_struct_or_union(self, cls):
if issubclass(cls, Structure):
self._check_struct_or_union(cls, is_struct=True)
elif issubclass(cls, Union):
self._check_struct_or_union(cls, is_struct=False)
else:
raise TypeError(cls)
def _check_struct_or_union(self, cls, is_struct):
# Check that fields are not overlapping (for structs),
# and that their metadata is consistent.
used_bits = 0
is_little_endian = (
hasattr(cls, '_swappedbytes_') ^ (sys.byteorder == 'little'))
anon_names = getattr(cls, '_anonymous_', ())
cls_size = ctypes.sizeof(cls)
for name, requested_type, *rest_of_tuple in cls._fields_:
field = getattr(cls, name)
with self.subTest(name=name, field=field):
is_bitfield = len(rest_of_tuple) > 0
# name
self.assertEqual(field.name, name)
# type
self.assertEqual(field.type, requested_type)
# offset === byte_offset
self.assertEqual(field.byte_offset, field.offset)
if not is_struct:
self.assertEqual(field.byte_offset, 0)
# byte_size
self.assertEqual(field.byte_size, ctypes.sizeof(field.type))
self.assertGreaterEqual(field.byte_size, 0)
# Check that the field is inside the struct.
# See gh-130410 for why this is skipped for bitfields of
# underaligned types. Later in this function (see `bit_end`)
# we assert that the value *bits* are inside the struct.
if not (field.is_bitfield and is_underaligned(field.type)):
self.assertLessEqual(field.byte_offset + field.byte_size,
cls_size)
# size
self.assertGreaterEqual(field.size, 0)
if is_bitfield:
# size has backwards-compatible bit-packed info
expected_size = (field.bit_size << 16) + field.bit_offset
self.assertEqual(field.size, expected_size)
else:
# size == byte_size
self.assertEqual(field.size, field.byte_size)
# is_bitfield (bool)
self.assertIs(field.is_bitfield, is_bitfield)
# bit_offset
if is_bitfield:
self.assertGreaterEqual(field.bit_offset, 0)
self.assertLessEqual(field.bit_offset + field.bit_size,
field.byte_size * 8)
else:
self.assertEqual(field.bit_offset, 0)
if not is_struct:
if is_little_endian:
self.assertEqual(field.bit_offset, 0)
else:
self.assertEqual(field.bit_offset,
field.byte_size * 8 - field.bit_size)
# bit_size
if is_bitfield:
self.assertGreaterEqual(field.bit_size, 0)
self.assertLessEqual(field.bit_size, field.byte_size * 8)
[requested_bit_size] = rest_of_tuple
self.assertEqual(field.bit_size, requested_bit_size)
else:
self.assertEqual(field.bit_size, field.byte_size * 8)
# is_anonymous (bool)
self.assertIs(field.is_anonymous, name in anon_names)
# In a struct, field should not overlap.
# (Test skipped if the structs is enormous.)
if is_struct and cls_size < 10_000:
# Get a mask indicating where the field is within the struct
if is_little_endian:
tp_shift = field.byte_offset * 8
else:
tp_shift = (cls_size
- field.byte_offset
- field.byte_size) * 8
mask = (1 << field.bit_size) - 1
mask <<= (tp_shift + field.bit_offset)
assert mask.bit_count() == field.bit_size
# Check that these bits aren't shared with previous fields
self.assertEqual(used_bits & mask, 0)
# Mark the bits for future checks
used_bits |= mask
# field is inside cls
bit_end = (field.byte_offset * 8
+ field.bit_offset
+ field.bit_size)
self.assertLessEqual(bit_end, cls_size * 8)

View File

@@ -1,13 +1,13 @@
from ctypes import (
c_char, c_uint32, c_uint16, c_ubyte, c_byte, alignment, sizeof,
BigEndianStructure, LittleEndianStructure,
BigEndianUnion, LittleEndianUnion, Structure
BigEndianUnion, LittleEndianUnion, Structure,
)
import struct
import unittest
from ._support import StructCheckMixin
class TestAlignedStructures(unittest.TestCase):
class TestAlignedStructures(unittest.TestCase, StructCheckMixin):
def test_aligned_string(self):
for base, e in (
(LittleEndianStructure, "<"),
@@ -19,12 +19,14 @@ class TestAlignedStructures(unittest.TestCase):
_fields_ = [
('value', c_char * 12)
]
self.check_struct(Aligned)
class Main(base):
_fields_ = [
('first', c_uint32),
('string', Aligned),
]
self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(main.first, 7)
@@ -46,12 +48,14 @@ class TestAlignedStructures(unittest.TestCase):
("bool1", c_ubyte),
("bool2", c_ubyte),
]
self.check_struct(SomeBools)
class Main(base):
_fields_ = [
("x", c_ubyte),
("y", SomeBools),
("z", c_ubyte),
]
self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(alignment(SomeBools), 4)
@@ -65,222 +69,6 @@ class TestAlignedStructures(unittest.TestCase):
self.assertEqual(Main.z.offset, 8)
self.assertEqual(main.z, 7)
def test_oversized_structure(self):
data = bytearray(b"\0" * 8)
for base in (LittleEndianStructure, BigEndianStructure):
class SomeBoolsTooBig(base):
_align_ = 8
_fields_ = [
("bool1", c_ubyte),
("bool2", c_ubyte),
("bool3", c_ubyte),
]
class Main(base):
_fields_ = [
("y", SomeBoolsTooBig),
("z", c_uint32),
]
with self.assertRaises(ValueError) as ctx:
Main.from_buffer(data)
self.assertEqual(
ctx.exception.args[0],
'Buffer size too small (4 instead of at least 8 bytes)'
)
def test_aligned_subclasses(self):
for base, e in (
(LittleEndianStructure, "<"),
(BigEndianStructure, ">"),
):
data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4))
class UnalignedSub(base):
x: c_uint32
_fields_ = [
("x", c_uint32),
]
class AlignedStruct(UnalignedSub):
_align_ = 8
_fields_ = [
("y", c_uint32),
]
class Main(base):
_fields_ = [
("a", c_uint32),
("b", AlignedStruct)
]
main = Main.from_buffer(data)
self.assertEqual(alignment(main.b), 8)
self.assertEqual(alignment(main), 8)
self.assertEqual(sizeof(main.b), 8)
self.assertEqual(sizeof(main), 16)
self.assertEqual(main.a, 1)
self.assertEqual(main.b.x, 3)
self.assertEqual(main.b.y, 4)
self.assertEqual(Main.b.offset, 8)
self.assertEqual(Main.b.size, 8)
def test_aligned_union(self):
for sbase, ubase, e in (
(LittleEndianStructure, LittleEndianUnion, "<"),
(BigEndianStructure, BigEndianUnion, ">"),
):
data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4))
class AlignedUnion(ubase):
_align_ = 8
_fields_ = [
("a", c_uint32),
("b", c_ubyte * 7),
]
class Main(sbase):
_fields_ = [
("first", c_uint32),
("union", AlignedUnion),
]
main = Main.from_buffer(data)
self.assertEqual(main.first, 1)
self.assertEqual(main.union.a, 3)
self.assertEqual(bytes(main.union.b), data[8:-1])
self.assertEqual(Main.union.offset, 8)
self.assertEqual(Main.union.size, 8)
self.assertEqual(alignment(main.union), 8)
self.assertEqual(alignment(main), 8)
def test_aligned_struct_in_union(self):
for sbase, ubase, e in (
(LittleEndianStructure, LittleEndianUnion, "<"),
(BigEndianStructure, BigEndianUnion, ">"),
):
data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4))
class Sub(sbase):
_align_ = 8
_fields_ = [
("x", c_uint32),
("y", c_uint32),
]
class MainUnion(ubase):
_fields_ = [
("a", c_uint32),
("b", Sub),
]
class Main(sbase):
_fields_ = [
("first", c_uint32),
("union", MainUnion),
]
main = Main.from_buffer(data)
self.assertEqual(Main.first.size, 4)
self.assertEqual(alignment(main.union), 8)
self.assertEqual(alignment(main), 8)
self.assertEqual(Main.union.offset, 8)
self.assertEqual(Main.union.size, 8)
self.assertEqual(main.first, 1)
self.assertEqual(main.union.a, 3)
self.assertEqual(main.union.b.x, 3)
self.assertEqual(main.union.b.y, 4)
def test_smaller_aligned_subclassed_union(self):
for sbase, ubase, e in (
(LittleEndianStructure, LittleEndianUnion, "<"),
(BigEndianStructure, BigEndianUnion, ">"),
):
data = bytearray(struct.pack(f"{e}H2xI", 1, 0xD60102D7))
class SubUnion(ubase):
_align_ = 2
_fields_ = [
("unsigned", c_ubyte),
("signed", c_byte),
]
class MainUnion(SubUnion):
_fields_ = [
("num", c_uint32)
]
class Main(sbase):
_fields_ = [
("first", c_uint16),
("union", MainUnion),
]
main = Main.from_buffer(data)
self.assertEqual(main.union.num, 0xD60102D7)
self.assertEqual(main.union.unsigned, data[4])
self.assertEqual(main.union.signed, data[4] - 256)
self.assertEqual(alignment(main), 4)
self.assertEqual(alignment(main.union), 4)
self.assertEqual(Main.union.offset, 4)
self.assertEqual(Main.union.size, 4)
self.assertEqual(Main.first.size, 2)
def test_larger_aligned_subclassed_union(self):
for ubase, e in (
(LittleEndianUnion, "<"),
(BigEndianUnion, ">"),
):
data = bytearray(struct.pack(f"{e}I4x", 0xD60102D6))
class SubUnion(ubase):
_align_ = 8
_fields_ = [
("unsigned", c_ubyte),
("signed", c_byte),
]
class Main(SubUnion):
_fields_ = [
("num", c_uint32)
]
main = Main.from_buffer(data)
self.assertEqual(alignment(main), 8)
self.assertEqual(sizeof(main), 8)
self.assertEqual(main.num, 0xD60102D6)
self.assertEqual(main.unsigned, 0xD6)
self.assertEqual(main.signed, -42)
def test_aligned_packed_structures(self):
for sbase, e in (
(LittleEndianStructure, "<"),
(BigEndianStructure, ">"),
):
data = bytearray(struct.pack(f"{e}B2H4xB", 1, 2, 3, 4))
class Inner(sbase):
_align_ = 8
_fields_ = [
("x", c_uint16),
("y", c_uint16),
]
class Main(sbase):
_pack_ = 1
_fields_ = [
("a", c_ubyte),
("b", Inner),
("c", c_ubyte),
]
main = Main.from_buffer(data)
self.assertEqual(sizeof(main), 10)
self.assertEqual(Main.b.offset, 1)
# Alignment == 8 because _pack_ wins out.
self.assertEqual(alignment(main.b), 8)
# Size is still 8 though since inside this Structure, it will have
# effect.
self.assertEqual(sizeof(main.b), 8)
self.assertEqual(Main.c.offset, 9)
self.assertEqual(main.a, 1)
self.assertEqual(main.b.x, 2)
self.assertEqual(main.b.y, 3)
self.assertEqual(main.c, 4)
def test_negative_align(self):
for base in (Structure, LittleEndianStructure, BigEndianStructure):
with (
@@ -316,6 +104,240 @@ class TestAlignedStructures(unittest.TestCase):
self.assertEqual(alignment(MyStructure), 1)
self.assertEqual(alignment(MyStructure()), 1)
def test_oversized_structure(self):
data = bytearray(b"\0" * 8)
for base in (LittleEndianStructure, BigEndianStructure):
class SomeBoolsTooBig(base):
_align_ = 8
_fields_ = [
("bool1", c_ubyte),
("bool2", c_ubyte),
("bool3", c_ubyte),
]
self.check_struct(SomeBoolsTooBig)
class Main(base):
_fields_ = [
("y", SomeBoolsTooBig),
("z", c_uint32),
]
self.check_struct(Main)
with self.assertRaises(ValueError) as ctx:
Main.from_buffer(data)
self.assertEqual(
ctx.exception.args[0],
'Buffer size too small (4 instead of at least 8 bytes)'
)
def test_aligned_subclasses(self):
for base, e in (
(LittleEndianStructure, "<"),
(BigEndianStructure, ">"),
):
data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4))
class UnalignedSub(base):
x: c_uint32
_fields_ = [
("x", c_uint32),
]
self.check_struct(UnalignedSub)
class AlignedStruct(UnalignedSub):
_align_ = 8
_fields_ = [
("y", c_uint32),
]
self.check_struct(AlignedStruct)
class Main(base):
_fields_ = [
("a", c_uint32),
("b", AlignedStruct)
]
self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(alignment(main.b), 8)
self.assertEqual(alignment(main), 8)
self.assertEqual(sizeof(main.b), 8)
self.assertEqual(sizeof(main), 16)
self.assertEqual(main.a, 1)
self.assertEqual(main.b.x, 3)
self.assertEqual(main.b.y, 4)
self.assertEqual(Main.b.offset, 8)
self.assertEqual(Main.b.size, 8)
def test_aligned_union(self):
for sbase, ubase, e in (
(LittleEndianStructure, LittleEndianUnion, "<"),
(BigEndianStructure, BigEndianUnion, ">"),
):
data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4))
class AlignedUnion(ubase):
_align_ = 8
_fields_ = [
("a", c_uint32),
("b", c_ubyte * 7),
]
self.check_union(AlignedUnion)
class Main(sbase):
_fields_ = [
("first", c_uint32),
("union", AlignedUnion),
]
self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(main.first, 1)
self.assertEqual(main.union.a, 3)
self.assertEqual(bytes(main.union.b), data[8:-1])
self.assertEqual(Main.union.offset, 8)
self.assertEqual(Main.union.size, 8)
self.assertEqual(alignment(main.union), 8)
self.assertEqual(alignment(main), 8)
def test_aligned_struct_in_union(self):
for sbase, ubase, e in (
(LittleEndianStructure, LittleEndianUnion, "<"),
(BigEndianStructure, BigEndianUnion, ">"),
):
data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4))
class Sub(sbase):
_align_ = 8
_fields_ = [
("x", c_uint32),
("y", c_uint32),
]
self.check_struct(Sub)
class MainUnion(ubase):
_fields_ = [
("a", c_uint32),
("b", Sub),
]
self.check_union(MainUnion)
class Main(sbase):
_fields_ = [
("first", c_uint32),
("union", MainUnion),
]
self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(Main.first.size, 4)
self.assertEqual(alignment(main.union), 8)
self.assertEqual(alignment(main), 8)
self.assertEqual(Main.union.offset, 8)
self.assertEqual(Main.union.size, 8)
self.assertEqual(main.first, 1)
self.assertEqual(main.union.a, 3)
self.assertEqual(main.union.b.x, 3)
self.assertEqual(main.union.b.y, 4)
def test_smaller_aligned_subclassed_union(self):
for sbase, ubase, e in (
(LittleEndianStructure, LittleEndianUnion, "<"),
(BigEndianStructure, BigEndianUnion, ">"),
):
data = bytearray(struct.pack(f"{e}H2xI", 1, 0xD60102D7))
class SubUnion(ubase):
_align_ = 2
_fields_ = [
("unsigned", c_ubyte),
("signed", c_byte),
]
self.check_union(SubUnion)
class MainUnion(SubUnion):
_fields_ = [
("num", c_uint32)
]
self.check_union(SubUnion)
class Main(sbase):
_fields_ = [
("first", c_uint16),
("union", MainUnion),
]
self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(main.union.num, 0xD60102D7)
self.assertEqual(main.union.unsigned, data[4])
self.assertEqual(main.union.signed, data[4] - 256)
self.assertEqual(alignment(main), 4)
self.assertEqual(alignment(main.union), 4)
self.assertEqual(Main.union.offset, 4)
self.assertEqual(Main.union.size, 4)
self.assertEqual(Main.first.size, 2)
def test_larger_aligned_subclassed_union(self):
for ubase, e in (
(LittleEndianUnion, "<"),
(BigEndianUnion, ">"),
):
data = bytearray(struct.pack(f"{e}I4x", 0xD60102D6))
class SubUnion(ubase):
_align_ = 8
_fields_ = [
("unsigned", c_ubyte),
("signed", c_byte),
]
self.check_union(SubUnion)
class Main(SubUnion):
_fields_ = [
("num", c_uint32)
]
self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(alignment(main), 8)
self.assertEqual(sizeof(main), 8)
self.assertEqual(main.num, 0xD60102D6)
self.assertEqual(main.unsigned, 0xD6)
self.assertEqual(main.signed, -42)
def test_aligned_packed_structures(self):
for sbase, e in (
(LittleEndianStructure, "<"),
(BigEndianStructure, ">"),
):
data = bytearray(struct.pack(f"{e}B2H4xB", 1, 2, 3, 4))
class Inner(sbase):
_align_ = 8
_fields_ = [
("x", c_uint16),
("y", c_uint16),
]
self.check_struct(Inner)
class Main(sbase):
_pack_ = 1
_layout_ = "ms"
_fields_ = [
("a", c_ubyte),
("b", Inner),
("c", c_ubyte),
]
self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(sizeof(main), 10)
self.assertEqual(Main.b.offset, 1)
# Alignment == 8 because _pack_ wins out.
self.assertEqual(alignment(main.b), 8)
# Size is still 8 though since inside this Structure, it will have
# effect.
self.assertEqual(sizeof(main.b), 8)
self.assertEqual(Main.c.offset, 9)
self.assertEqual(main.a, 1)
self.assertEqual(main.b.x, 2)
self.assertEqual(main.b.y, 3)
self.assertEqual(main.c, 4)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,20 +1,23 @@
import unittest
import test.support
from ctypes import c_int, Union, Structure, sizeof
from ._support import StructCheckMixin
class AnonTest(unittest.TestCase):
class AnonTest(unittest.TestCase, StructCheckMixin):
def test_anon(self):
class ANON(Union):
_fields_ = [("a", c_int),
("b", c_int)]
self.check_union(ANON)
class Y(Structure):
_fields_ = [("x", c_int),
("_", ANON),
("y", c_int)]
_anonymous_ = ["_"]
self.check_struct(Y)
self.assertEqual(Y.a.offset, sizeof(c_int))
self.assertEqual(Y.b.offset, sizeof(c_int))
@@ -52,17 +55,20 @@ class AnonTest(unittest.TestCase):
def test_nested(self):
class ANON_S(Structure):
_fields_ = [("a", c_int)]
self.check_struct(ANON_S)
class ANON_U(Union):
_fields_ = [("_", ANON_S),
("b", c_int)]
_anonymous_ = ["_"]
self.check_union(ANON_U)
class Y(Structure):
_fields_ = [("x", c_int),
("_", ANON_U),
("y", c_int)]
_anonymous_ = ["_"]
self.check_struct(Y)
self.assertEqual(Y.x.offset, 0)
self.assertEqual(Y.a.offset, sizeof(c_int))

View File

@@ -5,7 +5,7 @@ from ctypes import (Structure, Array, ARRAY, sizeof, addressof,
create_string_buffer, create_unicode_buffer,
c_char, c_wchar, c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint,
c_long, c_ulonglong, c_float, c_double, c_longdouble)
from test.support import bigmemtest, _2G
from test.support import bigmemtest, _2G, threading_helper, Py_GIL_DISABLED
from ._support import (_CData, PyCArrayType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)
@@ -267,6 +267,26 @@ class ArrayTestCase(unittest.TestCase):
def test_large_array(self, size):
c_char * size
@threading_helper.requires_working_threading()
@unittest.skipUnless(Py_GIL_DISABLED, "only meaningful if the GIL is disabled")
def test_thread_safety(self):
from threading import Thread
buffer = (ctypes.c_char_p * 10)()
def run():
for i in range(100):
buffer.value = b"hello"
buffer[0] = b"j"
with threading_helper.catch_threading_exception() as cm:
threads = (Thread(target=run) for _ in range(25))
with threading_helper.start_threads(threads):
pass
if cm.exc_value:
raise cm.exc_value
if __name__ == '__main__':
unittest.main()

View File

@@ -5,7 +5,7 @@ from ctypes import (Structure, CDLL, CFUNCTYPE,
c_short, c_int, c_long, c_longlong,
c_byte, c_wchar, c_float, c_double,
ArgumentError)
from test.support import import_helper
from test.support import import_helper, skip_if_sanitizer
_ctypes_test = import_helper.import_module("_ctypes_test")
@@ -192,6 +192,7 @@ class BasicWrapTestCase(unittest.TestCase):
self.assertEqual((s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h),
(9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9))
@skip_if_sanitizer('requires deep stack', thread=True)
def test_recursive_as_param(self):
class A:
pass

View File

@@ -1,72 +1,137 @@
import os
import sys
import unittest
from ctypes import (CDLL, Structure, sizeof, POINTER, byref, alignment,
LittleEndianStructure, BigEndianStructure,
c_byte, c_ubyte, c_char, c_char_p, c_void_p, c_wchar,
c_uint32, c_uint64,
c_short, c_ushort, c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong)
c_uint8, c_uint16, c_uint32, c_uint64,
c_short, c_ushort, c_int, c_uint, c_long, c_ulong,
c_longlong, c_ulonglong,
Union)
from test import support
from test.support import import_helper
from ._support import StructCheckMixin
_ctypes_test = import_helper.import_module("_ctypes_test")
class BITS(Structure):
_fields_ = [("A", c_int, 1),
("B", c_int, 2),
("C", c_int, 3),
("D", c_int, 4),
("E", c_int, 5),
("F", c_int, 6),
("G", c_int, 7),
("H", c_int, 8),
("I", c_int, 9),
TEST_FIELDS = (
("A", c_int, 1),
("B", c_int, 2),
("C", c_int, 3),
("D", c_int, 4),
("E", c_int, 5),
("F", c_int, 6),
("G", c_int, 7),
("H", c_int, 8),
("I", c_int, 9),
("M", c_short, 1),
("N", c_short, 2),
("O", c_short, 3),
("P", c_short, 4),
("Q", c_short, 5),
("R", c_short, 6),
("S", c_short, 7)]
("M", c_short, 1),
("N", c_short, 2),
("O", c_short, 3),
("P", c_short, 4),
("Q", c_short, 5),
("R", c_short, 6),
("S", c_short, 7),
)
class BITS(Structure):
_fields_ = TEST_FIELDS
func = CDLL(_ctypes_test.__file__).unpack_bitfields
func.argtypes = POINTER(BITS), c_char
class BITS_msvc(Structure):
_layout_ = "ms"
_fields_ = TEST_FIELDS
class BITS_gcc(Structure):
_layout_ = "gcc-sysv"
_fields_ = TEST_FIELDS
try:
func_msvc = CDLL(_ctypes_test.__file__).unpack_bitfields_msvc
except AttributeError as err:
# The MSVC struct must be available on Windows; it's optional elsewhere
if support.MS_WINDOWS:
raise err
func_msvc = None
else:
func_msvc.argtypes = POINTER(BITS_msvc), c_char
class C_Test(unittest.TestCase):
def test_ints(self):
for i in range(512):
for name in "ABCDEFGHI":
b = BITS()
setattr(b, name, i)
self.assertEqual(getattr(b, name), func(byref(b), name.encode('ascii')))
with self.subTest(i=i, name=name):
b = BITS()
setattr(b, name, i)
self.assertEqual(
getattr(b, name),
func(byref(b), (name.encode('ascii'))))
# bpo-46913: _ctypes/cfield.c h_get() has an undefined behavior
@support.skip_if_sanitizer(ub=True)
def test_shorts(self):
b = BITS()
name = "M"
# See Modules/_ctypes/_ctypes_test.c for where the magic 999 comes from.
if func(byref(b), name.encode('ascii')) == 999:
# unpack_bitfields and unpack_bitfields_msvc in
# Modules/_ctypes/_ctypes_test.c return 999 to indicate
# an invalid name. 'M' is only valid, if signed short bitfields
# are supported by the C compiler.
self.skipTest("Compiler does not support signed short bitfields")
for i in range(256):
for name in "MNOPQRS":
b = BITS()
setattr(b, name, i)
self.assertEqual(getattr(b, name), func(byref(b), name.encode('ascii')))
with self.subTest(i=i, name=name):
b = BITS()
setattr(b, name, i)
self.assertEqual(
getattr(b, name),
func(byref(b), (name.encode('ascii'))))
@unittest.skipUnless(func_msvc, "need MSVC or __attribute__((ms_struct))")
def test_shorts_msvc_mode(self):
b = BITS_msvc()
name = "M"
# See Modules/_ctypes/_ctypes_test.c for where the magic 999 comes from.
if func_msvc(byref(b), name.encode('ascii')) == 999:
# unpack_bitfields and unpack_bitfields_msvc in
# Modules/_ctypes/_ctypes_test.c return 999 to indicate
# an invalid name. 'M' is only valid, if signed short bitfields
# are supported by the C compiler.
self.skipTest("Compiler does not support signed short bitfields")
for i in range(256):
for name in "MNOPQRS":
with self.subTest(i=i, name=name):
b = BITS_msvc()
setattr(b, name, i)
self.assertEqual(
getattr(b, name),
func_msvc(byref(b), name.encode('ascii')))
signed_int_types = (c_byte, c_short, c_int, c_long, c_longlong)
unsigned_int_types = (c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong)
int_types = unsigned_int_types + signed_int_types
class BitFieldTest(unittest.TestCase):
class BitFieldTest(unittest.TestCase, StructCheckMixin):
def test_generic_checks(self):
self.check_struct(BITS)
self.check_struct(BITS_msvc)
self.check_struct(BITS_gcc)
def test_longlong(self):
class X(Structure):
_fields_ = [("a", c_longlong, 1),
("b", c_longlong, 62),
("c", c_longlong, 1)]
self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_longlong))
x = X()
@@ -78,6 +143,7 @@ class BitFieldTest(unittest.TestCase):
_fields_ = [("a", c_ulonglong, 1),
("b", c_ulonglong, 62),
("c", c_ulonglong, 1)]
self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_longlong))
x = X()
@@ -87,39 +153,49 @@ class BitFieldTest(unittest.TestCase):
def test_signed(self):
for c_typ in signed_int_types:
class X(Structure):
_fields_ = [("dummy", c_typ),
("a", c_typ, 3),
("b", c_typ, 3),
("c", c_typ, 1)]
self.assertEqual(sizeof(X), sizeof(c_typ)*2)
with self.subTest(c_typ):
if sizeof(c_typ) != alignment(c_typ):
self.skipTest('assumes size=alignment')
class X(Structure):
_fields_ = [("dummy", c_typ),
("a", c_typ, 3),
("b", c_typ, 3),
("c", c_typ, 1)]
self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_typ)*2)
x = X()
self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 0, 0))
x.a = -1
self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, -1, 0, 0))
x.a, x.b = 0, -1
self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, -1, 0))
x = X()
self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 0, 0))
x.a = -1
self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, -1, 0, 0))
x.a, x.b = 0, -1
self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, -1, 0))
def test_unsigned(self):
for c_typ in unsigned_int_types:
class X(Structure):
_fields_ = [("a", c_typ, 3),
("b", c_typ, 3),
("c", c_typ, 1)]
self.assertEqual(sizeof(X), sizeof(c_typ))
with self.subTest(c_typ):
if sizeof(c_typ) != alignment(c_typ):
self.skipTest('assumes size=alignment')
class X(Structure):
_fields_ = [("a", c_typ, 3),
("b", c_typ, 3),
("c", c_typ, 1)]
self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_typ))
x = X()
self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 0, 0))
x.a = -1
self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 7, 0, 0))
x.a, x.b = 0, -1
self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 7, 0))
x = X()
self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 0, 0))
x.a = -1
self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 7, 0, 0))
x.a, x.b = 0, -1
self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 7, 0))
def fail_fields(self, *fields):
return self.get_except(type(Structure), "X", (),
{"_fields_": fields})
for layout in "ms", "gcc-sysv":
with self.subTest(layout=layout):
return self.get_except(type(Structure), "X", (),
{"_fields_": fields, "layout": layout})
def test_nonint_types(self):
# bit fields are not allowed on non-integer types.
@@ -136,8 +212,16 @@ class BitFieldTest(unittest.TestCase):
result = self.fail_fields(("a", c_char, 1))
self.assertEqual(result, (TypeError, 'bit fields not allowed for type c_char'))
class Dummy(Structure):
class Empty(Structure):
_fields_ = []
self.check_struct(Empty)
result = self.fail_fields(("a", Empty, 1))
self.assertEqual(result, (ValueError, "number of bits invalid for bit field 'a'"))
class Dummy(Structure):
_fields_ = [("x", c_int)]
self.check_struct(Dummy)
result = self.fail_fields(("a", Dummy, 1))
self.assertEqual(result, (TypeError, 'bit fields not allowed for type Dummy'))
@@ -149,28 +233,37 @@ class BitFieldTest(unittest.TestCase):
def test_single_bitfield_size(self):
for c_typ in int_types:
result = self.fail_fields(("a", c_typ, -1))
self.assertEqual(result, (ValueError, 'number of bits invalid for bit field'))
with self.subTest(c_typ):
if sizeof(c_typ) != alignment(c_typ):
self.skipTest('assumes size=alignment')
result = self.fail_fields(("a", c_typ, -1))
self.assertEqual(result, (ValueError,
"number of bits invalid for bit field 'a'"))
result = self.fail_fields(("a", c_typ, 0))
self.assertEqual(result, (ValueError, 'number of bits invalid for bit field'))
result = self.fail_fields(("a", c_typ, 0))
self.assertEqual(result, (ValueError,
"number of bits invalid for bit field 'a'"))
class X(Structure):
_fields_ = [("a", c_typ, 1)]
self.assertEqual(sizeof(X), sizeof(c_typ))
class X(Structure):
_fields_ = [("a", c_typ, 1)]
self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_typ))
class X(Structure):
_fields_ = [("a", c_typ, sizeof(c_typ)*8)]
self.assertEqual(sizeof(X), sizeof(c_typ))
class X(Structure):
_fields_ = [("a", c_typ, sizeof(c_typ)*8)]
self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_typ))
result = self.fail_fields(("a", c_typ, sizeof(c_typ)*8 + 1))
self.assertEqual(result, (ValueError, 'number of bits invalid for bit field'))
result = self.fail_fields(("a", c_typ, sizeof(c_typ)*8 + 1))
self.assertEqual(result, (ValueError,
"number of bits invalid for bit field 'a'"))
def test_multi_bitfields_size(self):
class X(Structure):
_fields_ = [("a", c_short, 1),
("b", c_short, 14),
("c", c_short, 1)]
self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_short))
class X(Structure):
@@ -178,6 +271,7 @@ class BitFieldTest(unittest.TestCase):
("a1", c_short),
("b", c_short, 14),
("c", c_short, 1)]
self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_short)*3)
self.assertEqual(X.a.offset, 0)
self.assertEqual(X.a1.offset, sizeof(c_short))
@@ -188,6 +282,7 @@ class BitFieldTest(unittest.TestCase):
_fields_ = [("a", c_short, 3),
("b", c_short, 14),
("c", c_short, 14)]
self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_short)*3)
self.assertEqual(X.a.offset, sizeof(c_short)*0)
self.assertEqual(X.b.offset, sizeof(c_short)*1)
@@ -203,6 +298,7 @@ class BitFieldTest(unittest.TestCase):
class X(Structure):
_fields_ = [("a", c_byte, 4),
("b", c_int, 4)]
self.check_struct(X)
if os.name == "nt":
self.assertEqual(sizeof(X), sizeof(c_int)*2)
else:
@@ -212,12 +308,14 @@ class BitFieldTest(unittest.TestCase):
class X(Structure):
_fields_ = [("a", c_byte, 4),
("b", c_int, 32)]
self.check_struct(X)
self.assertEqual(sizeof(X), alignment(c_int)+sizeof(c_int))
def test_mixed_3(self):
class X(Structure):
_fields_ = [("a", c_byte, 4),
("b", c_ubyte, 4)]
self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_byte))
def test_mixed_4(self):
@@ -228,6 +326,7 @@ class BitFieldTest(unittest.TestCase):
("d", c_short, 4),
("e", c_short, 4),
("f", c_int, 24)]
self.check_struct(X)
# MSVC does NOT combine c_short and c_int into one field, GCC
# does (unless GCC is run with '-mms-bitfields' which
# produces code compatible with MSVC).
@@ -236,6 +335,177 @@ class BitFieldTest(unittest.TestCase):
else:
self.assertEqual(sizeof(X), sizeof(c_int) * 2)
def test_mixed_5(self):
class X(Structure):
_fields_ = [
('A', c_uint, 1),
('B', c_ushort, 16)]
self.check_struct(X)
a = X()
a.A = 0
a.B = 1
self.assertEqual(1, a.B)
def test_mixed_6(self):
class X(Structure):
_fields_ = [
('A', c_ulonglong, 1),
('B', c_uint, 32)]
self.check_struct(X)
a = X()
a.A = 0
a.B = 1
self.assertEqual(1, a.B)
@unittest.skipIf(sizeof(c_uint64) != alignment(c_uint64),
'assumes size=alignment')
def test_mixed_7(self):
class X(Structure):
_fields_ = [
("A", c_uint32),
('B', c_uint32, 20),
('C', c_uint64, 24)]
self.check_struct(X)
self.assertEqual(16, sizeof(X))
def test_mixed_8(self):
class Foo(Structure):
_fields_ = [
("A", c_uint32),
("B", c_uint32, 32),
("C", c_ulonglong, 1),
]
self.check_struct(Foo)
class Bar(Structure):
_fields_ = [
("A", c_uint32),
("B", c_uint32),
("C", c_ulonglong, 1),
]
self.check_struct(Bar)
self.assertEqual(sizeof(Foo), sizeof(Bar))
def test_mixed_9(self):
class X(Structure):
_fields_ = [
("A", c_uint8),
("B", c_uint32, 1),
]
self.check_struct(X)
if sys.platform == 'win32':
self.assertEqual(8, sizeof(X))
else:
self.assertEqual(4, sizeof(X))
@unittest.skipIf(sizeof(c_uint64) != alignment(c_uint64),
'assumes size=alignment')
def test_mixed_10(self):
class X(Structure):
_fields_ = [
("A", c_uint32, 1),
("B", c_uint64, 1),
]
self.check_struct(X)
if sys.platform == 'win32':
self.assertEqual(8, alignment(X))
self.assertEqual(16, sizeof(X))
else:
self.assertEqual(8, alignment(X))
self.assertEqual(8, sizeof(X))
def test_gh_95496(self):
for field_width in range(1, 33):
class TestStruct(Structure):
_fields_ = [
("Field1", c_uint32, field_width),
("Field2", c_uint8, 8)
]
self.check_struct(TestStruct)
cmd = TestStruct()
cmd.Field2 = 1
self.assertEqual(1, cmd.Field2)
def test_gh_84039(self):
class Bad(Structure):
_pack_ = 1
_layout_ = "ms"
_fields_ = [
("a0", c_uint8, 1),
("a1", c_uint8, 1),
("a2", c_uint8, 1),
("a3", c_uint8, 1),
("a4", c_uint8, 1),
("a5", c_uint8, 1),
("a6", c_uint8, 1),
("a7", c_uint8, 1),
("b0", c_uint16, 4),
("b1", c_uint16, 12),
]
class GoodA(Structure):
_pack_ = 1
_layout_ = "ms"
_fields_ = [
("a0", c_uint8, 1),
("a1", c_uint8, 1),
("a2", c_uint8, 1),
("a3", c_uint8, 1),
("a4", c_uint8, 1),
("a5", c_uint8, 1),
("a6", c_uint8, 1),
("a7", c_uint8, 1),
]
class Good(Structure):
_pack_ = 1
_layout_ = "ms"
_fields_ = [
("a", GoodA),
("b0", c_uint16, 4),
("b1", c_uint16, 12),
]
self.check_struct(Bad)
self.check_struct(GoodA)
self.check_struct(Good)
self.assertEqual(3, sizeof(Bad))
self.assertEqual(3, sizeof(Good))
def test_gh_73939(self):
class MyStructure(Structure):
_pack_ = 1
_layout_ = "ms"
_fields_ = [
("P", c_uint16),
("L", c_uint16, 9),
("Pro", c_uint16, 1),
("G", c_uint16, 1),
("IB", c_uint16, 1),
("IR", c_uint16, 1),
("R", c_uint16, 3),
("T", c_uint32, 10),
("C", c_uint32, 20),
("R2", c_uint32, 2)
]
self.check_struct(MyStructure)
self.assertEqual(8, sizeof(MyStructure))
def test_gh_86098(self):
class X(Structure):
_fields_ = [
("a", c_uint8, 8),
("b", c_uint8, 8),
("c", c_uint32, 16)
]
self.check_struct(X)
if sys.platform == 'win32':
self.assertEqual(8, sizeof(X))
else:
self.assertEqual(4, sizeof(X))
def test_anon_bitfields(self):
# anonymous bit-fields gave a strange error message
class X(Structure):
@@ -245,9 +515,13 @@ class BitFieldTest(unittest.TestCase):
_anonymous_ = ["_"]
_fields_ = [("_", X)]
self.check_struct(X)
self.check_struct(Y)
def test_uint32(self):
class X(Structure):
_fields_ = [("a", c_uint32, 32)]
self.check_struct(X)
x = X()
x.a = 10
self.assertEqual(x.a, 10)
@@ -257,6 +531,7 @@ class BitFieldTest(unittest.TestCase):
def test_uint64(self):
class X(Structure):
_fields_ = [("a", c_uint64, 64)]
self.check_struct(X)
x = X()
x.a = 10
self.assertEqual(x.a, 10)
@@ -269,6 +544,7 @@ class BitFieldTest(unittest.TestCase):
_fields_ = [("a", c_uint32, 24),
("b", c_uint32, 4),
("c", c_uint32, 4)]
self.check_struct(Little)
b = bytearray(4)
x = Little.from_buffer(b)
x.a = 0xabcdef
@@ -282,6 +558,7 @@ class BitFieldTest(unittest.TestCase):
_fields_ = [("a", c_uint32, 24),
("b", c_uint32, 4),
("c", c_uint32, 4)]
self.check_struct(Big)
b = bytearray(4)
x = Big.from_buffer(b)
x.a = 0xabcdef
@@ -289,6 +566,22 @@ class BitFieldTest(unittest.TestCase):
x.c = 2
self.assertEqual(b, b'\xab\xcd\xef\x12')
def test_union_bitfield(self):
class BitfieldUnion(Union):
_fields_ = [("a", c_uint32, 1),
("b", c_uint32, 2),
("c", c_uint32, 3)]
self.check_union(BitfieldUnion)
self.assertEqual(sizeof(BitfieldUnion), 4)
b = bytearray(4)
x = BitfieldUnion.from_buffer(b)
x.a = 1
self.assertEqual(int.from_bytes(b).bit_count(), 1)
x.b = 3
self.assertEqual(int.from_bytes(b).bit_count(), 2)
x.c = 7
self.assertEqual(int.from_bytes(b).bit_count(), 3)
if __name__ == "__main__":
unittest.main()

View File

@@ -3,9 +3,10 @@ import sys
import unittest
from _ctypes import _SimpleCData
from ctypes import Structure, c_char, c_char_p, c_wchar, c_wchar_p
from ._support import StructCheckMixin
class BytesTest(unittest.TestCase):
class BytesTest(unittest.TestCase, StructCheckMixin):
def test_c_char(self):
x = c_char(b"x")
self.assertRaises(TypeError, c_char, "x")
@@ -40,6 +41,7 @@ class BytesTest(unittest.TestCase):
def test_struct(self):
class X(Structure):
_fields_ = [("a", c_char * 3)]
self.check_struct(X)
x = X(b"abc")
self.assertRaises(TypeError, X, "abc")
@@ -49,6 +51,7 @@ class BytesTest(unittest.TestCase):
def test_struct_W(self):
class X(Structure):
_fields_ = [("a", c_wchar * 3)]
self.check_struct(X)
x = X("abc")
self.assertRaises(TypeError, X, b"abc")

View File

@@ -11,6 +11,7 @@ from ctypes import (Structure, Union, LittleEndianUnion, BigEndianUnion,
c_short, c_ushort, c_int, c_uint,
c_long, c_ulong, c_longlong, c_ulonglong,
c_uint32, c_float, c_double)
from ._support import StructCheckMixin
def bin(s):
@@ -24,15 +25,17 @@ def bin(s):
#
# For Structures and Unions, these types are created on demand.
class Test(unittest.TestCase):
class Test(unittest.TestCase, StructCheckMixin):
def test_slots(self):
class BigPoint(BigEndianStructure):
__slots__ = ()
_fields_ = [("x", c_int), ("y", c_int)]
self.check_struct(BigPoint)
class LowPoint(LittleEndianStructure):
__slots__ = ()
_fields_ = [("x", c_int), ("y", c_int)]
self.check_struct(LowPoint)
big = BigPoint()
little = LowPoint()
@@ -200,6 +203,7 @@ class Test(unittest.TestCase):
with self.assertRaises(TypeError):
class T(BigEndianStructure if sys.byteorder == "little" else LittleEndianStructure):
_fields_ = fields + [("x", typ)]
self.check_struct(T)
def test_struct_struct(self):
@@ -219,14 +223,15 @@ class Test(unittest.TestCase):
class NestedStructure(nested):
_fields_ = [("x", c_uint32),
("y", c_uint32)]
self.check_struct(NestedStructure)
class TestStructure(parent):
_fields_ = [("point", NestedStructure)]
self.check_struct(TestStructure)
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)
@@ -248,6 +253,7 @@ class Test(unittest.TestCase):
("h", c_short),
("i", c_int),
("d", c_double)]
self.check_struct(S)
s1 = S(0x12, 0x1234, 0x12345678, 3.14)
s2 = struct.pack(fmt, 0x12, 0x1234, 0x12345678, 3.14)
@@ -263,6 +269,7 @@ class Test(unittest.TestCase):
class S(base):
_pack_ = 1
_layout_ = "ms"
_fields_ = [("b", c_byte),
("h", c_short),
@@ -271,6 +278,7 @@ class Test(unittest.TestCase):
("_2", c_byte),
("d", c_double)]
self.check_struct(S)
s1 = S()
s1.b = 0x12
@@ -289,6 +297,7 @@ class Test(unittest.TestCase):
class S(Structure):
_pack_ = 1
_layout_ = "ms"
_fields_ = [("b", c_byte),
("h", c_short),
@@ -298,6 +307,7 @@ class Test(unittest.TestCase):
("_2", c_byte),
("d", c_double)]
self.check_struct(S)
s1 = S()
s1.b = 0x12
@@ -334,6 +344,7 @@ class Test(unittest.TestCase):
with self.assertRaises(TypeError):
class T(BigEndianUnion if sys.byteorder == "little" else LittleEndianUnion):
_fields_ = fields + [("x", typ)]
self.check_union(T)
def test_union_struct(self):
# nested structures in unions with different byteorders
@@ -352,14 +363,15 @@ class Test(unittest.TestCase):
class NestedStructure(nested):
_fields_ = [("x", c_uint32),
("y", c_uint32)]
self.check_struct(NestedStructure)
class TestUnion(parent):
_fields_ = [("point", NestedStructure)]
self.check_union(TestUnion)
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)
@@ -374,12 +386,15 @@ class Test(unittest.TestCase):
class S1(_Structure):
_fields_ = [("a", c_byte), ("b", c_byte)]
self.check_struct(S1)
class U1(_Union):
_fields_ = [("s1", S1), ("ab", c_short)]
self.check_union(U1)
class S2(_Structure):
_fields_ = [("u1", U1), ("c", c_byte)]
self.check_struct(S2)
if __name__ == "__main__":

View File

@@ -1,15 +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
def set_non_ctypes_pointer_type(cls, pointer_type):
cls.__pointer_type__ = pointer_type
class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
def tearDown(self):
# to not leak references, we must clean _pointer_type_cache
ctypes._reset_cache()
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.
@@ -35,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):
@@ -44,19 +44,35 @@ 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.assertTrue(issubclass(POINTER(Sub2), Sub2))
self.assertTrue(issubclass(POINTER(Sub2), POINTER(Sub)))
self.assertTrue(issubclass(POINTER(Sub), POINTER(CtBase)))
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`
@@ -68,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):
@@ -77,14 +93,26 @@ 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.assertTrue(issubclass(POINTER(Sub), Sub))
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):
@@ -102,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
@@ -110,19 +138,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.assertTrue(issubclass(POINTER(Sub2), Sub2))
self.assertTrue(issubclass(POINTER(Sub2), POINTER(Sub)))
self.assertTrue(issubclass(POINTER(Sub), POINTER(CtBase)))
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):
@@ -134,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
@@ -145,8 +190,195 @@ 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.assertTrue(issubclass(POINTER(Sub), Sub))
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
# set of types as well as correct construction of the string.)
with self.assertRaises(AttributeError) as cm:
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')
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))

View File

@@ -324,7 +324,7 @@ class SampleCallbacksTestCase(unittest.TestCase):
self.assertIsInstance(cm.unraisable.exc_value, TypeError)
self.assertEqual(cm.unraisable.err_msg,
f"Exception ignored on converting result "
f"Exception ignored while converting result "
f"of ctypes callback function {func!r}")
self.assertIsNone(cm.unraisable.object)

View File

@@ -32,8 +32,6 @@ class Test(unittest.TestCase):
ptr = cast(address, POINTER(c_int))
self.assertEqual([ptr[i] for i in range(3)], [42, 17, 2])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_p2a_objects(self):
array = (c_char_p * 5)()
self.assertEqual(array._objects, None)

View File

@@ -5,7 +5,8 @@ from ctypes import (CDLL,
c_short, c_ushort, c_int, c_uint,
c_long, c_ulong, c_longlong, c_ulonglong,
c_float, c_double, c_longdouble)
from test.support import import_helper
from test import support
from test.support import import_helper, threading_helper
_ctypes_test = import_helper.import_module("_ctypes_test")
@@ -13,9 +14,10 @@ class CFunctions(unittest.TestCase):
_dll = CDLL(_ctypes_test.__file__)
def S(self):
return c_longlong.in_dll(self._dll, "last_tf_arg_s").value
return _ctypes_test.get_last_tf_arg_s()
def U(self):
return c_ulonglong.in_dll(self._dll, "last_tf_arg_u").value
return _ctypes_test.get_last_tf_arg_u()
def test_byte(self):
self._dll.tf_b.restype = c_byte
@@ -191,6 +193,23 @@ class CFunctions(unittest.TestCase):
self.assertEqual(self._dll.tv_i(-42), None)
self.assertEqual(self.S(), -42)
@threading_helper.requires_working_threading()
@support.requires_resource("cpu")
@unittest.skipUnless(support.Py_GIL_DISABLED, "only meaningful on free-threading")
def test_thread_safety(self):
from threading import Thread
def concurrent():
for _ in range(100):
self._dll.tf_b.restype = c_byte
self._dll.tf_b.argtypes = (c_byte,)
with threading_helper.catch_threading_exception() as exc:
with threading_helper.start_threads((Thread(target=concurrent) for _ in range(10))):
pass
self.assertIsNone(exc.exc_value)
# The following repeats the above tests with stdcall functions (where
# they are available)

View File

@@ -32,6 +32,7 @@ void *foo(void)
@unittest.skipUnless(sys.platform.startswith('linux'),
'test requires GNU IFUNC support')
@unittest.skipIf(test.support.linked_to_musl(), "Requires glibc")
class TestNullDlsym(unittest.TestCase):
"""GH-126554: Ensure that we catch NULL dlsym return values
@@ -54,7 +55,8 @@ class TestNullDlsym(unittest.TestCase):
this 'dlsym returned NULL -> throw Error' rule.
"""
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_null_dlsym(self):
import subprocess
import tempfile
@@ -121,7 +123,7 @@ class TestNullDlsym(unittest.TestCase):
# Assert that the IFUNC was called
self.assertEqual(os.read(pipe_r, 2), b'OK')
@test.support.thread_unsafe('setlocale is not thread-safe')
@unittest.skipUnless(os.name != 'nt', 'test requires dlerror() calls')
class TestLocalization(unittest.TestCase):

63
Lib/test/test_ctypes/test_dllist.py vendored Normal file
View File

@@ -0,0 +1,63 @@
import os
import sys
import unittest
from ctypes import CDLL
import ctypes.util
from test.support import import_helper
WINDOWS = os.name == "nt"
APPLE = sys.platform in {"darwin", "ios", "tvos", "watchos"}
if WINDOWS:
KNOWN_LIBRARIES = ["KERNEL32.DLL"]
elif APPLE:
KNOWN_LIBRARIES = ["libSystem.B.dylib"]
else:
# trickier than it seems, because libc may not be present
# on musl systems, and sometimes goes by different names.
# However, ctypes itself loads libffi
KNOWN_LIBRARIES = ["libc.so", "libffi.so"]
@unittest.skipUnless(
hasattr(ctypes.util, "dllist"),
"ctypes.util.dllist is not available on this platform",
)
class ListSharedLibraries(unittest.TestCase):
# TODO: RUSTPYTHON
@unittest.skipIf(not APPLE, "TODO: RUSTPYTHON")
def test_lists_system(self):
dlls = ctypes.util.dllist()
self.assertGreater(len(dlls), 0, f"loaded={dlls}")
self.assertTrue(
any(lib in dll for dll in dlls for lib in KNOWN_LIBRARIES), f"loaded={dlls}"
)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_lists_updates(self):
dlls = ctypes.util.dllist()
# this test relies on being able to import a library which is
# not already loaded.
# If it is (e.g. by a previous test in the same process), we skip
if any("_ctypes_test" in dll for dll in dlls):
self.skipTest("Test library is already loaded")
_ctypes_test = import_helper.import_module("_ctypes_test")
test_module = CDLL(_ctypes_test.__file__)
dlls2 = ctypes.util.dllist()
self.assertIsNotNone(dlls2)
dlls1 = set(dlls)
dlls2 = set(dlls2)
self.assertGreater(dlls2, dlls1, f"newly loaded libraries: {dlls2 - dlls1}")
self.assertTrue(any("_ctypes_test" in dll for dll in dlls2), f"loaded={dlls2}")
if __name__ == "__main__":
unittest.main()

View File

@@ -5,7 +5,7 @@ import unittest
import unittest.mock
from ctypes import CDLL, RTLD_GLOBAL
from ctypes.util import find_library
from test.support import os_helper
from test.support import os_helper, thread_unsafe
# On some systems, loading the OpenGL libraries needs the RTLD_GLOBAL mode.
@@ -78,6 +78,7 @@ class Test_OpenGL_libs(unittest.TestCase):
@unittest.skipUnless(sys.platform.startswith('linux'),
'Test only valid for Linux')
class FindLibraryLinux(unittest.TestCase):
@thread_unsafe('uses setenv')
def test_find_on_libpath(self):
import subprocess
import tempfile
@@ -152,5 +153,73 @@ class FindLibraryAndroid(unittest.TestCase):
self.assertIsNone(find_library(name))
@unittest.skipUnless(test.support.is_emscripten,
'Test only valid for Emscripten')
class FindLibraryEmscripten(unittest.TestCase):
@classmethod
def setUpClass(cls):
import tempfile
# A very simple wasm module
# In WAT format: (module)
cls.wasm_module = b'\x00asm\x01\x00\x00\x00\x00\x08\x04name\x02\x01\x00'
cls.non_wasm_content = b'This is not a WASM file'
cls.temp_dir = tempfile.mkdtemp()
cls.libdummy_so_path = os.path.join(cls.temp_dir, 'libdummy.so')
with open(cls.libdummy_so_path, 'wb') as f:
f.write(cls.wasm_module)
cls.libother_wasm_path = os.path.join(cls.temp_dir, 'libother.wasm')
with open(cls.libother_wasm_path, 'wb') as f:
f.write(cls.wasm_module)
cls.libnowasm_so_path = os.path.join(cls.temp_dir, 'libnowasm.so')
with open(cls.libnowasm_so_path, 'wb') as f:
f.write(cls.non_wasm_content)
@classmethod
def tearDownClass(cls):
import shutil
shutil.rmtree(cls.temp_dir)
def test_find_wasm_file_with_so_extension(self):
with os_helper.EnvironmentVarGuard() as env:
env.set('LD_LIBRARY_PATH', self.temp_dir)
result = find_library('dummy')
self.assertEqual(result, self.libdummy_so_path)
def test_find_wasm_file_with_wasm_extension(self):
with os_helper.EnvironmentVarGuard() as env:
env.set('LD_LIBRARY_PATH', self.temp_dir)
result = find_library('other')
self.assertEqual(result, self.libother_wasm_path)
def test_ignore_non_wasm_file(self):
with os_helper.EnvironmentVarGuard() as env:
env.set('LD_LIBRARY_PATH', self.temp_dir)
result = find_library('nowasm')
self.assertIsNone(result)
def test_find_nothing_without_ld_library_path(self):
with os_helper.EnvironmentVarGuard() as env:
if 'LD_LIBRARY_PATH' in env:
del env['LD_LIBRARY_PATH']
result = find_library('dummy')
self.assertIsNone(result)
result = find_library('other')
self.assertIsNone(result)
def test_find_nothing_with_wrong_ld_library_path(self):
import tempfile
with tempfile.TemporaryDirectory() as empty_dir:
with os_helper.EnvironmentVarGuard() as env:
env.set('LD_LIBRARY_PATH', empty_dir)
result = find_library('dummy')
self.assertIsNone(result)
result = find_library('other')
self.assertIsNone(result)
if __name__ == "__main__":
unittest.main()

View File

@@ -5,7 +5,7 @@ from ctypes import (CDLL, Structure, CFUNCTYPE, sizeof, _CFuncPtr,
from test.support import import_helper
_ctypes_test = import_helper.import_module("_ctypes_test")
from ._support import (_CData, PyCFuncPtrType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)
Py_TPFLAGS_IMMUTABLETYPE, StructCheckMixin)
try:
@@ -17,7 +17,7 @@ except AttributeError:
lib = CDLL(_ctypes_test.__file__)
class CFuncPtrTestCase(unittest.TestCase):
class CFuncPtrTestCase(unittest.TestCase, StructCheckMixin):
def test_inheritance_hierarchy(self):
self.assertEqual(_CFuncPtr.mro(), [_CFuncPtr, _CData, object])
@@ -88,6 +88,7 @@ class CFuncPtrTestCase(unittest.TestCase):
("hCursor", HCURSOR),
("lpszMenuName", LPCTSTR),
("lpszClassName", LPCTSTR)]
self.check_struct(WNDCLASS)
wndclass = WNDCLASS()
wndclass.lpfnWndProc = WNDPROC(wndproc)

View File

@@ -2,7 +2,7 @@ import ctypes
import sys
import unittest
from ctypes import (CDLL, Structure, Array, CFUNCTYPE,
byref, POINTER, pointer, ArgumentError,
byref, POINTER, pointer, ArgumentError, sizeof,
c_char, c_wchar, c_byte, c_char_p, c_wchar_p,
c_short, c_int, c_long, c_longlong, c_void_p,
c_float, c_double, c_longdouble)
@@ -72,7 +72,8 @@ class FunctionTestCase(unittest.TestCase):
self.assertEqual(str(cm.exception),
"argument 1: TypeError: one character bytes, "
"bytearray or integer expected")
"bytearray, or an integer in range(256) expected, "
"not bytes of length 3")
def test_wchar_parm(self):
f = dll._testfunc_i_bhilfd
@@ -84,14 +85,27 @@ class FunctionTestCase(unittest.TestCase):
with self.assertRaises(ArgumentError) as cm:
f(1, 2, 3, 4, 5.0, 6.0)
self.assertEqual(str(cm.exception),
"argument 2: TypeError: unicode string expected "
"instead of int instance")
"argument 2: TypeError: a unicode character expected, "
"not instance of int")
with self.assertRaises(ArgumentError) as cm:
f(1, "abc", 3, 4, 5.0, 6.0)
self.assertEqual(str(cm.exception),
"argument 2: TypeError: one character unicode string "
"expected")
"argument 2: TypeError: a unicode character expected, "
"not a string of length 3")
with self.assertRaises(ArgumentError) as cm:
f(1, "", 3, 4, 5.0, 6.0)
self.assertEqual(str(cm.exception),
"argument 2: TypeError: a unicode character expected, "
"not a string of length 0")
if sizeof(c_wchar) < 4:
with self.assertRaises(ArgumentError) as cm:
f(1, "\U0001f40d", 3, 4, 5.0, 6.0)
self.assertEqual(str(cm.exception),
"argument 2: TypeError: the string '\\U0001f40d' "
"cannot be converted to a single wchar_t character")
def test_c_char_p_parm(self):
"""Test the error message when converting an incompatible type to c_char_p."""

View File

@@ -0,0 +1,761 @@
"""Test CTypes structs, unions, bitfields against C equivalents.
The types here are auto-converted to C source at
`Modules/_ctypes/_ctypes_test_generated.c.h`, which is compiled into
_ctypes_test.
Run this module to regenerate the files:
./python Lib/test/test_ctypes/test_generated_structs.py > Modules/_ctypes/_ctypes_test_generated.c.h
"""
import unittest
from test.support import import_helper, verbose
import re
from dataclasses import dataclass
from functools import cached_property
import sys
import ctypes
from ctypes import Structure, Union
from ctypes import sizeof, alignment, pointer, string_at
_ctypes_test = import_helper.import_module("_ctypes_test")
from test.test_ctypes._support import StructCheckMixin
# A 64-bit number where each nibble (hex digit) is different and
# has 2-3 bits set.
TEST_PATTERN = 0xae7596db
# ctypes erases the difference between `c_int` and e.g.`c_int16`.
# To keep it, we'll use custom subclasses with the C name stashed in `_c_name`:
class c_bool(ctypes.c_bool):
_c_name = '_Bool'
# To do it for all the other types, use some metaprogramming:
for c_name, ctypes_name in {
'signed char': 'c_byte',
'short': 'c_short',
'int': 'c_int',
'long': 'c_long',
'long long': 'c_longlong',
'unsigned char': 'c_ubyte',
'unsigned short': 'c_ushort',
'unsigned int': 'c_uint',
'unsigned long': 'c_ulong',
'unsigned long long': 'c_ulonglong',
**{f'{u}int{n}_t': f'c_{u}int{n}'
for u in ('', 'u')
for n in (8, 16, 32, 64)}
}.items():
ctype = getattr(ctypes, ctypes_name)
newtype = type(ctypes_name, (ctype,), {'_c_name': c_name})
globals()[ctypes_name] = newtype
# Register structs and unions to test
TESTCASES = {}
def register(name=None, set_name=False):
def decorator(cls, name=name):
if name is None:
name = cls.__name__
assert name.isascii() # will be used in _PyUnicode_EqualToASCIIString
assert name.isidentifier() # will be used as a C identifier
assert name not in TESTCASES
TESTCASES[name] = cls
if set_name:
cls.__name__ = name
return cls
return decorator
@register()
class SingleInt(Structure):
_fields_ = [('a', c_int)]
@register()
class SingleInt_Union(Union):
_fields_ = [('a', c_int)]
@register()
class SingleU32(Structure):
_fields_ = [('a', c_uint32)]
@register()
class SimpleStruct(Structure):
_fields_ = [('x', c_int32), ('y', c_int8), ('z', c_uint16)]
@register()
class SimpleUnion(Union):
_fields_ = [('x', c_int32), ('y', c_int8), ('z', c_uint16)]
@register()
class ManyTypes(Structure):
_fields_ = [
('i8', c_int8), ('u8', c_uint8),
('i16', c_int16), ('u16', c_uint16),
('i32', c_int32), ('u32', c_uint32),
('i64', c_int64), ('u64', c_uint64),
]
@register()
class ManyTypesU(Union):
_fields_ = [
('i8', c_int8), ('u8', c_uint8),
('i16', c_int16), ('u16', c_uint16),
('i32', c_int32), ('u32', c_uint32),
('i64', c_int64), ('u64', c_uint64),
]
@register()
class Nested(Structure):
_fields_ = [
('a', SimpleStruct), ('b', SimpleUnion), ('anon', SimpleStruct),
]
_anonymous_ = ['anon']
@register()
class Packed1(Structure):
_fields_ = [('a', c_int8), ('b', c_int64)]
_pack_ = 1
_layout_ = 'ms'
@register()
class Packed2(Structure):
_fields_ = [('a', c_int8), ('b', c_int64)]
_pack_ = 2
_layout_ = 'ms'
@register()
class Packed3(Structure):
_fields_ = [('a', c_int8), ('b', c_int64)]
_pack_ = 4
_layout_ = 'ms'
@register()
class Packed4(Structure):
def _maybe_skip():
# `_pack_` enables MSVC-style packing, but keeps platform-specific
# alignments.
# The C code we generate for GCC/clang currently uses
# `__attribute__((ms_struct))`, which activates MSVC layout *and*
# alignments, that is, sizeof(basic type) == alignment(basic type).
# On a Pentium, int64 is 32-bit aligned, so the two won't match.
# The expected behavior is instead tested in
# StructureTestCase.test_packed, over in test_structures.py.
if sizeof(c_int64) != alignment(c_int64):
raise unittest.SkipTest('cannot test on this platform')
_fields_ = [('a', c_int8), ('b', c_int64)]
_pack_ = 8
_layout_ = 'ms'
@register()
class X86_32EdgeCase(Structure):
# On a Pentium, long long (int64) is 32-bit aligned,
# so these are packed tightly.
_fields_ = [('a', c_int32), ('b', c_int64), ('c', c_int32)]
@register()
class MSBitFieldExample(Structure):
# From https://learn.microsoft.com/en-us/cpp/c-language/c-bit-fields
_fields_ = [
('a', c_uint, 4),
('b', c_uint, 5),
('c', c_uint, 7)]
@register()
class MSStraddlingExample(Structure):
# From https://learn.microsoft.com/en-us/cpp/c-language/c-bit-fields
_fields_ = [
('first', c_uint, 9),
('second', c_uint, 7),
('may_straddle', c_uint, 30),
('last', c_uint, 18)]
@register()
class IntBits(Structure):
_fields_ = [("A", c_int, 1),
("B", c_int, 2),
("C", c_int, 3),
("D", c_int, 4),
("E", c_int, 5),
("F", c_int, 6),
("G", c_int, 7),
("H", c_int, 8),
("I", c_int, 9)]
@register()
class Bits(Structure):
_fields_ = [*IntBits._fields_,
("M", c_short, 1),
("N", c_short, 2),
("O", c_short, 3),
("P", c_short, 4),
("Q", c_short, 5),
("R", c_short, 6),
("S", c_short, 7)]
@register()
class IntBits_MSVC(Structure):
_layout_ = "ms"
_fields_ = [("A", c_int, 1),
("B", c_int, 2),
("C", c_int, 3),
("D", c_int, 4),
("E", c_int, 5),
("F", c_int, 6),
("G", c_int, 7),
("H", c_int, 8),
("I", c_int, 9)]
@register()
class Bits_MSVC(Structure):
_layout_ = "ms"
_fields_ = [*IntBits_MSVC._fields_,
("M", c_short, 1),
("N", c_short, 2),
("O", c_short, 3),
("P", c_short, 4),
("Q", c_short, 5),
("R", c_short, 6),
("S", c_short, 7)]
# Skipped for now -- we don't always match the alignment
#@register()
class IntBits_Union(Union):
_fields_ = [("A", c_int, 1),
("B", c_int, 2),
("C", c_int, 3),
("D", c_int, 4),
("E", c_int, 5),
("F", c_int, 6),
("G", c_int, 7),
("H", c_int, 8),
("I", c_int, 9)]
# Skipped for now -- we don't always match the alignment
#@register()
class BitsUnion(Union):
_fields_ = [*IntBits_Union._fields_,
("M", c_short, 1),
("N", c_short, 2),
("O", c_short, 3),
("P", c_short, 4),
("Q", c_short, 5),
("R", c_short, 6),
("S", c_short, 7)]
@register()
class I64Bits(Structure):
_fields_ = [("a", c_int64, 1),
("b", c_int64, 62),
("c", c_int64, 1)]
@register()
class U64Bits(Structure):
_fields_ = [("a", c_uint64, 1),
("b", c_uint64, 62),
("c", c_uint64, 1)]
for n in 8, 16, 32, 64:
for signedness in '', 'u':
ctype = globals()[f'c_{signedness}int{n}']
@register(f'Struct331_{signedness}{n}', set_name=True)
class _cls(Structure):
_fields_ = [("a", ctype, 3),
("b", ctype, 3),
("c", ctype, 1)]
@register(f'Struct1x1_{signedness}{n}', set_name=True)
class _cls(Structure):
_fields_ = [("a", ctype, 1),
("b", ctype, n-2),
("c", ctype, 1)]
@register(f'Struct1nx1_{signedness}{n}', set_name=True)
class _cls(Structure):
_fields_ = [("a", ctype, 1),
("full", ctype),
("b", ctype, n-2),
("c", ctype, 1)]
@register(f'Struct3xx_{signedness}{n}', set_name=True)
class _cls(Structure):
_fields_ = [("a", ctype, 3),
("b", ctype, n-2),
("c", ctype, n-2)]
@register()
class Mixed1(Structure):
_fields_ = [("a", c_byte, 4),
("b", c_int, 4)]
@register()
class Mixed2(Structure):
_fields_ = [("a", c_byte, 4),
("b", c_int32, 32)]
@register()
class Mixed3(Structure):
_fields_ = [("a", c_byte, 4),
("b", c_ubyte, 4)]
@register()
class Mixed4(Structure):
_fields_ = [("a", c_short, 4),
("b", c_short, 4),
("c", c_int, 24),
("d", c_short, 4),
("e", c_short, 4),
("f", c_int, 24)]
@register()
class Mixed5(Structure):
_fields_ = [('A', c_uint, 1),
('B', c_ushort, 16)]
@register()
class Mixed6(Structure):
_fields_ = [('A', c_ulonglong, 1),
('B', c_uint, 32)]
@register()
class Mixed7(Structure):
_fields_ = [("A", c_uint32),
('B', c_uint32, 20),
('C', c_uint64, 24)]
@register()
class Mixed8_a(Structure):
_fields_ = [("A", c_uint32),
("B", c_uint32, 32),
("C", c_ulonglong, 1)]
@register()
class Mixed8_b(Structure):
_fields_ = [("A", c_uint32),
("B", c_uint32),
("C", c_ulonglong, 1)]
@register()
class Mixed9(Structure):
_fields_ = [("A", c_uint8),
("B", c_uint32, 1)]
@register()
class Mixed10(Structure):
_fields_ = [("A", c_uint32, 1),
("B", c_uint64, 1)]
@register()
class Example_gh_95496(Structure):
_fields_ = [("A", c_uint32, 1),
("B", c_uint64, 1)]
@register()
class Example_gh_84039_bad(Structure):
_pack_ = 1
_layout_ = 'ms'
_fields_ = [("a0", c_uint8, 1),
("a1", c_uint8, 1),
("a2", c_uint8, 1),
("a3", c_uint8, 1),
("a4", c_uint8, 1),
("a5", c_uint8, 1),
("a6", c_uint8, 1),
("a7", c_uint8, 1),
("b0", c_uint16, 4),
("b1", c_uint16, 12)]
@register()
class Example_gh_84039_good_a(Structure):
_pack_ = 1
_layout_ = 'ms'
_fields_ = [("a0", c_uint8, 1),
("a1", c_uint8, 1),
("a2", c_uint8, 1),
("a3", c_uint8, 1),
("a4", c_uint8, 1),
("a5", c_uint8, 1),
("a6", c_uint8, 1),
("a7", c_uint8, 1)]
@register()
class Example_gh_84039_good(Structure):
_pack_ = 1
_layout_ = 'ms'
_fields_ = [("a", Example_gh_84039_good_a),
("b0", c_uint16, 4),
("b1", c_uint16, 12)]
@register()
class Example_gh_73939(Structure):
_pack_ = 1
_layout_ = 'ms'
_fields_ = [("P", c_uint16),
("L", c_uint16, 9),
("Pro", c_uint16, 1),
("G", c_uint16, 1),
("IB", c_uint16, 1),
("IR", c_uint16, 1),
("R", c_uint16, 3),
("T", c_uint32, 10),
("C", c_uint32, 20),
("R2", c_uint32, 2)]
@register()
class Example_gh_86098(Structure):
_fields_ = [("a", c_uint8, 8),
("b", c_uint8, 8),
("c", c_uint32, 16)]
@register()
class Example_gh_86098_pack(Structure):
_pack_ = 1
_layout_ = 'ms'
_fields_ = [("a", c_uint8, 8),
("b", c_uint8, 8),
("c", c_uint32, 16)]
@register()
class AnonBitfields(Structure):
class X(Structure):
_fields_ = [("a", c_byte, 4),
("b", c_ubyte, 4)]
_anonymous_ = ["_"]
_fields_ = [("_", X), ('y', c_byte)]
class GeneratedTest(unittest.TestCase, StructCheckMixin):
def test_generated_data(self):
"""Check that a ctypes struct/union matches its C equivalent.
This compares with data from get_generated_test_data(), a list of:
- name (str)
- size (int)
- alignment (int)
- for each field, three snapshots of memory, as bytes:
- memory after the field is set to -1
- memory after the field is set to 1
- memory after the field is set to 0
or:
- None
- reason to skip the test (str)
This does depend on the C compiler keeping padding bits unchanged.
Common compilers seem to do so.
"""
for name, cls in TESTCASES.items():
with self.subTest(name=name):
self.check_struct_or_union(cls)
if _maybe_skip := getattr(cls, '_maybe_skip', None):
_maybe_skip()
expected = iter(_ctypes_test.get_generated_test_data(name))
expected_name = next(expected)
if expected_name is None:
self.skipTest(next(expected))
self.assertEqual(name, expected_name)
self.assertEqual(sizeof(cls), next(expected))
with self.subTest('alignment'):
self.assertEqual(alignment(cls), next(expected))
obj = cls()
ptr = pointer(obj)
for field in iterfields(cls):
for value in -1, 1, TEST_PATTERN, 0:
with self.subTest(field=field.full_name, value=value):
field.set_to(obj, value)
py_mem = string_at(ptr, sizeof(obj))
c_mem = next(expected)
if py_mem != c_mem:
# Generate a helpful failure message
lines, requires = dump_ctype(cls)
m = "\n".join([str(field), 'in:', *lines])
self.assertEqual(py_mem.hex(), c_mem.hex(), m)
descriptor = field.descriptor
field_mem = py_mem[
field.byte_offset
: field.byte_offset + descriptor.byte_size]
field_int = int.from_bytes(field_mem, sys.byteorder)
mask = (1 << descriptor.bit_size) - 1
self.assertEqual(
(field_int >> descriptor.bit_offset) & mask,
value & mask)
# The rest of this file is generating C code from a ctypes type.
# This is only meant for (and tested with) the known inputs in this file!
def c_str_repr(string):
"""Return a string as a C literal"""
return '"' + re.sub('([\"\'\\\\\n])', r'\\\1', string) + '"'
def dump_simple_ctype(tp, variable_name='', semi=''):
"""Get C type name or declaration of a scalar type
variable_name: if given, declare the given variable
semi: a semicolon, and/or bitfield specification to tack on to the end
"""
length = getattr(tp, '_length_', None)
if length is not None:
return f'{dump_simple_ctype(tp._type_, variable_name)}[{length}]{semi}'
assert not issubclass(tp, (Structure, Union))
return f'{tp._c_name}{maybe_space(variable_name)}{semi}'
def dump_ctype(tp, struct_or_union_tag='', variable_name='', semi=''):
"""Get C type name or declaration of a ctype
struct_or_union_tag: name of the struct or union
variable_name: if given, declare the given variable
semi: a semicolon, and/or bitfield specification to tack on to the end
"""
requires = set()
if issubclass(tp, (Structure, Union)):
attributes = []
pushes = []
pops = []
pack = getattr(tp, '_pack_', None)
if pack is not None:
pushes.append(f'#pragma pack(push, {pack})')
pops.append(f'#pragma pack(pop)')
layout = getattr(tp, '_layout_', None)
if layout == 'ms':
# The 'ms_struct' attribute only works on x86 and PowerPC
requires.add(
'defined(MS_WIN32) || ('
'(defined(__x86_64__) || defined(__i386__) || defined(__ppc64__)) && ('
'defined(__GNUC__) || defined(__clang__)))'
)
attributes.append('ms_struct')
if attributes:
a = f' GCC_ATTR({", ".join(attributes)})'
else:
a = ''
lines = [f'{struct_or_union(tp)}{a}{maybe_space(struct_or_union_tag)} ' +'{']
for fielddesc in tp._fields_:
f_name, f_tp, f_bits = unpack_field_desc(*fielddesc)
if f_name in getattr(tp, '_anonymous_', ()):
f_name = ''
if f_bits is None:
subsemi = ';'
else:
if f_tp not in (c_int, c_uint):
# XLC can reportedly only handle int & unsigned int
# bitfields (the only types required by C spec)
requires.add('!defined(__xlc__)')
subsemi = f' :{f_bits};'
sub_lines, sub_requires = dump_ctype(
f_tp, variable_name=f_name, semi=subsemi)
requires.update(sub_requires)
for line in sub_lines:
lines.append(' ' + line)
lines.append(f'}}{maybe_space(variable_name)}{semi}')
return [*pushes, *lines, *reversed(pops)], requires
else:
return [dump_simple_ctype(tp, variable_name, semi)], requires
def struct_or_union(cls):
if issubclass(cls, Structure):
return 'struct'
if issubclass(cls, Union):
return 'union'
raise TypeError(cls)
def maybe_space(string):
if string:
return ' ' + string
return string
def unpack_field_desc(f_name, f_tp, f_bits=None):
"""Unpack a _fields_ entry into a (name, type, bits) triple"""
return f_name, f_tp, f_bits
@dataclass
class FieldInfo:
"""Information about a (possibly nested) struct/union field"""
name: str
tp: type
bits: int | None # number if this is a bit field
parent_type: type
parent: 'FieldInfo' #| None
descriptor: object
byte_offset: int
@cached_property
def attr_path(self):
"""Attribute names to get at the value of this field"""
if self.name in getattr(self.parent_type, '_anonymous_', ()):
selfpath = ()
else:
selfpath = (self.name,)
if self.parent:
return (*self.parent.attr_path, *selfpath)
else:
return selfpath
@cached_property
def full_name(self):
"""Attribute names to get at the value of this field"""
return '.'.join(self.attr_path)
def set_to(self, obj, new):
"""Set the field on a given Structure/Union instance"""
for attr_name in self.attr_path[:-1]:
obj = getattr(obj, attr_name)
setattr(obj, self.attr_path[-1], new)
@cached_property
def root(self):
if self.parent is None:
return self
else:
return self.parent
def __repr__(self):
qname = f'{self.root.parent_type.__name__}.{self.full_name}'
try:
desc = self.descriptor
except AttributeError:
desc = '???'
return f'<{type(self).__name__} for {qname}: {desc}>'
def iterfields(tp, parent=None):
"""Get *leaf* fields of a structure or union, as FieldInfo"""
try:
fields = tp._fields_
except AttributeError:
yield parent
else:
for fielddesc in fields:
f_name, f_tp, f_bits = unpack_field_desc(*fielddesc)
descriptor = getattr(tp, f_name)
byte_offset = descriptor.byte_offset
if parent:
byte_offset += parent.byte_offset
sub = FieldInfo(f_name, f_tp, f_bits, tp, parent, descriptor, byte_offset)
yield from iterfields(f_tp, sub)
if __name__ == '__main__':
# Dump C source to stdout
def output(string):
print(re.compile(r'^ +$', re.MULTILINE).sub('', string).lstrip('\n'))
output("/* Generated by Lib/test/test_ctypes/test_generated_structs.py */")
output(f"#define TEST_PATTERN {TEST_PATTERN}")
output("""
// Append VALUE to the result.
#define APPEND(ITEM) { \\
PyObject *item = ITEM; \\
if (!item) { \\
Py_DECREF(result); \\
return NULL; \\
} \\
int rv = PyList_Append(result, item); \\
Py_DECREF(item); \\
if (rv < 0) { \\
Py_DECREF(result); \\
return NULL; \\
} \\
}
// Set TARGET, and append a snapshot of `value`'s
// memory to the result.
#define SET_AND_APPEND(TYPE, TARGET, VAL) { \\
TYPE v = VAL; \\
TARGET = v; \\
APPEND(PyBytes_FromStringAndSize( \\
(char*)&value, sizeof(value))); \\
}
// Set a field to test values; append a snapshot of the memory
// after each of the operations.
#define TEST_FIELD(TYPE, TARGET) { \\
SET_AND_APPEND(TYPE, TARGET, -1) \\
SET_AND_APPEND(TYPE, TARGET, 1) \\
SET_AND_APPEND(TYPE, TARGET, (TYPE)TEST_PATTERN) \\
SET_AND_APPEND(TYPE, TARGET, 0) \\
}
#if defined(__GNUC__) || defined(__clang__)
#define GCC_ATTR(X) __attribute__((X))
#else
#define GCC_ATTR(X) /* */
#endif
static PyObject *
get_generated_test_data(PyObject *self, PyObject *name)
{
if (!PyUnicode_Check(name)) {
PyErr_SetString(PyExc_TypeError, "need a string");
return NULL;
}
PyObject *result = PyList_New(0);
if (!result) {
return NULL;
}
""")
for name, cls in TESTCASES.items():
output("""
if (PyUnicode_CompareWithASCIIString(name, %s) == 0) {
""" % c_str_repr(name))
lines, requires = dump_ctype(cls, struct_or_union_tag=name, semi=';')
if requires:
output(f"""
#if {" && ".join(f'({r})' for r in sorted(requires))}
""")
for line in lines:
output(' ' + line)
typename = f'{struct_or_union(cls)} {name}'
output(f"""
{typename} value;
memset(&value, 0, sizeof(value));
APPEND(PyUnicode_FromString({c_str_repr(name)}));
APPEND(PyLong_FromLong(sizeof({typename})));
APPEND(PyLong_FromLong(_Alignof({typename})));
""".rstrip())
for field in iterfields(cls):
f_tp = dump_simple_ctype(field.tp)
output(f"""\
TEST_FIELD({f_tp}, value.{field.full_name});
""".rstrip())
if requires:
output(f"""
#else
APPEND(Py_NewRef(Py_None));
APPEND(PyUnicode_FromString("skipped on this compiler"));
#endif
""")
output("""
return result;
}
""")
output("""
Py_DECREF(result);
PyErr_Format(PyExc_ValueError, "unknown testcase %R", name);
return NULL;
}
#undef GCC_ATTR
#undef TEST_FIELD
#undef SET_AND_APPEND
#undef APPEND
""")

View 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()

View File

@@ -27,8 +27,6 @@ class ObjectsTestCase(unittest.TestCase):
self.assertEqual(refcnt, sys.getrefcount(i))
self.assertEqual(ci._objects, None)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_c_char_p(self):
s = "Hello, World".encode("ascii")
refcnt = sys.getrefcount(s)
@@ -64,8 +62,6 @@ class ObjectsTestCase(unittest.TestCase):
x1.a, x2.b = 42, 93
self.assertEqual(y._objects, {"0": {}, "1": {}})
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_xxx(self):
class X(Structure):
_fields_ = [("a", c_char_p), ("b", c_char_p)]

View 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):
@@ -12,8 +11,6 @@ class SimpleTestCase(unittest.TestCase):
x = c_int(99)
self.assertEqual(x._objects, None)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_ccharp(self):
x = c_char_p()
self.assertEqual(x._objects, None)
@@ -35,8 +32,6 @@ class StructureTestCase(unittest.TestCase):
x.b = 99
self.assertEqual(x._objects, None)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_ccharp_struct(self):
class X(Structure):
_fields_ = [("a", c_char_p),
@@ -119,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()

View File

@@ -1,3 +1,4 @@
import ctypes
import math
import unittest
from ctypes import (CDLL, CFUNCTYPE, POINTER, create_string_buffer, sizeof,
@@ -21,6 +22,31 @@ class LibTest(unittest.TestCase):
self.assertEqual(lib.my_sqrt(4.0), 2.0)
self.assertEqual(lib.my_sqrt(2.0), math.sqrt(2.0))
@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
"requires C11 complex type and libffi >= 3.3.0")
def test_csqrt(self):
lib.my_csqrt.argtypes = ctypes.c_double_complex,
lib.my_csqrt.restype = ctypes.c_double_complex
self.assertEqual(lib.my_csqrt(4), 2+0j)
self.assertAlmostEqual(lib.my_csqrt(-1+0.01j),
0.004999937502734214+1.0000124996093955j)
self.assertAlmostEqual(lib.my_csqrt(-1-0.01j),
0.004999937502734214-1.0000124996093955j)
lib.my_csqrtf.argtypes = ctypes.c_float_complex,
lib.my_csqrtf.restype = ctypes.c_float_complex
self.assertAlmostEqual(lib.my_csqrtf(-1+0.01j),
0.004999937502734214+1.0000124996093955j)
self.assertAlmostEqual(lib.my_csqrtf(-1-0.01j),
0.004999937502734214-1.0000124996093955j)
lib.my_csqrtl.argtypes = ctypes.c_longdouble_complex,
lib.my_csqrtl.restype = ctypes.c_longdouble_complex
self.assertAlmostEqual(lib.my_csqrtl(-1+0.01j),
0.004999937502734214+1.0000124996093955j)
self.assertAlmostEqual(lib.my_csqrtl(-1-0.01j),
0.004999937502734214-1.0000124996093955j)
def test_qsort(self):
comparefunc = CFUNCTYPE(c_int, POINTER(c_char), POINTER(c_char))
lib.my_qsort.argtypes = c_void_p, c_size_t, c_size_t, comparefunc

View File

@@ -141,7 +141,7 @@ class LoaderTest(unittest.TestCase):
'test specific to Windows')
def test_load_hasattr(self):
# bpo-34816: shouldn't raise OSError
self.assertFalse(hasattr(ctypes.windll, 'test'))
self.assertNotHasAttr(ctypes.windll, 'test')
@unittest.skipUnless(os.name == "nt",
'test specific to Windows')

View File

@@ -5,7 +5,9 @@ from ctypes import (POINTER, sizeof, cast,
create_string_buffer, string_at,
create_unicode_buffer, wstring_at,
memmove, memset,
c_char_p, c_byte, c_ubyte, c_wchar)
memoryview_at, c_void_p,
c_char_p, c_byte, c_ubyte, c_wchar,
addressof, byref)
class MemFunctionsTest(unittest.TestCase):
@@ -58,9 +60,6 @@ class MemFunctionsTest(unittest.TestCase):
@support.refcount_test
def test_string_at(self):
s = string_at(b"foo bar")
# XXX The following may be wrong, depending on how Python
# manages string instances
self.assertEqual(2, sys.getrefcount(s))
self.assertTrue(s, "foo bar")
self.assertEqual(string_at(b"foo bar", 7), b"foo bar")
@@ -77,6 +76,62 @@ class MemFunctionsTest(unittest.TestCase):
self.assertEqual(wstring_at(a, 16), "Hello, World\0\0\0\0")
self.assertEqual(wstring_at(a, 0), "")
def test_memoryview_at(self):
b = (c_byte * 10)()
size = len(b)
for foreign_ptr in (
b,
cast(b, c_void_p),
byref(b),
addressof(b),
):
with self.subTest(foreign_ptr=type(foreign_ptr).__name__):
b[:] = b"initialval"
v = memoryview_at(foreign_ptr, size)
self.assertIsInstance(v, memoryview)
self.assertEqual(bytes(v), b"initialval")
# test that writes to source buffer get reflected in memoryview
b[:] = b"0123456789"
self.assertEqual(bytes(v), b"0123456789")
# test that writes to memoryview get reflected in source buffer
v[:] = b"9876543210"
self.assertEqual(bytes(b), b"9876543210")
with self.assertRaises(ValueError):
memoryview_at(foreign_ptr, -1)
with self.assertRaises(ValueError):
memoryview_at(foreign_ptr, sys.maxsize + 1)
v0 = memoryview_at(foreign_ptr, 0)
self.assertEqual(bytes(v0), b'')
def test_memoryview_at_readonly(self):
b = (c_byte * 10)()
size = len(b)
for foreign_ptr in (
b,
cast(b, c_void_p),
byref(b),
addressof(b),
):
with self.subTest(foreign_ptr=type(foreign_ptr).__name__):
b[:] = b"initialval"
v = memoryview_at(foreign_ptr, size, readonly=True)
self.assertIsInstance(v, memoryview)
self.assertEqual(bytes(v), b"initialval")
# test that writes to source buffer get reflected in memoryview
b[:] = b"0123456789"
self.assertEqual(bytes(v), b"0123456789")
# test that writes to the memoryview are blocked
with self.assertRaises(TypeError):
v[:] = b"9876543210"
if __name__ == "__main__":
unittest.main()

View File

@@ -1,12 +1,15 @@
import array
import ctypes
import struct
import sys
import unittest
from itertools import combinations
from operator import truth
from ctypes import (byref, sizeof, alignment,
c_char, c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint,
c_long, c_ulong, c_longlong, c_ulonglong,
c_float, c_double, c_longdouble, c_bool)
from test.support.testcase import ComplexesAreIdenticalMixin
def valid_ranges(*types):
@@ -38,8 +41,28 @@ unsigned_ranges = valid_ranges(*unsigned_types)
signed_ranges = valid_ranges(*signed_types)
bool_values = [True, False, 0, 1, -1, 5000, 'test', [], [1]]
class IntLike:
def __int__(self):
return 2
class NumberTestCase(unittest.TestCase):
class IndexLike:
def __index__(self):
return 2
class FloatLike:
def __float__(self):
return 2.0
class ComplexLike:
def __complex__(self):
return 1+1j
INF = float("inf")
NAN = float("nan")
class NumberTestCase(unittest.TestCase, ComplexesAreIdenticalMixin):
def test_default_init(self):
# default values are set to zero
@@ -86,9 +109,6 @@ class NumberTestCase(unittest.TestCase):
def test_floats(self):
# c_float and c_double can be created from
# Python int and float
class FloatLike:
def __float__(self):
return 2.0
f = FloatLike()
for t in float_types:
self.assertEqual(t(2.0).value, 2.0)
@@ -96,18 +116,34 @@ class NumberTestCase(unittest.TestCase):
self.assertEqual(t(2).value, 2.0)
self.assertEqual(t(f).value, 2.0)
@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]:
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)
@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
"requires C11 complex type")
def test_complex_round_trip(self):
# Ensure complexes transformed exactly. The CMPLX macro should
# preserve special components (like inf/nan or signed zero).
values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2,
-3, INF, -INF, NAN], 2)]
for z in values:
for t in [ctypes.c_double_complex, ctypes.c_float_complex,
ctypes.c_longdouble_complex]:
with self.subTest(z=z, type=t):
self.assertComplexesAreIdentical(z, t(z).value)
def test_integers(self):
class FloatLike:
def __float__(self):
return 2.0
f = FloatLike()
class IntLike:
def __int__(self):
return 2
d = IntLike()
class IndexLike:
def __index__(self):
return 2
i = IndexLike()
# integers cannot be constructed from floats,
# but from integer-like objects

View File

@@ -58,8 +58,7 @@ import unittest
def load_tests(loader, tests, pattern):
# TODO: RUSTPYTHON - doctest disabled due to null terminator in _objects
# tests.addTest(doctest.DocTestSuite())
tests.addTest(doctest.DocTestSuite())
return tests

View File

@@ -4,7 +4,7 @@ import test.support
from ctypes import (CDLL, PyDLL, ArgumentError,
Structure, Array, Union,
_Pointer, _SimpleCData, _CFuncPtr,
POINTER, pointer, byref,
POINTER, pointer, byref, sizeof,
c_void_p, c_char_p, c_wchar_p, py_object,
c_bool,
c_char, c_wchar,
@@ -88,19 +88,33 @@ class SimpleTypesTestCase(unittest.TestCase):
with self.assertRaises(TypeError) as cm:
c_char.from_param(b"abc")
self.assertEqual(str(cm.exception),
"one character bytes, bytearray or integer expected")
"one character bytes, bytearray, or an integer "
"in range(256) expected, not bytes of length 3")
def test_c_wchar(self):
with self.assertRaises(TypeError) as cm:
c_wchar.from_param("abc")
self.assertEqual(str(cm.exception),
"one character unicode string expected")
"a unicode character expected, not a string of length 3")
with self.assertRaises(TypeError) as cm:
c_wchar.from_param("")
self.assertEqual(str(cm.exception),
"a unicode character expected, not a string of length 0")
with self.assertRaises(TypeError) as cm:
c_wchar.from_param(123)
self.assertEqual(str(cm.exception),
"unicode string expected instead of int instance")
"a unicode character expected, not instance of int")
if sizeof(c_wchar) < 4:
with self.assertRaises(TypeError) as cm:
c_wchar.from_param('\U0001f40d')
self.assertEqual(str(cm.exception),
"the string '\\U0001f40d' cannot be converted to "
"a single wchar_t character")
def test_int_pointers(self):
LPINT = POINTER(c_int)

View File

@@ -81,6 +81,7 @@ class Point(Structure):
class PackedPoint(Structure):
_pack_ = 2
_layout_ = 'ms'
_fields_ = [("x", c_long), ("y", c_long)]
class PointMidPad(Structure):
@@ -88,6 +89,7 @@ class PointMidPad(Structure):
class PackedPointMidPad(Structure):
_pack_ = 2
_layout_ = 'ms'
_fields_ = [("x", c_byte), ("y", c_uint64)]
class PointEndPad(Structure):
@@ -95,6 +97,7 @@ class PointEndPad(Structure):
class PackedPointEndPad(Structure):
_pack_ = 2
_layout_ = 'ms'
_fields_ = [("x", c_uint64), ("y", c_byte)]
class Point2(Structure):

View File

@@ -3,7 +3,7 @@ import unittest
from ctypes import (CDLL, Structure, CFUNCTYPE, pointer,
c_void_p, c_char_p, c_wchar_p,
c_char, c_wchar, c_int, c_double)
from test.support import import_helper
from test.support import import_helper, thread_unsafe
_ctypes_test = import_helper.import_module("_ctypes_test")
@@ -21,7 +21,6 @@ class X(Structure):
class Y(X):
_fields_ = [("str", c_char_p)]
class PickleTest:
def dumps(self, item):
return pickle.dumps(item, self.proto)
@@ -39,6 +38,7 @@ class PickleTest:
self.assertEqual(memoryview(src).tobytes(),
memoryview(dst).tobytes())
@thread_unsafe('not thread safe')
def test_struct(self):
X.init_called = 0

View 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,230 @@ 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)
def test_pointer_proto_missing_argtypes_error(self):
class BadType(ctypes._Pointer):
# _type_ is intentionally missing
pass
func = ctypes.pythonapi.Py_GetVersion
func.argtypes = (BadType,)
with self.assertRaises(ctypes.ArgumentError):
func(object())
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):

View File

@@ -7,7 +7,7 @@ from ctypes import (pythonapi, POINTER, create_string_buffer, sizeof,
class PythonAPITestCase(unittest.TestCase):
# TODO: RUSTPYTHON
# TODO: RUSTPYTHON - requires pythonapi (Python C API)
@unittest.expectedFailure
def test_PyBytes_FromStringAndSize(self):
PyBytes_FromStringAndSize = pythonapi.PyBytes_FromStringAndSize
@@ -59,7 +59,7 @@ class PythonAPITestCase(unittest.TestCase):
del pyobj
self.assertEqual(sys.getrefcount(s), ref)
# TODO: RUSTPYTHON
# TODO: RUSTPYTHON - requires pythonapi (Python C API)
@unittest.expectedFailure
def test_PyOS_snprintf(self):
PyOS_snprintf = pythonapi.PyOS_snprintf

View File

@@ -51,7 +51,7 @@ class CallbackTracbackTestCase(unittest.TestCase):
if exc_msg is not None:
self.assertEqual(str(cm.unraisable.exc_value), exc_msg)
self.assertEqual(cm.unraisable.err_msg,
f"Exception ignored on calling ctypes "
f"Exception ignored while calling ctypes "
f"callback function {callback_func!r}")
self.assertIsNone(cm.unraisable.object)
@@ -70,8 +70,6 @@ class CallbackTracbackTestCase(unittest.TestCase):
with self.expect_unraisable(ZeroDivisionError):
cb(0.0)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_TypeErrorDivisionError(self):
cb = CFUNCTYPE(c_int, c_char_p)(callback_func)
err_msg = "unsupported operand type(s) for /: 'int' and 'bytes'"

View File

@@ -3,7 +3,7 @@ import gc
import sys
import unittest
from test import support
from test.support import import_helper
from test.support import import_helper, thread_unsafe
from test.support import script_helper
_ctypes_test = import_helper.import_module("_ctypes_test")
@@ -13,7 +13,7 @@ OtherCallback = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_ulonglong)
dll = ctypes.CDLL(_ctypes_test.__file__)
@thread_unsafe('not thread safe')
class RefcountTestCase(unittest.TestCase):
@support.refcount_test
def test_1(self):
@@ -24,36 +24,35 @@ class RefcountTestCase(unittest.TestCase):
def callback(value):
return value
self.assertEqual(sys.getrefcount(callback), 2)
orig_refcount = sys.getrefcount(callback)
cb = MyCallback(callback)
self.assertGreater(sys.getrefcount(callback), 2)
self.assertGreater(sys.getrefcount(callback), orig_refcount)
result = f(-10, cb)
self.assertEqual(result, -18)
cb = None
gc.collect()
self.assertEqual(sys.getrefcount(callback), 2)
self.assertEqual(sys.getrefcount(callback), orig_refcount)
@support.refcount_test
def test_refcount(self):
def func(*args):
pass
# this is the standard refcount for func
self.assertEqual(sys.getrefcount(func), 2)
orig_refcount = sys.getrefcount(func)
# the CFuncPtr instance holds at least one refcount on func:
f = OtherCallback(func)
self.assertGreater(sys.getrefcount(func), 2)
self.assertGreater(sys.getrefcount(func), orig_refcount)
# and may release it again
del f
self.assertGreaterEqual(sys.getrefcount(func), 2)
self.assertGreaterEqual(sys.getrefcount(func), orig_refcount)
# but now it must be gone
gc.collect()
self.assertEqual(sys.getrefcount(func), 2)
self.assertEqual(sys.getrefcount(func), orig_refcount)
class X(ctypes.Structure):
_fields_ = [("a", OtherCallback)]
@@ -61,29 +60,29 @@ class RefcountTestCase(unittest.TestCase):
x.a = OtherCallback(func)
# the CFuncPtr instance holds at least one refcount on func:
self.assertGreater(sys.getrefcount(func), 2)
self.assertGreater(sys.getrefcount(func), orig_refcount)
# and may release it again
del x
self.assertGreaterEqual(sys.getrefcount(func), 2)
self.assertGreaterEqual(sys.getrefcount(func), orig_refcount)
# and now it must be gone again
gc.collect()
self.assertEqual(sys.getrefcount(func), 2)
self.assertEqual(sys.getrefcount(func), orig_refcount)
f = OtherCallback(func)
# the CFuncPtr instance holds at least one refcount on func:
self.assertGreater(sys.getrefcount(func), 2)
self.assertGreater(sys.getrefcount(func), orig_refcount)
# create a cycle
f.cycle = f
del f
gc.collect()
self.assertEqual(sys.getrefcount(func), 2)
self.assertEqual(sys.getrefcount(func), orig_refcount)
@thread_unsafe('not thread safe')
class AnotherLeak(unittest.TestCase):
def test_callback(self):
proto = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int)

View File

@@ -22,12 +22,12 @@ class ReprTest(unittest.TestCase):
def test_numbers(self):
for typ in subclasses:
base = typ.__bases__[0]
self.assertTrue(repr(base(42)).startswith(base.__name__))
self.assertEqual("<X object at", repr(typ(42))[:12])
self.assertStartsWith(repr(base(42)), base.__name__)
self.assertStartsWith(repr(typ(42)), "<X object at")
def test_char(self):
self.assertEqual("c_char(b'x')", repr(c_char(b'x')))
self.assertEqual("<X object at", repr(X(b'x'))[:12])
self.assertStartsWith(repr(X(b'x')), "<X object at")
if __name__ == "__main__":

View File

@@ -20,9 +20,9 @@ class StringPtrTestCase(unittest.TestCase):
# NULL pointer access
self.assertRaises(ValueError, getattr, x.str, "contents")
b = create_string_buffer(b"Hello, World")
self.assertEqual(sys.getrefcount(b), 2)
orig_refcount = sys.getrefcount(b)
x.str = b
self.assertEqual(sys.getrefcount(b), 3)
self.assertEqual(sys.getrefcount(b), orig_refcount + 1)
# POINTER(c_char) and Python string is NOT compatible
# POINTER(c_char) and create_string_buffer() is compatible

View File

@@ -1,11 +1,12 @@
import unittest
import sys
from ctypes import Structure, Union, sizeof, c_char, c_int
from ._support import (CField, Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)
from ctypes import Structure, Union, sizeof, c_byte, c_char, c_int, CField
from ._support import Py_TPFLAGS_IMMUTABLETYPE, StructCheckMixin
class StructFieldsTestCase(unittest.TestCase):
NOTHING = object()
class FieldsTestBase(StructCheckMixin):
# Structure/Union classes must get 'finalized' sooner or
# later, when one of these things happen:
#
@@ -15,42 +16,47 @@ class StructFieldsTestCase(unittest.TestCase):
# 4. The type is subclassed
#
# When they are finalized, assigning _fields_ is no longer allowed.
def assert_final_fields(self, cls, expected=NOTHING):
self.assertRaises(AttributeError, setattr, cls, "_fields_", [])
self.assertEqual(getattr(cls, "_fields_", NOTHING), expected)
def test_1_A(self):
class X(Structure):
class X(self.cls):
pass
self.assertEqual(sizeof(X), 0) # not finalized
X._fields_ = [] # finalized
self.assertRaises(AttributeError, setattr, X, "_fields_", [])
self.assert_final_fields(X, expected=[])
def test_1_B(self):
class X(Structure):
class X(self.cls):
_fields_ = [] # finalized
self.assertRaises(AttributeError, setattr, X, "_fields_", [])
self.assert_final_fields(X, expected=[])
def test_2(self):
class X(Structure):
class X(self.cls):
pass
X()
self.assertRaises(AttributeError, setattr, X, "_fields_", [])
self.assert_final_fields(X)
def test_3(self):
class X(Structure):
class X(self.cls):
pass
class Y(Structure):
class Y(self.cls):
_fields_ = [("x", X)] # finalizes X
self.assertRaises(AttributeError, setattr, X, "_fields_", [])
self.assert_final_fields(X)
def test_4(self):
class X(Structure):
class X(self.cls):
pass
class Y(X):
pass
self.assertRaises(AttributeError, setattr, X, "_fields_", [])
self.assert_final_fields(X)
Y._fields_ = []
self.assertRaises(AttributeError, setattr, X, "_fields_", [])
self.assert_final_fields(X)
def test_5(self):
class X(Structure):
class X(self.cls):
_fields_ = (("char", c_char * 5),)
x = X(b'#' * 5)
@@ -60,15 +66,8 @@ class StructFieldsTestCase(unittest.TestCase):
def test_6(self):
self.assertRaises(TypeError, CField)
def test_cfield_type_flags(self):
self.assertTrue(CField.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)
self.assertTrue(CField.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
def test_cfield_inheritance_hierarchy(self):
self.assertEqual(CField.mro(), [CField, object])
def test_gh99275(self):
class BrokenStructure(Structure):
class BrokenStructure(self.cls):
def __init_subclass__(cls, **kwargs):
cls._fields_ = [] # This line will fail, `stginfo` is not ready
@@ -76,19 +75,44 @@ class StructFieldsTestCase(unittest.TestCase):
'ctypes state is not initialized'):
class Subclass(BrokenStructure): ...
def test_invalid_byte_size_raises_gh132470(self):
with self.assertRaisesRegex(ValueError, r"does not match type size"):
CField(
name="a",
type=c_byte,
byte_size=2, # Wrong size: c_byte is only 1 byte
byte_offset=2,
index=1,
_internal_use=True
)
def test_max_field_size_gh126937(self):
# Classes for big structs should be created successfully.
# (But they most likely can't be instantiated.)
# The size must fit in Py_ssize_t.
max_field_size = sys.maxsize
class X(Structure):
_fields_ = [('char', c_char),]
max_field_size = sys.maxsize
self.check_struct(X)
class Y(Structure):
_fields_ = [('largeField', X * max_field_size)]
self.check_struct(Y)
class Z(Structure):
_fields_ = [('largeField', c_char * max_field_size)]
self.check_struct(Z)
# The *bit* size overflows Py_ssize_t.
self.assertEqual(Y.largeField.bit_size, max_field_size * 8)
self.assertEqual(Z.largeField.bit_size, max_field_size * 8)
self.assertEqual(Y.largeField.byte_size, max_field_size)
self.assertEqual(Z.largeField.byte_size, max_field_size)
self.assertEqual(sizeof(Y), max_field_size)
self.assertEqual(sizeof(Z), max_field_size)
with self.assertRaises(OverflowError):
class TooBig(Structure):
@@ -97,29 +121,56 @@ class StructFieldsTestCase(unittest.TestCase):
class TooBig(Structure):
_fields_ = [('largeField', c_char * (max_field_size + 1))]
# Also test around edge case for the bit_size calculation
for size in (max_field_size // 8 - 1,
max_field_size // 8,
max_field_size // 8 + 1):
class S(Structure):
_fields_ = [('largeField', c_char * size),]
self.check_struct(S)
self.assertEqual(S.largeField.bit_size, size * 8)
def test_bitfield_overflow_error_message(self):
with self.assertRaisesRegex(
ValueError,
r"bit field 'x' overflows its type \(2 \+ 7 > 8\)",
):
CField(
name="x",
type=c_byte,
byte_size=1,
byte_offset=0,
index=0,
_internal_use=True,
bit_size=7,
bit_offset=2,
)
# __set__ and __get__ should raise a TypeError in case their self
# argument is not a ctype instance.
def test___set__(self):
class MyCStruct(Structure):
class MyCStruct(self.cls):
_fields_ = (("field", c_int),)
self.assertRaises(TypeError,
MyCStruct.field.__set__, 'wrong type self', 42)
class MyCUnion(Union):
_fields_ = (("field", c_int),)
self.assertRaises(TypeError,
MyCUnion.field.__set__, 'wrong type self', 42)
def test___get__(self):
class MyCStruct(Structure):
class MyCStruct(self.cls):
_fields_ = (("field", c_int),)
self.assertRaises(TypeError,
MyCStruct.field.__get__, 'wrong type self', 42)
class MyCUnion(Union):
_fields_ = (("field", c_int),)
self.assertRaises(TypeError,
MyCUnion.field.__get__, 'wrong type self', 42)
class StructFieldsTestCase(unittest.TestCase, FieldsTestBase):
cls = Structure
def test_cfield_type_flags(self):
self.assertTrue(CField.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
def test_cfield_inheritance_hierarchy(self):
self.assertEqual(CField.mro(), [CField, object])
class UnionFieldsTestCase(unittest.TestCase, FieldsTestBase):
cls = Union
if __name__ == "__main__":

477
Lib/test/test_ctypes/test_structunion.py vendored Normal file
View File

@@ -0,0 +1,477 @@
"""Common tests for ctypes.Structure and ctypes.Union"""
import unittest
import sys
from ctypes import (Structure, Union, POINTER, sizeof, alignment,
c_char, c_byte, c_ubyte,
c_short, c_ushort, c_int, c_uint,
c_long, c_ulong, c_longlong, c_ulonglong, c_float, c_double,
c_int8, c_int16, c_int32)
from ._support import (_CData, PyCStructType, UnionType,
Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)
from struct import calcsize
import contextlib
from test.support import MS_WINDOWS
class StructUnionTestBase:
formats = {"c": c_char,
"b": c_byte,
"B": c_ubyte,
"h": c_short,
"H": c_ushort,
"i": c_int,
"I": c_uint,
"l": c_long,
"L": c_ulong,
"q": c_longlong,
"Q": c_ulonglong,
"f": c_float,
"d": c_double,
}
def test_subclass(self):
class X(self.cls):
_fields_ = [("a", c_int)]
class Y(X):
_fields_ = [("b", c_int)]
class Z(X):
pass
self.assertEqual(sizeof(X), sizeof(c_int))
self.check_sizeof(Y,
struct_size=sizeof(c_int)*2,
union_size=sizeof(c_int))
self.assertEqual(sizeof(Z), sizeof(c_int))
self.assertEqual(X._fields_, [("a", c_int)])
self.assertEqual(Y._fields_, [("b", c_int)])
self.assertEqual(Z._fields_, [("a", c_int)])
def test_subclass_delayed(self):
class X(self.cls):
pass
self.assertEqual(sizeof(X), 0)
X._fields_ = [("a", c_int)]
class Y(X):
pass
self.assertEqual(sizeof(Y), sizeof(X))
Y._fields_ = [("b", c_int)]
class Z(X):
pass
self.assertEqual(sizeof(X), sizeof(c_int))
self.check_sizeof(Y,
struct_size=sizeof(c_int)*2,
union_size=sizeof(c_int))
self.assertEqual(sizeof(Z), sizeof(c_int))
self.assertEqual(X._fields_, [("a", c_int)])
self.assertEqual(Y._fields_, [("b", c_int)])
self.assertEqual(Z._fields_, [("a", c_int)])
def test_inheritance_hierarchy(self):
self.assertEqual(self.cls.mro(), [self.cls, _CData, object])
self.assertEqual(type(self.metacls), type)
def test_type_flags(self):
for cls in self.cls, self.metacls:
with self.subTest(cls=cls):
self.assertTrue(cls.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
self.assertFalse(cls.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)
def test_metaclass_details(self):
# Abstract classes (whose metaclass __init__ was not called) can't be
# instantiated directly
NewClass = self.metacls.__new__(self.metacls, 'NewClass',
(self.cls,), {})
for cls in self.cls, NewClass:
with self.subTest(cls=cls):
with self.assertRaisesRegex(TypeError, "abstract class"):
obj = cls()
# Cannot call the metaclass __init__ more than once
class T(self.cls):
_fields_ = [("x", c_char),
("y", c_char)]
with self.assertRaisesRegex(SystemError, "already initialized"):
self.metacls.__init__(T, 'ptr', (), {})
def test_alignment(self):
class X(self.cls):
_fields_ = [("x", c_char * 3)]
self.assertEqual(alignment(X), calcsize("s"))
self.assertEqual(sizeof(X), calcsize("3s"))
class Y(self.cls):
_fields_ = [("x", c_char * 3),
("y", c_int)]
self.assertEqual(alignment(Y), alignment(c_int))
self.check_sizeof(Y,
struct_size=calcsize("3s i"),
union_size=max(calcsize("3s"), calcsize("i")))
class SI(self.cls):
_fields_ = [("a", X),
("b", Y)]
self.assertEqual(alignment(SI), max(alignment(Y), alignment(X)))
self.check_sizeof(SI,
struct_size=calcsize("3s0i 3si 0i"),
union_size=max(calcsize("3s"), calcsize("i")))
class IS(self.cls):
_fields_ = [("b", Y),
("a", X)]
self.assertEqual(alignment(SI), max(alignment(X), alignment(Y)))
self.check_sizeof(IS,
struct_size=calcsize("3si 3s 0i"),
union_size=max(calcsize("3s"), calcsize("i")))
class XX(self.cls):
_fields_ = [("a", X),
("b", X)]
self.assertEqual(alignment(XX), alignment(X))
self.check_sizeof(XX,
struct_size=calcsize("3s 3s 0s"),
union_size=calcsize("3s"))
def test_empty(self):
# I had problems with these
#
# Although these are pathological cases: Empty Structures!
class X(self.cls):
_fields_ = []
# Is this really the correct alignment, or should it be 0?
self.assertTrue(alignment(X) == 1)
self.assertTrue(sizeof(X) == 0)
class XX(self.cls):
_fields_ = [("a", X),
("b", X)]
self.assertEqual(alignment(XX), 1)
self.assertEqual(sizeof(XX), 0)
def test_fields(self):
# test the offset and size attributes of Structure/Union fields.
class X(self.cls):
_fields_ = [("x", c_int),
("y", c_char)]
self.assertEqual(X.x.offset, 0)
self.assertEqual(X.x.size, sizeof(c_int))
if self.cls == Structure:
self.assertEqual(X.y.offset, sizeof(c_int))
else:
self.assertEqual(X.y.offset, 0)
self.assertEqual(X.y.size, sizeof(c_char))
# readonly
self.assertRaises((TypeError, AttributeError), setattr, X.x, "offset", 92)
self.assertRaises((TypeError, AttributeError), setattr, X.x, "size", 92)
# XXX Should we check nested data types also?
# offset is always relative to the class...
def test_field_descriptor_attributes(self):
"""Test information provided by the descriptors"""
class Inner(Structure):
_fields_ = [
("a", c_int16),
("b", c_int8, 1),
("c", c_int8, 2),
]
class X(self.cls):
_fields_ = [
("x", c_int32),
("y", c_int16, 1),
("_", Inner),
]
_anonymous_ = ["_"]
field_names = "xy_abc"
# name
for name in field_names:
with self.subTest(name=name):
self.assertEqual(getattr(X, name).name, name)
# type
expected_types = dict(
x=c_int32,
y=c_int16,
_=Inner,
a=c_int16,
b=c_int8,
c=c_int8,
)
assert set(expected_types) == set(field_names)
for name, tp in expected_types.items():
with self.subTest(name=name):
self.assertEqual(getattr(X, name).type, tp)
self.assertEqual(getattr(X, name).byte_size, sizeof(tp))
# offset, byte_offset
expected_offsets = dict(
x=(0, 0),
y=(0, 4),
_=(0, 6),
a=(0, 6),
b=(2, 8),
c=(2, 8),
)
assert set(expected_offsets) == set(field_names)
for name, (union_offset, struct_offset) in expected_offsets.items():
with self.subTest(name=name):
self.assertEqual(getattr(X, name).offset,
getattr(X, name).byte_offset)
if self.cls == Structure:
self.assertEqual(getattr(X, name).offset, struct_offset)
else:
self.assertEqual(getattr(X, name).offset, union_offset)
# is_bitfield, bit_size, bit_offset
# size
little_endian = (sys.byteorder == 'little')
expected_bitfield_info = dict(
# (bit_size, bit_offset)
b=(1, 0 if little_endian else 7),
c=(2, 1 if little_endian else 5),
y=(1, 0 if little_endian else 15),
)
for name in field_names:
with self.subTest(name=name):
if info := expected_bitfield_info.get(name):
self.assertEqual(getattr(X, name).is_bitfield, True)
expected_bit_size, expected_bit_offset = info
self.assertEqual(getattr(X, name).bit_size,
expected_bit_size)
self.assertEqual(getattr(X, name).bit_offset,
expected_bit_offset)
self.assertEqual(getattr(X, name).size,
(expected_bit_size << 16)
| expected_bit_offset)
else:
self.assertEqual(getattr(X, name).is_bitfield, False)
type_size = sizeof(expected_types[name])
self.assertEqual(getattr(X, name).bit_size, type_size * 8)
self.assertEqual(getattr(X, name).bit_offset, 0)
self.assertEqual(getattr(X, name).size, type_size)
# is_anonymous
for name in field_names:
with self.subTest(name=name):
self.assertEqual(getattr(X, name).is_anonymous, (name == '_'))
def test_invalid_field_types(self):
class POINT(self.cls):
pass
self.assertRaises(TypeError, setattr, POINT, "_fields_", [("x", 1), ("y", 2)])
def test_invalid_name(self):
# field name must be string
for name in b"x", 3, None:
with self.subTest(name=name):
with self.assertRaises(TypeError):
class S(self.cls):
_fields_ = [(name, c_int)]
def test_str_name(self):
class WeirdString(str):
def __str__(self):
return "unwanted value"
class S(self.cls):
_fields_ = [(WeirdString("f"), c_int)]
self.assertEqual(S.f.name, "f")
def test_intarray_fields(self):
class SomeInts(self.cls):
_fields_ = [("a", c_int * 4)]
# can use tuple to initialize array (but not list!)
self.assertEqual(SomeInts((1, 2)).a[:], [1, 2, 0, 0])
self.assertEqual(SomeInts((1, 2)).a[::], [1, 2, 0, 0])
self.assertEqual(SomeInts((1, 2)).a[::-1], [0, 0, 2, 1])
self.assertEqual(SomeInts((1, 2)).a[::2], [1, 0])
self.assertEqual(SomeInts((1, 2)).a[1:5:6], [2])
self.assertEqual(SomeInts((1, 2)).a[6:4:-1], [])
self.assertEqual(SomeInts((1, 2, 3, 4)).a[:], [1, 2, 3, 4])
self.assertEqual(SomeInts((1, 2, 3, 4)).a[::], [1, 2, 3, 4])
# too long
# XXX Should raise ValueError?, not RuntimeError
self.assertRaises(RuntimeError, SomeInts, (1, 2, 3, 4, 5))
def test_huge_field_name(self):
# issue12881: segfault with large structure field names
def create_class(length):
class S(self.cls):
_fields_ = [('x' * length, c_int)]
for length in [10 ** i for i in range(0, 8)]:
try:
create_class(length)
except MemoryError:
# MemoryErrors are OK, we just don't want to segfault
pass
def test_abstract_class(self):
class X(self.cls):
_abstract_ = "something"
with self.assertRaisesRegex(TypeError, r"^abstract class$"):
X()
def test_methods(self):
self.assertIn("in_dll", dir(type(self.cls)))
self.assertIn("from_address", dir(type(self.cls)))
self.assertIn("in_dll", dir(type(self.cls)))
def test_pack_layout_switch(self):
# Setting _pack_ implicitly sets default layout to MSVC;
# this is deprecated on non-Windows platforms.
if MS_WINDOWS:
warn_context = contextlib.nullcontext()
else:
warn_context = self.assertWarns(DeprecationWarning)
with warn_context:
class X(self.cls):
_pack_ = 1
# _layout_ missing
_fields_ = [('a', c_int8, 1), ('b', c_int16, 2)]
# Check MSVC layout (bitfields of different types aren't combined)
self.check_sizeof(X, struct_size=3, union_size=2)
class StructureTestCase(unittest.TestCase, StructUnionTestBase):
cls = Structure
metacls = PyCStructType
def test_metaclass_name(self):
self.assertEqual(self.metacls.__name__, "PyCStructType")
def check_sizeof(self, cls, *, struct_size, union_size):
self.assertEqual(sizeof(cls), struct_size)
def test_simple_structs(self):
for code, tp in self.formats.items():
class X(Structure):
_fields_ = [("x", c_char),
("y", tp)]
self.assertEqual((sizeof(X), code),
(calcsize("c%c0%c" % (code, code)), code))
class UnionTestCase(unittest.TestCase, StructUnionTestBase):
cls = Union
metacls = UnionType
def test_metaclass_name(self):
self.assertEqual(self.metacls.__name__, "UnionType")
def check_sizeof(self, cls, *, struct_size, union_size):
self.assertEqual(sizeof(cls), union_size)
def test_simple_unions(self):
for code, tp in self.formats.items():
class X(Union):
_fields_ = [("x", c_char),
("y", tp)]
self.assertEqual((sizeof(X), code),
(calcsize("%c" % (code)), code))
class PointerMemberTestBase:
def test(self):
# a Structure/Union with a POINTER field
class S(self.cls):
_fields_ = [("array", POINTER(c_int))]
s = S()
# We can assign arrays of the correct type
s.array = (c_int * 3)(1, 2, 3)
items = [s.array[i] for i in range(3)]
self.assertEqual(items, [1, 2, 3])
s.array[0] = 42
items = [s.array[i] for i in range(3)]
self.assertEqual(items, [42, 2, 3])
s.array[0] = 1
items = [s.array[i] for i in range(3)]
self.assertEqual(items, [1, 2, 3])
class PointerMemberTestCase_Struct(unittest.TestCase, PointerMemberTestBase):
cls = Structure
def test_none_to_pointer_fields(self):
class S(self.cls):
_fields_ = [("x", c_int),
("p", POINTER(c_int))]
s = S()
s.x = 12345678
s.p = None
self.assertEqual(s.x, 12345678)
class PointerMemberTestCase_Union(unittest.TestCase, PointerMemberTestBase):
cls = Union
def test_none_to_pointer_fields(self):
class S(self.cls):
_fields_ = [("x", c_int),
("p", POINTER(c_int))]
s = S()
s.x = 12345678
s.p = None
self.assertFalse(s.p) # NULL pointers are falsy
class TestRecursiveBase:
def test_contains_itself(self):
class Recursive(self.cls):
pass
try:
Recursive._fields_ = [("next", Recursive)]
except AttributeError as details:
self.assertIn("Structure or union cannot contain itself",
str(details))
else:
self.fail("Structure or union cannot contain itself")
def test_vice_versa(self):
class First(self.cls):
pass
class Second(self.cls):
pass
First._fields_ = [("second", Second)]
try:
Second._fields_ = [("first", First)]
except AttributeError as details:
self.assertIn("_fields_ is final", str(details))
else:
self.fail("AttributeError not raised")
class TestRecursiveStructure(unittest.TestCase, TestRecursiveBase):
cls = Structure
class TestRecursiveUnion(unittest.TestCase, TestRecursiveBase):
cls = Union

View File

@@ -1,214 +1,32 @@
"""Tests for ctypes.Structure
Features common with Union should go in test_structunion.py instead.
"""
from platform import architecture as _architecture
import struct
import sys
import unittest
from ctypes import (CDLL, Array, Structure, Union, POINTER, sizeof, byref, alignment,
from ctypes import (CDLL, Structure, Union, POINTER, sizeof, byref,
c_void_p, c_char, c_wchar, c_byte, c_ubyte,
c_uint8, c_uint16, c_uint32,
c_short, c_ushort, c_int, c_uint,
c_long, c_ulong, c_longlong, c_ulonglong, c_float, c_double)
c_uint8, c_uint16, c_uint32, c_int, c_uint,
c_long, c_ulong, c_longlong, c_float, c_double)
from ctypes.util import find_library
from struct import calcsize
from collections import namedtuple
from test import support
from test.support import import_helper
from ._support import StructCheckMixin
_ctypes_test = import_helper.import_module("_ctypes_test")
from ._support import (_CData, PyCStructType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)
class SubclassesTest(unittest.TestCase):
def test_subclass(self):
class X(Structure):
_fields_ = [("a", c_int)]
class Y(X):
_fields_ = [("b", c_int)]
class Z(X):
pass
self.assertEqual(sizeof(X), sizeof(c_int))
self.assertEqual(sizeof(Y), sizeof(c_int)*2)
self.assertEqual(sizeof(Z), sizeof(c_int))
self.assertEqual(X._fields_, [("a", c_int)])
self.assertEqual(Y._fields_, [("b", c_int)])
self.assertEqual(Z._fields_, [("a", c_int)])
def test_subclass_delayed(self):
class X(Structure):
pass
self.assertEqual(sizeof(X), 0)
X._fields_ = [("a", c_int)]
class Y(X):
pass
self.assertEqual(sizeof(Y), sizeof(X))
Y._fields_ = [("b", c_int)]
class Z(X):
pass
self.assertEqual(sizeof(X), sizeof(c_int))
self.assertEqual(sizeof(Y), sizeof(c_int)*2)
self.assertEqual(sizeof(Z), sizeof(c_int))
self.assertEqual(X._fields_, [("a", c_int)])
self.assertEqual(Y._fields_, [("b", c_int)])
self.assertEqual(Z._fields_, [("a", c_int)])
class StructureTestCase(unittest.TestCase):
formats = {"c": c_char,
"b": c_byte,
"B": c_ubyte,
"h": c_short,
"H": c_ushort,
"i": c_int,
"I": c_uint,
"l": c_long,
"L": c_ulong,
"q": c_longlong,
"Q": c_ulonglong,
"f": c_float,
"d": c_double,
}
def test_inheritance_hierarchy(self):
self.assertEqual(Structure.mro(), [Structure, _CData, object])
self.assertEqual(PyCStructType.__name__, "PyCStructType")
self.assertEqual(type(PyCStructType), type)
def test_type_flags(self):
for cls in Structure, PyCStructType:
with self.subTest(cls=cls):
self.assertTrue(Structure.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
self.assertFalse(Structure.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)
def test_metaclass_details(self):
# Abstract classes (whose metaclass __init__ was not called) can't be
# instantiated directly
NewStructure = PyCStructType.__new__(PyCStructType, 'NewStructure',
(Structure,), {})
for cls in Structure, NewStructure:
with self.subTest(cls=cls):
with self.assertRaisesRegex(TypeError, "abstract class"):
obj = cls()
# Cannot call the metaclass __init__ more than once
class T(Structure):
_fields_ = [("x", c_char),
("y", c_char)]
with self.assertRaisesRegex(SystemError, "already initialized"):
PyCStructType.__init__(T, 'ptr', (), {})
def test_simple_structs(self):
for code, tp in self.formats.items():
class X(Structure):
_fields_ = [("x", c_char),
("y", tp)]
self.assertEqual((sizeof(X), code),
(calcsize("c%c0%c" % (code, code)), code))
def test_unions(self):
for code, tp in self.formats.items():
class X(Union):
_fields_ = [("x", c_char),
("y", tp)]
self.assertEqual((sizeof(X), code),
(calcsize("%c" % (code)), code))
def test_struct_alignment(self):
class X(Structure):
_fields_ = [("x", c_char * 3)]
self.assertEqual(alignment(X), calcsize("s"))
self.assertEqual(sizeof(X), calcsize("3s"))
class Y(Structure):
_fields_ = [("x", c_char * 3),
("y", c_int)]
self.assertEqual(alignment(Y), alignment(c_int))
self.assertEqual(sizeof(Y), calcsize("3si"))
class SI(Structure):
_fields_ = [("a", X),
("b", Y)]
self.assertEqual(alignment(SI), max(alignment(Y), alignment(X)))
self.assertEqual(sizeof(SI), calcsize("3s0i 3si 0i"))
class IS(Structure):
_fields_ = [("b", Y),
("a", X)]
self.assertEqual(alignment(SI), max(alignment(X), alignment(Y)))
self.assertEqual(sizeof(IS), calcsize("3si 3s 0i"))
class XX(Structure):
_fields_ = [("a", X),
("b", X)]
self.assertEqual(alignment(XX), alignment(X))
self.assertEqual(sizeof(XX), calcsize("3s 3s 0s"))
def test_empty(self):
# I had problems with these
#
# Although these are pathological cases: Empty Structures!
class X(Structure):
_fields_ = []
class Y(Union):
_fields_ = []
# Is this really the correct alignment, or should it be 0?
self.assertTrue(alignment(X) == alignment(Y) == 1)
self.assertTrue(sizeof(X) == sizeof(Y) == 0)
class XX(Structure):
_fields_ = [("a", X),
("b", X)]
self.assertEqual(alignment(XX), 1)
self.assertEqual(sizeof(XX), 0)
def test_fields(self):
# test the offset and size attributes of Structure/Union fields.
class X(Structure):
_fields_ = [("x", c_int),
("y", c_char)]
self.assertEqual(X.x.offset, 0)
self.assertEqual(X.x.size, sizeof(c_int))
self.assertEqual(X.y.offset, sizeof(c_int))
self.assertEqual(X.y.size, sizeof(c_char))
# readonly
self.assertRaises((TypeError, AttributeError), setattr, X.x, "offset", 92)
self.assertRaises((TypeError, AttributeError), setattr, X.x, "size", 92)
class X(Union):
_fields_ = [("x", c_int),
("y", c_char)]
self.assertEqual(X.x.offset, 0)
self.assertEqual(X.x.size, sizeof(c_int))
self.assertEqual(X.y.offset, 0)
self.assertEqual(X.y.size, sizeof(c_char))
# readonly
self.assertRaises((TypeError, AttributeError), setattr, X.x, "offset", 92)
self.assertRaises((TypeError, AttributeError), setattr, X.x, "size", 92)
# XXX Should we check nested data types also?
# offset is always relative to the class...
class StructureTestCase(unittest.TestCase, StructCheckMixin):
def test_packed(self):
class X(Structure):
_fields_ = [("a", c_byte),
("b", c_longlong)]
_pack_ = 1
_layout_ = 'ms'
self.check_struct(X)
self.assertEqual(sizeof(X), 9)
self.assertEqual(X.b.offset, 1)
@@ -217,6 +35,8 @@ class StructureTestCase(unittest.TestCase):
_fields_ = [("a", c_byte),
("b", c_longlong)]
_pack_ = 2
_layout_ = 'ms'
self.check_struct(X)
self.assertEqual(sizeof(X), 10)
self.assertEqual(X.b.offset, 2)
@@ -227,6 +47,8 @@ class StructureTestCase(unittest.TestCase):
_fields_ = [("a", c_byte),
("b", c_longlong)]
_pack_ = 4
_layout_ = 'ms'
self.check_struct(X)
self.assertEqual(sizeof(X), min(4, longlong_align) + longlong_size)
self.assertEqual(X.b.offset, min(4, longlong_align))
@@ -234,26 +56,33 @@ class StructureTestCase(unittest.TestCase):
_fields_ = [("a", c_byte),
("b", c_longlong)]
_pack_ = 8
_layout_ = 'ms'
self.check_struct(X)
self.assertEqual(sizeof(X), min(8, longlong_align) + longlong_size)
self.assertEqual(X.b.offset, min(8, longlong_align))
d = {"_fields_": [("a", "b"),
("b", "q")],
"_pack_": -1}
self.assertRaises(ValueError, type(Structure), "X", (Structure,), d)
with self.assertRaises(ValueError):
class X(Structure):
_fields_ = [("a", "b"), ("b", "q")]
_pack_ = -1
_layout_ = "ms"
@support.cpython_only
def test_packed_c_limits(self):
# Issue 15989
import _testcapi
d = {"_fields_": [("a", c_byte)],
"_pack_": _testcapi.INT_MAX + 1}
self.assertRaises(ValueError, type(Structure), "X", (Structure,), d)
d = {"_fields_": [("a", c_byte)],
"_pack_": _testcapi.UINT_MAX + 2}
self.assertRaises(ValueError, type(Structure), "X", (Structure,), d)
with self.assertRaises(ValueError):
class X(Structure):
_fields_ = [("a", c_byte)]
_pack_ = _testcapi.INT_MAX + 1
_layout_ = "ms"
with self.assertRaises(ValueError):
class X(Structure):
_fields_ = [("a", c_byte)]
_pack_ = _testcapi.UINT_MAX + 2
_layout_ = "ms"
def test_initializers(self):
class Person(Structure):
@@ -274,6 +103,7 @@ class StructureTestCase(unittest.TestCase):
def test_conflicting_initializers(self):
class POINT(Structure):
_fields_ = [("phi", c_float), ("rho", c_float)]
self.check_struct(POINT)
# conflicting positional and keyword args
self.assertRaisesRegex(TypeError, "phi", POINT, 2, 3, phi=4)
self.assertRaisesRegex(TypeError, "rho", POINT, 2, 3, rho=4)
@@ -284,52 +114,25 @@ class StructureTestCase(unittest.TestCase):
def test_keyword_initializers(self):
class POINT(Structure):
_fields_ = [("x", c_int), ("y", c_int)]
self.check_struct(POINT)
pt = POINT(1, 2)
self.assertEqual((pt.x, pt.y), (1, 2))
pt = POINT(y=2, x=1)
self.assertEqual((pt.x, pt.y), (1, 2))
def test_invalid_field_types(self):
class POINT(Structure):
pass
self.assertRaises(TypeError, setattr, POINT, "_fields_", [("x", 1), ("y", 2)])
def test_invalid_name(self):
# field name must be string
def declare_with_name(name):
class S(Structure):
_fields_ = [(name, c_int)]
self.assertRaises(TypeError, declare_with_name, b"x")
def test_intarray_fields(self):
class SomeInts(Structure):
_fields_ = [("a", c_int * 4)]
# can use tuple to initialize array (but not list!)
self.assertEqual(SomeInts((1, 2)).a[:], [1, 2, 0, 0])
self.assertEqual(SomeInts((1, 2)).a[::], [1, 2, 0, 0])
self.assertEqual(SomeInts((1, 2)).a[::-1], [0, 0, 2, 1])
self.assertEqual(SomeInts((1, 2)).a[::2], [1, 0])
self.assertEqual(SomeInts((1, 2)).a[1:5:6], [2])
self.assertEqual(SomeInts((1, 2)).a[6:4:-1], [])
self.assertEqual(SomeInts((1, 2, 3, 4)).a[:], [1, 2, 3, 4])
self.assertEqual(SomeInts((1, 2, 3, 4)).a[::], [1, 2, 3, 4])
# too long
# XXX Should raise ValueError?, not RuntimeError
self.assertRaises(RuntimeError, SomeInts, (1, 2, 3, 4, 5))
def test_nested_initializers(self):
# test initializing nested structures
class Phone(Structure):
_fields_ = [("areacode", c_char*6),
("number", c_char*12)]
self.check_struct(Phone)
class Person(Structure):
_fields_ = [("name", c_char * 12),
("phone", Phone),
("age", c_int)]
self.check_struct(Person)
p = Person(b"Someone", (b"1234", b"5678"), 5)
@@ -342,6 +145,7 @@ class StructureTestCase(unittest.TestCase):
class PersonW(Structure):
_fields_ = [("name", c_wchar * 12),
("age", c_int)]
self.check_struct(PersonW)
p = PersonW("Someone \xe9")
self.assertEqual(p.name, "Someone \xe9")
@@ -357,11 +161,13 @@ class StructureTestCase(unittest.TestCase):
class Phone(Structure):
_fields_ = [("areacode", c_char*6),
("number", c_char*12)]
self.check_struct(Phone)
class Person(Structure):
_fields_ = [("name", c_char * 12),
("phone", Phone),
("age", c_int)]
self.check_struct(Person)
cls, msg = self.get_except(Person, b"Someone", (1, 2))
self.assertEqual(cls, RuntimeError)
@@ -374,47 +180,29 @@ class StructureTestCase(unittest.TestCase):
self.assertEqual(msg,
"(Phone) TypeError: too many initializers")
def test_huge_field_name(self):
# issue12881: segfault with large structure field names
def create_class(length):
class S(Structure):
_fields_ = [('x' * length, c_int)]
for length in [10 ** i for i in range(0, 8)]:
try:
create_class(length)
except MemoryError:
# MemoryErrors are OK, we just don't want to segfault
pass
def get_except(self, func, *args):
try:
func(*args)
except Exception as detail:
return detail.__class__, str(detail)
def test_abstract_class(self):
class X(Structure):
_abstract_ = "something"
# try 'X()'
cls, msg = self.get_except(eval, "X()", locals())
self.assertEqual((cls, msg), (TypeError, "abstract class"))
def test_methods(self):
self.assertIn("in_dll", dir(type(Structure)))
self.assertIn("from_address", dir(type(Structure)))
self.assertIn("in_dll", dir(type(Structure)))
def test_positional_args(self):
# see also http://bugs.python.org/issue5042
class W(Structure):
_fields_ = [("a", c_int), ("b", c_int)]
self.check_struct(W)
class X(W):
_fields_ = [("c", c_int)]
self.check_struct(X)
class Y(X):
pass
self.check_struct(Y)
class Z(Y):
_fields_ = [("d", c_int), ("e", c_int), ("f", c_int)]
self.check_struct(Z)
z = Z(1, 2, 3, 4, 5, 6)
self.assertEqual((z.a, z.b, z.c, z.d, z.e, z.f),
@@ -433,6 +221,7 @@ class StructureTestCase(unittest.TestCase):
('second', c_ulong),
('third', c_ulong),
]
self.check_struct(Test)
s = Test()
s.first = 0xdeadbeef
@@ -462,6 +251,7 @@ class StructureTestCase(unittest.TestCase):
]
def __del__(self):
finalizer_calls.append("called")
self.check_struct(Test)
s = Test(1, 2, 3)
# Test the StructUnionType_paramfunc() code path which copies the
@@ -491,6 +281,7 @@ class StructureTestCase(unittest.TestCase):
('first', c_uint),
('second', c_uint)
]
self.check_struct(X)
s = X()
s.first = 0xdeadbeef
@@ -502,11 +293,15 @@ class StructureTestCase(unittest.TestCase):
func(s)
self.assertEqual(s.first, 0xdeadbeef)
self.assertEqual(s.second, 0xcafebabe)
got = X.in_dll(dll, "last_tfrsuv_arg")
dll.get_last_tfrsuv_arg.argtypes = ()
dll.get_last_tfrsuv_arg.restype = X
got = dll.get_last_tfrsuv_arg()
self.assertEqual(s.first, got.first)
self.assertEqual(s.second, got.second)
def _test_issue18060(self, Vector):
# Regression tests for gh-62260
# The call to atan2() should succeed if the
# class fields were correctly cloned in the
# subclasses. Otherwise, it will segfault.
@@ -577,36 +372,43 @@ class StructureTestCase(unittest.TestCase):
_fields_ = [
('data', c_ubyte * 16),
]
self.check_struct(Test2)
class Test3AParent(Structure):
_fields_ = [
('data', c_float * 2),
]
self.check_struct(Test3AParent)
class Test3A(Test3AParent):
_fields_ = [
('more_data', c_float * 2),
]
self.check_struct(Test3A)
class Test3B(Structure):
_fields_ = [
('data', c_double * 2),
]
self.check_struct(Test3B)
class Test3C(Structure):
_fields_ = [
("data", c_double * 4)
]
self.check_struct(Test3C)
class Test3D(Structure):
_fields_ = [
("data", c_double * 8)
]
self.check_struct(Test3D)
class Test3E(Structure):
_fields_ = [
("data", c_double * 9)
]
self.check_struct(Test3E)
# Tests for struct Test2
@@ -698,12 +500,15 @@ class StructureTestCase(unittest.TestCase):
self.assertEqual(result.data[i], float(i+1))
def test_38368(self):
# Regression test for gh-82549
class U(Union):
_fields_ = [
('f1', c_uint8 * 16),
('f2', c_uint16 * 8),
('f3', c_uint32 * 4),
]
self.check_union(U)
u = U()
u.f3[0] = 0x01234567
u.f3[1] = 0x89ABCDEF
@@ -719,9 +524,9 @@ class StructureTestCase(unittest.TestCase):
self.assertEqual(f2, [0x4567, 0x0123, 0xcdef, 0x89ab,
0x3210, 0x7654, 0xba98, 0xfedc])
@unittest.skipIf(True, 'Test disabled for now - see bpo-16575/bpo-16576')
@unittest.skipIf(True, 'Test disabled for now - see gh-60779/gh-60780')
def test_union_by_value(self):
# See bpo-16575
# See gh-60779
# These should mirror the structures in Modules/_ctypes/_ctypes_test.c
@@ -730,18 +535,21 @@ class StructureTestCase(unittest.TestCase):
('an_int', c_int),
('another_int', c_int),
]
self.check_struct(Nested1)
class Test4(Union):
_fields_ = [
('a_long', c_long),
('a_struct', Nested1),
]
self.check_struct(Test4)
class Nested2(Structure):
_fields_ = [
('an_int', c_int),
('a_union', Test4),
]
self.check_struct(Nested2)
class Test5(Structure):
_fields_ = [
@@ -749,6 +557,7 @@ class StructureTestCase(unittest.TestCase):
('nested', Nested2),
('another_int', c_int),
]
self.check_struct(Test5)
test4 = Test4()
dll = CDLL(_ctypes_test.__file__)
@@ -800,9 +609,9 @@ class StructureTestCase(unittest.TestCase):
self.assertEqual(test5.nested.an_int, 0)
self.assertEqual(test5.another_int, 0)
@unittest.skipIf(True, 'Test disabled for now - see bpo-16575/bpo-16576')
@unittest.skipIf(True, 'Test disabled for now - see gh-60779/gh-60780')
def test_bitfield_by_value(self):
# See bpo-16576
# See gh-60780
# These should mirror the structures in Modules/_ctypes/_ctypes_test.c
@@ -813,6 +622,7 @@ class StructureTestCase(unittest.TestCase):
('C', c_int, 3),
('D', c_int, 2),
]
self.check_struct(Test6)
test6 = Test6()
# As these are signed int fields, all are logically -1 due to sign
@@ -848,6 +658,8 @@ class StructureTestCase(unittest.TestCase):
('C', c_uint, 3),
('D', c_uint, 2),
]
self.check_struct(Test7)
test7 = Test7()
test7.A = 1
test7.B = 3
@@ -871,6 +683,7 @@ class StructureTestCase(unittest.TestCase):
('C', c_int, 3),
('D', c_int, 2),
]
self.check_union(Test8)
test8 = Test8()
with self.assertRaises(TypeError) as ctx:
@@ -881,75 +694,29 @@ class StructureTestCase(unittest.TestCase):
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 PointerMemberTestCase(unittest.TestCase):
class Mid(Base):
pass
Mid._fields_ = []
mid_ptr = POINTER(Mid)
def test(self):
# a Structure with a POINTER field
class S(Structure):
_fields_ = [("array", POINTER(c_int))]
s = S()
# We can assign arrays of the correct type
s.array = (c_int * 3)(1, 2, 3)
items = [s.array[i] for i in range(3)]
self.assertEqual(items, [1, 2, 3])
# The following are bugs, but are included here because the unittests
# also describe the current behaviour.
#
# This fails with SystemError: bad arg to internal function
# or with IndexError (with a patch I have)
s.array[0] = 42
items = [s.array[i] for i in range(3)]
self.assertEqual(items, [42, 2, 3])
s.array[0] = 1
items = [s.array[i] for i in range(3)]
self.assertEqual(items, [1, 2, 3])
def test_none_to_pointer_fields(self):
class S(Structure):
_fields_ = [("x", c_int),
("p", POINTER(c_int))]
s = S()
s.x = 12345678
s.p = None
self.assertEqual(s.x, 12345678)
class TestRecursiveStructure(unittest.TestCase):
def test_contains_itself(self):
class Recursive(Structure):
class Vector(Mid):
pass
try:
Recursive._fields_ = [("next", Recursive)]
except AttributeError as details:
self.assertIn("Structure or union cannot contain itself",
str(details))
else:
self.fail("Structure or union cannot contain itself")
vector_ptr = POINTER(Vector)
def test_vice_versa(self):
class First(Structure):
pass
class Second(Structure):
pass
First._fields_ = [("second", Second)]
try:
Second._fields_ = [("first", First)]
except AttributeError as details:
self.assertIn("_fields_ is final", str(details))
else:
self.fail("AttributeError not raised")
self.assertIsNot(base_ptr, mid_ptr)
self.assertIsNot(base_ptr, vector_ptr)
self.assertIsNot(mid_ptr, vector_ptr)
if __name__ == '__main__':

View File

@@ -19,10 +19,12 @@ for typ in [c_short, c_int, c_long, c_longlong,
c_ushort, c_uint, c_ulong, c_ulonglong]:
class X(Structure):
_pack_ = 1
_layout_ = 'ms'
_fields_ = [("pad", c_byte),
("value", typ)]
class Y(SwappedStructure):
_pack_ = 1
_layout_ = 'ms'
_fields_ = [("pad", c_byte),
("value", typ)]
structures.append(X)

View File

@@ -7,9 +7,8 @@ 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
from test.support import import_helper, thread_unsafe
class ValuesTestCase(unittest.TestCase):
@@ -18,6 +17,7 @@ class ValuesTestCase(unittest.TestCase):
_ctypes_test = import_helper.import_module("_ctypes_test")
self.ctdll = CDLL(_ctypes_test.__file__)
@thread_unsafe("static global variables aren't thread-safe")
def test_an_integer(self):
# This test checks and changes an integer stored inside the
# _ctypes_test dll/shared lib.
@@ -39,7 +39,7 @@ class ValuesTestCase(unittest.TestCase):
class PythonValuesTestCase(unittest.TestCase):
"""This test only works when python itself is a dll/shared library"""
# TODO: RUSTPYTHON
# TODO: RUSTPYTHON - requires pythonapi (Python C API)
@unittest.expectedFailure
def test_optimizeflag(self):
# This test accesses the Py_OptimizeFlag integer, which is
@@ -48,8 +48,9 @@ class PythonValuesTestCase(unittest.TestCase):
opt = c_int.in_dll(pythonapi, "Py_OptimizeFlag").value
self.assertEqual(opt, sys.flags.optimize)
# TODO: RUSTPYTHON
# TODO: RUSTPYTHON - requires pythonapi (Python C API)
@unittest.expectedFailure
@thread_unsafe('overrides frozen modules')
def test_frozentable(self):
# Python exports a PyImport_FrozenModules symbol. This is a
# pointer to an array of struct _frozen entries. The end of the
@@ -100,8 +101,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")

View 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
@@ -14,11 +13,11 @@ from ._support import Py_TPFLAGS_DISALLOW_INSTANTIATION, Py_TPFLAGS_IMMUTABLETYP
@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test')
class FunctionCallTestCase(unittest.TestCase):
# TODO: RUSTPYTHON: SEH not implemented, crashes with STATUS_ACCESS_VIOLATION
@unittest.skip("TODO: RUSTPYTHON")
@unittest.skipUnless('MSC' in sys.version, "SEH only supported by MSC")
@unittest.skipIf(sys.executable.lower().endswith('_d.exe'),
"SEH not enabled in debug builds")
# TODO: RUSTPYTHON - SEH not implemented
@unittest.skipIf("RustPython" in sys.version, "SEH not implemented in RustPython")
def test_SEH(self):
# Disable faulthandler to prevent logging the warning:
# "Windows fatal exception: access violation"
@@ -67,15 +66,16 @@ class TestWintypes(unittest.TestCase):
sizeof(c_void_p))
def test_COMError(self):
from _ctypes import COMError
from ctypes import COMError
if support.HAVE_DOCSTRINGS:
self.assertEqual(COMError.__doc__,
"Raised when a COM method call failed.")
ex = COMError(-1, "text", ("details",))
ex = COMError(-1, "text", ("descr", "source", "helpfile", 0, "progid"))
self.assertEqual(ex.hresult, -1)
self.assertEqual(ex.text, "text")
self.assertEqual(ex.details, ("details",))
self.assertEqual(ex.details,
("descr", "source", "helpfile", 0, "progid"))
self.assertEqual(COMError.mro(),
[COMError, Exception, BaseException, object])
@@ -146,8 +146,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__':

View File

@@ -9,8 +9,7 @@ if sys.platform != "win32":
raise unittest.SkipTest("Windows-specific test")
from _ctypes import COMError, CopyComPointer
from ctypes import HRESULT
from ctypes import COMError, CopyComPointer, HRESULT
COINIT_APARTMENTTHREADED = 0x2
@@ -158,8 +157,7 @@ class ForeignFunctionsThatWillCallComMethodsTests(unittest.TestCase):
self.assertEqual(0, ppst.Release())
# TODO: RUSTPYTHON - COM iid parameter handling not implemented
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - COM iid parameter handling not implemented
def test_with_paramflags_and_iid(self):
class IUnknown(c_void_p):
QueryInterface = proto_query_interface(None, IID_IUnknown)

1004
Lib/test/test_stable_abi_ctypes.py generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -228,6 +228,11 @@ impl FormatCode {
let mut arg_count = 0usize;
let mut codes = vec![];
while chars.peek().is_some() {
// Skip whitespace before repeat count or format char
while let Some(b' ' | b'\t' | b'\n' | b'\r') = chars.peek() {
chars.next();
}
// determine repeat operator:
let repeat = match chars.peek() {
Some(b'0'..=b'9') => {
@@ -246,11 +251,6 @@ impl FormatCode {
_ => 1,
};
// Skip whitespace (Python ignores whitespace in format strings)
while let Some(b' ' | b'\t' | b'\n' | b'\r') = chars.peek() {
chars.next();
}
// determine format char:
let c = match chars.next() {
Some(c) => c,

View File

@@ -322,7 +322,12 @@ impl PyObject {
match op {
PyComparisonOp::Eq => Ok(Either::B(self.is(&other))),
PyComparisonOp::Ne => Ok(Either::B(!self.is(&other))),
_ => Err(vm.new_unsupported_bin_op_error(self, other, op.operator_token())),
_ => Err(vm.new_type_error(format!(
"'{}' not supported between instances of '{}' and '{}'",
op.operator_token(),
self.class().name(),
other.class().name()
))),
}
}
#[inline(always)]

View File

@@ -70,18 +70,26 @@ impl Py<PyType> {
}
impl PyType {
/// Check if StgInfo is already initialized - prevent double initialization
/// Check if StgInfo is already initialized.
/// Raises SystemError if already initialized.
pub(crate) fn check_not_initialized(&self, vm: &VirtualMachine) -> PyResult<()> {
if let Some(stg_info) = self.get_type_data::<StgInfo>()
&& stg_info.initialized
{
return Err(vm.new_exception_msg(
vm.ctx.exceptions.system_error.to_owned(),
format!("StgInfo of '{}' is already initialized.", self.name()),
format!("class \"{}\" already initialized", self.name()),
));
}
Ok(())
}
/// Check if StgInfo is already initialized, returning true if so.
/// Unlike check_not_initialized, does not raise an error.
pub(crate) fn is_initialized(&self) -> bool {
self.get_type_data::<StgInfo>()
.is_some_and(|stg_info| stg_info.initialized)
}
}
// Dynamic type check helpers for PyCData
@@ -988,6 +996,11 @@ pub(crate) mod _ctypes {
super::function::INTERNAL_CAST_ADDR
}
#[pyattr]
fn _memoryview_at_addr(_vm: &VirtualMachine) -> usize {
super::function::INTERNAL_MEMORYVIEW_AT_ADDR
}
#[pyfunction]
fn _cast(
obj: PyObjectRef,
@@ -1293,6 +1306,7 @@ pub(crate) mod _ctypes {
structure::PyCStructType::make_class(ctx);
union::PyCUnionType::make_class(ctx);
function::PyCFuncPtrType::make_class(ctx);
function::RawMemoryBuffer::make_class(ctx);
extend_module!(vm, module, {
"_CData" => PyCData::make_class(ctx),

View File

@@ -301,6 +301,16 @@ impl Initializer for PyCArrayType {
#[pyclass(flags(IMMUTABLETYPE), with(Initializer, AsNumber))]
impl PyCArrayType {
#[pygetset(name = "__pointer_type__")]
fn pointer_type(zelf: PyTypeRef, vm: &VirtualMachine) -> PyResult {
super::base::pointer_type_get(&zelf, vm)
}
#[pygetset(name = "__pointer_type__", setter)]
fn set_pointer_type(zelf: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
super::base::pointer_type_set(&zelf, value, vm)
}
#[pymethod]
fn from_param(zelf: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// zelf is the array type class that from_param was called on
@@ -777,8 +787,9 @@ impl PyCArray {
let (ptr_val, converted) = if value.is(&vm.ctx.none) {
(0usize, None)
} else if let Some(bytes) = value.downcast_ref::<PyBytes>() {
let (c, ptr) = super::base::ensure_z_null_terminated(bytes, vm);
(ptr, Some(c))
let (kept_alive, ptr) = super::base::ensure_z_null_terminated(bytes, vm);
zelf.0.keep_alive(index, kept_alive);
(ptr, Some(value.to_owned()))
} else if let Ok(int_val) = value.try_index(vm) {
(int_val.as_bigint().to_usize().unwrap_or(0), None)
} else {

View File

@@ -1,9 +1,9 @@
use super::array::{WCHAR_SIZE, wchar_from_bytes, wchar_to_bytes};
use crate::builtins::{PyBytes, PyDict, PyMemoryView, PyStr, PyType, PyTypeRef};
use crate::builtins::{PyBytes, PyDict, PyMemoryView, PyStr, PyTuple, PyType, PyTypeRef};
use crate::class::StaticType;
use crate::function::{ArgBytesLike, OptionalArg, PySetterValue};
use crate::protocol::{BufferMethods, PyBuffer};
use crate::types::{GetDescriptor, Representable};
use crate::types::{Constructor, GetDescriptor, Representable};
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine,
};
@@ -97,6 +97,9 @@ pub struct StgInfo {
// FFI field types for structure/union passing (inherited from base class)
pub ffi_field_types: Vec<libffi::middle::Type>,
// Cached pointer type (non-inheritable via descriptor)
pub pointer_type: Option<PyObjectRef>,
}
// StgInfo is stored in type_data which requires Send + Sync.
@@ -141,6 +144,7 @@ impl Default for StgInfo {
paramfunc: ParamFunc::None,
big_endian: cfg!(target_endian = "big"), // native endian by default
ffi_field_types: Vec::new(),
pointer_type: None,
}
}
}
@@ -161,6 +165,7 @@ impl StgInfo {
paramfunc: ParamFunc::None,
big_endian: cfg!(target_endian = "big"), // native endian by default
ffi_field_types: Vec::new(),
pointer_type: None,
}
}
@@ -212,6 +217,7 @@ impl StgInfo {
paramfunc: ParamFunc::Array,
big_endian: cfg!(target_endian = "big"), // native endian by default
ffi_field_types: Vec::new(),
pointer_type: None,
}
}
@@ -293,6 +299,34 @@ impl StgInfo {
}
}
/// __pointer_type__ getter for ctypes metaclasses.
/// Reads from StgInfo.pointer_type (non-inheritable).
pub(super) fn pointer_type_get(zelf: &Py<PyType>, vm: &VirtualMachine) -> PyResult {
zelf.stg_info_opt()
.and_then(|info| info.pointer_type.clone())
.ok_or_else(|| {
vm.new_attribute_error(format!(
"type {} has no attribute '__pointer_type__'",
zelf.name()
))
})
}
/// __pointer_type__ setter for ctypes metaclasses.
/// Writes to StgInfo.pointer_type (non-inheritable).
pub(super) fn pointer_type_set(
zelf: &Py<PyType>,
value: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<()> {
if let Some(mut info) = zelf.get_type_data_mut::<StgInfo>() {
info.pointer_type = Some(value);
Ok(())
} else {
Err(vm.new_attribute_error(format!("cannot set __pointer_type__ on {}", zelf.name())))
}
}
/// Get PEP3118 format string for a field type
/// Returns the format string considering byte order
pub(super) fn get_field_format(
@@ -380,25 +414,20 @@ fn vec_to_bytes<T>(vec: Vec<T>) -> Vec<u8> {
unsafe { Vec::from_raw_parts(ptr, len, cap) }
}
/// Ensure PyBytes is null-terminated. Returns (PyBytes to keep, pointer).
/// If already contains null, returns original. Otherwise creates new with null appended.
/// Ensure PyBytes data is null-terminated. Returns (kept_alive_obj, pointer).
/// The caller must keep the returned object alive to keep the pointer valid.
pub(super) fn ensure_z_null_terminated(
bytes: &PyBytes,
vm: &VirtualMachine,
) -> (PyObjectRef, usize) {
let data = bytes.as_bytes();
if data.contains(&0) {
// Already has null, use original
let original: PyObjectRef = vm.ctx.new_bytes(data.to_vec()).into();
(original, data.as_ptr() as usize)
} else {
// Create new with null appended
let mut buffer = data.to_vec();
let mut buffer = data.to_vec();
if !buffer.ends_with(&[0]) {
buffer.push(0);
let ptr = buffer.as_ptr() as usize;
let new_bytes: PyObjectRef = vm.ctx.new_bytes(buffer).into();
(new_bytes, ptr)
}
let ptr = buffer.as_ptr() as usize;
let kept_alive: PyObjectRef = vm.ctx.new_bytes(buffer).into();
(kept_alive, ptr)
}
/// Convert str to null-terminated wchar_t buffer. Returns (PyBytes holder, pointer).
@@ -434,6 +463,11 @@ pub struct PyCData {
pub objects: PyRwLock<Option<PyObjectRef>>,
/// number of references we need (b_length)
pub length: AtomicCell<usize>,
/// References kept alive but not visible in _objects.
/// Used for null-terminated c_char_p buffer copies, since
/// RustPython's PyBytes lacks CPython's internal trailing null.
/// Keyed by unique_key (hierarchical) so nested fields don't collide.
pub(super) kept_refs: PyRwLock<std::collections::HashMap<String, PyObjectRef>>,
}
impl PyCData {
@@ -446,6 +480,7 @@ impl PyCData {
index: AtomicCell::new(0),
objects: PyRwLock::new(None),
length: AtomicCell::new(stg_info.length),
kept_refs: PyRwLock::new(std::collections::HashMap::new()),
}
}
@@ -458,6 +493,7 @@ impl PyCData {
index: AtomicCell::new(0),
objects: PyRwLock::new(objects),
length: AtomicCell::new(0),
kept_refs: PyRwLock::new(std::collections::HashMap::new()),
}
}
@@ -474,6 +510,7 @@ impl PyCData {
index: AtomicCell::new(0),
objects: PyRwLock::new(objects),
length: AtomicCell::new(length),
kept_refs: PyRwLock::new(std::collections::HashMap::new()),
}
}
@@ -494,6 +531,7 @@ impl PyCData {
index: AtomicCell::new(0),
objects: PyRwLock::new(None),
length: AtomicCell::new(0),
kept_refs: PyRwLock::new(std::collections::HashMap::new()),
}
}
@@ -516,6 +554,7 @@ impl PyCData {
index: AtomicCell::new(idx),
objects: PyRwLock::new(None),
length: AtomicCell::new(length),
kept_refs: PyRwLock::new(std::collections::HashMap::new()),
}
}
@@ -542,6 +581,7 @@ impl PyCData {
index: AtomicCell::new(idx),
objects: PyRwLock::new(None),
length: AtomicCell::new(0),
kept_refs: PyRwLock::new(std::collections::HashMap::new()),
}
}
@@ -576,6 +616,7 @@ impl PyCData {
index: AtomicCell::new(0),
objects: PyRwLock::new(Some(objects_dict.into())),
length: AtomicCell::new(length),
kept_refs: PyRwLock::new(std::collections::HashMap::new()),
}
}
@@ -797,6 +838,22 @@ impl PyCData {
Ok(())
}
/// Keep a reference alive without exposing it in _objects.
/// Walks up to root object (same as keep_ref) so the reference
/// lives as long as the owning ctypes object.
/// Uses unique_key (hierarchical) so nested fields don't collide.
pub fn keep_alive(&self, index: usize, obj: PyObjectRef) {
let key = self.unique_key(index);
if let Some(base_obj) = self.base.read().clone() {
let root = Self::find_root_object(&base_obj);
if let Some(cdata) = root.downcast_ref::<PyCData>() {
cdata.kept_refs.write().insert(key, obj);
return;
}
}
self.kept_refs.write().insert(key, obj);
}
/// Find the root object (one without a base) by walking up the base chain
fn find_root_object(obj: &PyObject) -> PyObjectRef {
// Try to get base from different ctypes types
@@ -942,12 +999,68 @@ impl PyCData {
return Ok(());
}
// For array fields with tuple/list input, instantiate the array type
// and unpack elements as positional args (Array_init expects *args)
if let Some(proto_type) = proto.downcast_ref::<PyType>()
&& let Some(stg) = proto_type.stg_info_opt()
&& stg.element_type.is_some()
{
let items: Option<Vec<PyObjectRef>> =
if let Some(tuple) = value.downcast_ref::<PyTuple>() {
Some(tuple.to_vec())
} else {
value
.downcast_ref::<crate::builtins::PyList>()
.map(|list| list.borrow_vec().to_vec())
};
if let Some(items) = items {
let array_obj = proto_type.as_object().call(items, vm).map_err(|e| {
// Wrap errors in RuntimeError with type name prefix
let type_name = proto_type.name().to_string();
let exc_name = e.class().name().to_string();
let exc_args = e.args();
let exc_msg = exc_args
.first()
.and_then(|a| a.downcast_ref::<PyStr>().map(|s| s.to_string()))
.unwrap_or_default();
vm.new_runtime_error(format!("({type_name}) {exc_name}: {exc_msg}"))
})?;
if let Some(arr) = array_obj.downcast_ref::<super::array::PyCArray>() {
let arr_buffer = arr.0.buffer.read();
let len = core::cmp::min(arr_buffer.len(), size);
self.write_bytes_at_offset(offset, &arr_buffer[..len]);
drop(arr_buffer);
self.keep_ref(index, array_obj, vm)?;
return Ok(());
}
}
}
// Get field type code for special handling
let field_type_code = proto
.get_attr("_type_", vm)
.ok()
.and_then(|attr| attr.downcast_ref::<PyStr>().map(|s| s.to_string()));
// c_char_p (z type) with bytes: store original in _objects, keep
// null-terminated copy alive separately for the pointer.
if field_type_code.as_deref() == Some("z")
&& let Some(bytes_val) = value.downcast_ref::<PyBytes>()
{
let (kept_alive, ptr) = ensure_z_null_terminated(bytes_val, vm);
let mut result = vec![0u8; size];
let addr_bytes = ptr.to_ne_bytes();
let len = core::cmp::min(addr_bytes.len(), size);
result[..len].copy_from_slice(&addr_bytes[..len]);
if needs_swap {
result.reverse();
}
self.write_bytes_at_offset(offset, &result);
self.keep_ref(index, value, vm)?;
self.keep_alive(index, kept_alive);
return Ok(());
}
let (mut bytes, converted_value) = if let Some(type_code) = &field_type_code {
PyCField::value_to_bytes_for_type(type_code, &value, size, vm)?
} else {
@@ -1111,7 +1224,7 @@ impl PyCData {
// CDataType_methods - shared across all ctypes types
#[pyclassmethod]
fn from_buffer(
pub(super) fn from_buffer(
cls: PyTypeRef,
source: PyObjectRef,
offset: OptionalArg<isize>,
@@ -1122,7 +1235,7 @@ impl PyCData {
}
#[pyclassmethod]
fn from_buffer_copy(
pub(super) fn from_buffer_copy(
cls: PyTypeRef,
source: ArgBytesLike,
offset: OptionalArg<isize>,
@@ -1134,7 +1247,7 @@ impl PyCData {
}
#[pyclassmethod]
fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult {
pub(super) fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult {
let size = {
let stg_info = cls.stg_info(vm)?;
stg_info.size
@@ -1150,7 +1263,7 @@ impl PyCData {
}
#[pyclassmethod]
fn in_dll(
pub(super) fn in_dll(
cls: PyTypeRef,
dll: PyObjectRef,
name: crate::builtins::PyStrRef,
@@ -1217,78 +1330,84 @@ impl PyCData {
#[pyclass(name = "CField", module = "_ctypes")]
#[derive(Debug, PyPayload)]
pub struct PyCField {
/// Field name
pub(crate) name: String,
/// Byte offset of the field within the structure/union
pub(crate) offset: isize,
/// Encoded size: for bitfields (bit_size << 16) | bit_offset, otherwise byte size
pub(crate) size: isize,
/// Byte size of the underlying type
pub(crate) byte_size_val: isize,
/// Index into PyCData's object array
pub(crate) index: usize,
/// The ctypes type for this field
pub(crate) proto: PyTypeRef,
/// Flag indicating if the field is anonymous (MakeAnonFields sets this)
pub(crate) anonymous: bool,
}
#[inline(always)]
const fn num_bits(size: isize) -> isize {
size >> 16
}
#[inline(always)]
const fn field_size(size: isize) -> isize {
size & 0xFFFF
}
#[inline(always)]
const fn is_bitfield(size: isize) -> bool {
(size >> 16) != 0
/// Bitfield size in bits (0 for non-bitfield)
pub(crate) bitfield_size: u16,
/// Bit offset within the storage unit (only meaningful for bitfields)
pub(crate) bit_offset_val: u16,
}
impl PyCField {
/// Create a new CField descriptor (non-bitfield)
pub fn new(proto: PyTypeRef, offset: isize, size: isize, index: usize) -> Self {
pub fn new(
name: String,
proto: PyTypeRef,
offset: isize,
byte_size: isize,
index: usize,
) -> Self {
Self {
name,
offset,
size,
byte_size_val: byte_size,
index,
proto,
anonymous: false,
bitfield_size: 0,
bit_offset_val: 0,
}
}
/// Create a new CField descriptor for a bitfield
#[allow(dead_code)]
pub fn new_bitfield(
name: String,
proto: PyTypeRef,
offset: isize,
bit_size: u16,
byte_size: isize,
bitfield_size: u16,
bit_offset: u16,
index: usize,
) -> Self {
let encoded_size = ((bit_size as isize) << 16) | (bit_offset as isize);
Self {
name,
offset,
size: encoded_size,
byte_size_val: byte_size,
index,
proto,
anonymous: false,
bitfield_size,
bit_offset_val: bit_offset,
}
}
/// Get the actual byte size (for non-bitfields) or bit storage size (for bitfields)
pub fn byte_size(&self) -> usize {
field_size(self.size) as usize
/// Get the byte size of the field's underlying type
pub fn get_byte_size(&self) -> usize {
self.byte_size_val as usize
}
/// Create a new CField from an existing field with adjusted offset and index
/// Used by MakeFields to promote anonymous fields
pub fn new_from_field(fdescr: &PyCField, index_offset: usize, offset_delta: isize) -> Self {
Self {
name: fdescr.name.clone(),
offset: fdescr.offset + offset_delta,
size: fdescr.size,
byte_size_val: fdescr.byte_size_val,
index: fdescr.index + index_offset,
proto: fdescr.proto.clone(),
anonymous: false, // promoted fields are not anonymous themselves
bitfield_size: fdescr.bitfield_size,
bit_offset_val: fdescr.bit_offset_val,
}
}
@@ -1298,6 +1417,107 @@ impl PyCField {
}
}
impl Constructor for PyCField {
type Args = crate::function::FuncArgs;
fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
// PyCField_new_impl: requires _internal_use=True
let internal_use = if let Some(v) = args.kwargs.get("_internal_use") {
v.clone().try_to_bool(vm)?
} else {
false
};
if !internal_use {
return Err(vm.new_type_error(
"CField is not intended to be used directly; use it via Structure or Union fields"
.to_string(),
));
}
let name: String = args
.kwargs
.get("name")
.ok_or_else(|| vm.new_type_error("missing required argument: 'name'"))?
.try_to_value(vm)?;
let field_type: PyTypeRef = args
.kwargs
.get("type")
.ok_or_else(|| vm.new_type_error("missing required argument: 'type'"))?
.clone()
.downcast()
.map_err(|_| vm.new_type_error("'type' must be a ctypes type"))?;
let byte_size: isize = args
.kwargs
.get("byte_size")
.ok_or_else(|| vm.new_type_error("missing required argument: 'byte_size'"))?
.try_to_value(vm)?;
let byte_offset: isize = args
.kwargs
.get("byte_offset")
.ok_or_else(|| vm.new_type_error("missing required argument: 'byte_offset'"))?
.try_to_value(vm)?;
let index: usize = args
.kwargs
.get("index")
.ok_or_else(|| vm.new_type_error("missing required argument: 'index'"))?
.try_to_value(vm)?;
// Validate byte_size matches the type
let type_size = super::base::get_field_size(field_type.as_object(), vm)? as isize;
if byte_size != type_size {
return Err(vm.new_value_error(format!(
"byte_size {} does not match type size {}",
byte_size, type_size
)));
}
let bit_size_val: Option<isize> = args
.kwargs
.get("bit_size")
.map(|v| v.try_to_value(vm))
.transpose()?;
let bit_offset_val: Option<isize> = args
.kwargs
.get("bit_offset")
.map(|v| v.try_to_value(vm))
.transpose()?;
if let Some(bs) = bit_size_val {
if bs < 0 {
return Err(vm.new_value_error("number of bits invalid for bit field".to_string()));
}
let bo = bit_offset_val.unwrap_or(0);
if bo < 0 {
return Err(vm.new_value_error("bit_offset must be >= 0".to_string()));
}
let type_bits = byte_size * 8;
if bo + bs > type_bits {
return Err(vm.new_value_error(format!(
"bit field '{}' overflows its type ({} + {} > {})",
name, bo, bs, type_bits
)));
}
Ok(Self::new_bitfield(
name,
field_type,
byte_offset,
byte_size,
bs as u16,
bo as u16,
index,
))
} else {
Ok(Self::new(name, field_type, byte_offset, byte_size, index))
}
}
}
impl Representable for PyCField {
fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
// Get type name from proto (which is always PyTypeRef)
@@ -1305,17 +1525,15 @@ impl Representable for PyCField {
// Bitfield: <Field type=TYPE, ofs=OFFSET:BIT_OFFSET, bits=NUM_BITS>
// Regular: <Field type=TYPE, ofs=OFFSET, size=SIZE>
if is_bitfield(zelf.size) {
let bit_offset = field_size(zelf.size);
let bits = num_bits(zelf.size);
if zelf.bitfield_size > 0 {
Ok(format!(
"<Field type={}, ofs={}:{}, bits={}>",
tp_name, zelf.offset, bit_offset, bits
tp_name, zelf.offset, zelf.bit_offset_val, zelf.bitfield_size
))
} else {
Ok(format!(
"<Field type={}, ofs={}, size={}>",
tp_name, zelf.offset, zelf.size
tp_name, zelf.offset, zelf.byte_size_val
))
}
}
@@ -1340,7 +1558,7 @@ impl GetDescriptor for PyCField {
};
let offset = zelf.offset as usize;
let size = zelf.byte_size();
let size = zelf.get_byte_size();
// Get PyCData from obj (works for both Structure and Union)
let cdata = PyCField::get_cdata_from_obj(&obj, vm)?;
@@ -1468,15 +1686,8 @@ impl PyCField {
Ok((f.to_ne_bytes().to_vec(), None))
}
"z" => {
// c_char_p: store pointer to null-terminated bytes
if let Some(bytes) = value.downcast_ref::<PyBytes>() {
let (converted, ptr) = ensure_z_null_terminated(bytes, vm);
let mut result = vec![0u8; size];
let addr_bytes = ptr.to_ne_bytes();
let len = core::cmp::min(addr_bytes.len(), size);
result[..len].copy_from_slice(&addr_bytes[..len]);
return Ok((result, Some(converted)));
}
// c_char_p with bytes is handled in set_field before this call.
// This handles integer address and None cases.
// Integer address
if let Ok(int_val) = value.try_index(vm) {
let v = int_val.as_bigint().to_usize().unwrap_or(0);
@@ -1583,10 +1794,7 @@ impl PyCField {
}
}
#[pyclass(
flags(DISALLOW_INSTANTIATION, IMMUTABLETYPE),
with(Representable, GetDescriptor)
)]
#[pyclass(flags(IMMUTABLETYPE), with(Representable, GetDescriptor, Constructor))]
impl PyCField {
/// Get PyCData from object (works for both Structure and Union)
fn get_cdata_from_obj<'a>(obj: &'a PyObjectRef, vm: &VirtualMachine) -> PyResult<&'a PyCData> {
@@ -1615,7 +1823,7 @@ impl PyCField {
.ok_or_else(|| vm.new_type_error("expected CField"))?;
let offset = zelf.offset as usize;
let size = zelf.byte_size();
let size = zelf.get_byte_size();
// Get PyCData from obj (works for both Structure and Union)
let cdata = Self::get_cdata_from_obj(&obj, vm)?;
@@ -1650,14 +1858,65 @@ impl PyCField {
}
}
#[pygetset]
fn name(&self) -> String {
self.name.clone()
}
#[pygetset(name = "type")]
fn type_(&self) -> PyTypeRef {
self.proto.clone()
}
#[pygetset]
fn offset(&self) -> isize {
self.offset
}
#[pygetset]
fn byte_offset(&self) -> isize {
self.offset
}
#[pygetset]
fn size(&self) -> isize {
self.size
// Legacy: encode as (bitfield_size << 16) | bit_offset for bitfields
if self.bitfield_size > 0 {
((self.bitfield_size as isize) << 16) | (self.bit_offset_val as isize)
} else {
self.byte_size_val
}
}
#[pygetset]
fn byte_size(&self) -> isize {
self.byte_size_val
}
#[pygetset]
fn bit_offset(&self) -> isize {
self.bit_offset_val as isize
}
#[pygetset]
fn bit_size(&self, vm: &VirtualMachine) -> PyObjectRef {
if self.bitfield_size > 0 {
vm.ctx.new_int(self.bitfield_size).into()
} else {
// Non-bitfield: bit_size = byte_size * 8
let byte_size = self.byte_size_val as i128;
vm.ctx.new_int(byte_size * 8).into()
}
}
#[pygetset]
fn is_bitfield(&self) -> bool {
self.bitfield_size > 0
}
#[pygetset]
fn is_anonymous(&self) -> bool {
self.anonymous
}
}
@@ -2139,7 +2398,12 @@ pub(super) fn set_or_init_stginfo(type_ref: &PyType, stg_info: StgInfo) {
if type_ref.init_type_data(stg_info.clone()).is_err()
&& let Some(mut existing) = type_ref.get_type_data_mut::<StgInfo>()
{
// Preserve pointer_type cache across StgInfo replacement
let old_pointer_type = existing.pointer_type.take();
*existing = stg_info;
if existing.pointer_type.is_none() {
existing.pointer_type = old_pointer_type;
}
}
}

View File

@@ -31,6 +31,7 @@ use rustpython_common::lock::PyRwLock;
pub(super) const INTERNAL_CAST_ADDR: usize = 1;
pub(super) const INTERNAL_STRING_AT_ADDR: usize = 2;
pub(super) const INTERNAL_WSTRING_AT_ADDR: usize = 3;
pub(super) const INTERNAL_MEMORYVIEW_AT_ADDR: usize = 4;
// Thread-local errno storage for ctypes
std::thread_local! {
@@ -512,7 +513,17 @@ impl Initializer for PyCFuncPtrType {
}
#[pyclass(flags(IMMUTABLETYPE), with(Initializer))]
impl PyCFuncPtrType {}
impl PyCFuncPtrType {
#[pygetset(name = "__pointer_type__")]
fn pointer_type(zelf: PyTypeRef, vm: &VirtualMachine) -> PyResult {
super::base::pointer_type_get(&zelf, vm)
}
#[pygetset(name = "__pointer_type__", setter)]
fn set_pointer_type(zelf: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
super::base::pointer_type_set(&zelf, value, vm)
}
}
/// PyCFuncPtr - Function pointer instance
/// Saved in _base.buffer
@@ -561,6 +572,16 @@ impl Debug for PyCFuncPtr {
/// Extract pointer value from a ctypes argument (c_void_p conversion)
fn extract_ptr_from_arg(arg: &PyObject, vm: &VirtualMachine) -> PyResult<usize> {
// Try CArgObject first - extract the wrapped pointer value, applying offset
if let Some(carg) = arg.downcast_ref::<super::_ctypes::CArgObject>() {
if carg.offset != 0
&& let Some(cdata) = carg.obj.downcast_ref::<PyCData>()
{
let base = cdata.buffer.read().as_ptr() as isize;
return Ok((base + carg.offset) as usize);
}
return extract_ptr_from_arg(&carg.obj, vm);
}
// Try to get pointer value from various ctypes types
if let Some(ptr) = arg.downcast_ref::<PyCPointer>() {
return Ok(ptr.get_ptr_value());
@@ -655,6 +676,68 @@ fn wstring_at_impl(ptr: usize, size: isize, vm: &VirtualMachine) -> PyResult {
}
}
/// A buffer wrapping raw memory at a given pointer, for zero-copy memoryview.
#[pyclass(name = "_RawMemoryBuffer", module = "_ctypes")]
#[derive(Debug, PyPayload)]
pub(super) struct RawMemoryBuffer {
ptr: *const u8,
size: usize,
readonly: bool,
}
// SAFETY: The caller ensures the pointer remains valid
unsafe impl Send for RawMemoryBuffer {}
unsafe impl Sync for RawMemoryBuffer {}
static RAW_MEMORY_BUFFER_METHODS: crate::protocol::BufferMethods = crate::protocol::BufferMethods {
obj_bytes: |buffer| {
let raw = buffer.obj_as::<RawMemoryBuffer>();
let slice = unsafe { core::slice::from_raw_parts(raw.ptr, raw.size) };
rustpython_common::borrow::BorrowedValue::Ref(slice)
},
obj_bytes_mut: |buffer| {
let raw = buffer.obj_as::<RawMemoryBuffer>();
let slice = unsafe { core::slice::from_raw_parts_mut(raw.ptr as *mut u8, raw.size) };
rustpython_common::borrow::BorrowedValueMut::RefMut(slice)
},
release: |_| {},
retain: |_| {},
};
#[pyclass(with(AsBuffer))]
impl RawMemoryBuffer {}
impl AsBuffer for RawMemoryBuffer {
fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> {
Ok(PyBuffer::new(
zelf.to_owned().into(),
BufferDescriptor::simple(zelf.size, zelf.readonly),
&RAW_MEMORY_BUFFER_METHODS,
))
}
}
/// memoryview_at implementation - create a memoryview from memory at ptr
fn memoryview_at_impl(ptr: usize, size: isize, readonly: bool, vm: &VirtualMachine) -> PyResult {
use crate::builtins::PyMemoryView;
if ptr == 0 {
return Err(vm.new_value_error("NULL pointer access"));
}
if size < 0 {
return Err(vm.new_value_error("negative size"));
}
let len = size as usize;
let raw_buf = RawMemoryBuffer {
ptr: ptr as *const u8,
size: len,
readonly,
}
.into_pyobject(vm);
let mv = PyMemoryView::from_object(&raw_buf, vm)?;
Ok(mv.into_pyobject(vm))
}
// cast_check_pointertype
fn cast_check_pointertype(ctype: &PyObject, vm: &VirtualMachine) -> bool {
use super::pointer::PyCPointerType;
@@ -1064,6 +1147,25 @@ fn handle_internal_func(addr: usize, args: &FuncArgs, vm: &VirtualMachine) -> Op
}));
}
if addr == INTERNAL_MEMORYVIEW_AT_ADDR {
let result: PyResult<(PyObjectRef, PyObjectRef, Option<PyObjectRef>)> =
args.clone().bind(vm);
return Some(result.and_then(|(ptr_arg, size_arg, readonly_arg)| {
let ptr = extract_ptr_from_arg(&ptr_arg, vm)?;
let size_int = size_arg.try_int(vm)?;
let size = size_int
.as_bigint()
.to_isize()
.ok_or_else(|| vm.new_value_error("size too large"))?;
let readonly = readonly_arg
.and_then(|r| r.try_int(vm).ok())
.and_then(|i| i.as_bigint().to_i32())
.unwrap_or(0)
!= 0;
memoryview_at_impl(ptr, size, readonly, vm)
}));
}
None
}
@@ -2091,7 +2193,7 @@ unsafe extern "C" fn thunk_callback(
.map(|s| s.to_string())
.unwrap_or_else(|_| "<unknown>".to_string());
let msg = format!(
"Exception ignored on calling ctypes callback function {}",
"Exception ignored while calling ctypes callback function {}",
repr
);
vm.run_unraisable(exc.clone(), Some(msg), vm.ctx.none());

View File

@@ -27,16 +27,24 @@ impl Initializer for PyCPointerType {
.downcast()
.map_err(|_| vm.new_type_error("expected type"))?;
new_type.check_not_initialized(vm)?;
if new_type.is_initialized() {
return Ok(());
}
// Get the _type_ attribute (element type)
// PyCPointerType_init gets the element type from _type_ attribute
let proto = new_type
.as_object()
.get_attr("_type_", vm)
.ok()
.and_then(|obj| obj.downcast::<PyType>().ok());
// Validate that _type_ has storage info (is a ctypes type)
if let Some(ref proto_type) = proto
&& proto_type.stg_info_opt().is_none()
{
return Err(vm.new_type_error(format!("{} must have storage info", proto_type.name())));
}
// Initialize StgInfo for pointer type
let pointer_size = core::mem::size_of::<usize>();
let mut stg_info = StgInfo::new(pointer_size, pointer_size);
@@ -62,12 +70,31 @@ impl Initializer for PyCPointerType {
let _ = new_type.init_type_data(stg_info);
// Cache: set target_type.__pointer_type__ = self (via StgInfo, not as inheritable attr)
if let Ok(type_attr) = new_type.as_object().get_attr("_type_", vm)
&& let Ok(target_type) = type_attr.downcast::<PyType>()
&& let Some(mut target_info) = target_type.get_type_data_mut::<StgInfo>()
{
let zelf_obj: PyObjectRef = zelf.into();
target_info.pointer_type = Some(zelf_obj);
}
Ok(())
}
}
#[pyclass(flags(IMMUTABLETYPE), with(AsNumber, Initializer))]
#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(AsNumber, Initializer))]
impl PyCPointerType {
#[pygetset(name = "__pointer_type__")]
fn pointer_type(zelf: PyTypeRef, vm: &VirtualMachine) -> PyResult {
super::base::pointer_type_get(&zelf, vm)
}
#[pygetset(name = "__pointer_type__", setter)]
fn set_pointer_type(zelf: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
super::base::pointer_type_set(&zelf, value, vm)
}
#[pymethod]
fn from_param(zelf: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// zelf is the pointer type class that from_param was called on
@@ -182,7 +209,12 @@ impl PyCPointerType {
}
// 4. Set _type_ attribute on the pointer type
zelf.as_object().set_attr("_type_", typ_type, vm)?;
zelf.as_object().set_attr("_type_", typ_type.clone(), vm)?;
// 5. Cache: set target_type.__pointer_type__ = self (via StgInfo)
if let Some(mut target_info) = typ_type.get_type_data_mut::<StgInfo>() {
target_info.pointer_type = Some(zelf.into());
}
Ok(())
}
@@ -598,11 +630,12 @@ impl PyCPointer {
if type_code.as_deref() == Some("z")
&& let Some(bytes) = value.downcast_ref::<PyBytes>()
{
let (converted, ptr_val) = super::base::ensure_z_null_terminated(bytes, vm);
let (kept_alive, ptr_val) = super::base::ensure_z_null_terminated(bytes, vm);
unsafe {
*(addr as *mut usize) = ptr_val;
}
return zelf.0.keep_ref(index as usize, converted, vm);
zelf.0.keep_alive(index as usize, kept_alive);
return zelf.0.keep_ref(index as usize, value.clone(), vm);
} else if type_code.as_deref() == Some("Z")
&& let Some(s) = value.downcast_ref::<PyStr>()
{

View File

@@ -18,8 +18,12 @@ use core::fmt::Debug;
use num_traits::ToPrimitive;
/// Valid type codes for ctypes simple types
#[cfg(windows)]
// spell-checker: disable-next-line
pub(super) const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQPXOv?g";
#[cfg(not(windows))]
// spell-checker: disable-next-line
pub(super) const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQPOv?g";
/// Convert ctypes type code to PEP 3118 format code.
/// Some ctypes codes need to be mapped to standard-size codes based on platform.
@@ -235,6 +239,16 @@ pub struct PyCSimpleType(PyType);
#[pyclass(flags(BASETYPE), with(AsNumber, Initializer))]
impl PyCSimpleType {
#[pygetset(name = "__pointer_type__")]
fn pointer_type(zelf: PyTypeRef, vm: &VirtualMachine) -> PyResult {
super::base::pointer_type_get(&zelf, vm)
}
#[pygetset(name = "__pointer_type__", setter)]
fn set_pointer_type(zelf: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
super::base::pointer_type_set(&zelf, value, vm)
}
#[allow(clippy::new_ret_no_self)]
#[pymethod]
fn new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult {
@@ -327,10 +341,10 @@ impl PyCSimpleType {
Some("z") => {
// 1. bytes → create CArgObject with null-terminated buffer
if let Some(bytes) = value.downcast_ref::<PyBytes>() {
let (holder, ptr) = super::base::ensure_z_null_terminated(bytes, vm);
let (kept_alive, ptr) = super::base::ensure_z_null_terminated(bytes, vm);
return Ok(CArgObject {
tag: b'z',
value: FfiArgValue::OwnedPointer(ptr, holder),
value: FfiArgValue::OwnedPointer(ptr, kept_alive),
obj: value.clone(),
size: 0,
offset: 0,
@@ -381,10 +395,10 @@ impl PyCSimpleType {
}
// 2. bytes → create CArgObject with null-terminated buffer
if let Some(bytes) = value.downcast_ref::<PyBytes>() {
let (holder, ptr) = super::base::ensure_z_null_terminated(bytes, vm);
let (kept_alive, ptr) = super::base::ensure_z_null_terminated(bytes, vm);
return Ok(CArgObject {
tag: b'z',
value: FfiArgValue::OwnedPointer(ptr, holder),
value: FfiArgValue::OwnedPointer(ptr, kept_alive),
obj: value.clone(),
size: 0,
offset: 0,
@@ -1027,9 +1041,10 @@ impl Constructor for PyCSimple {
if let Some(ref v) = init_arg {
if _type_ == "z" {
if let Some(bytes) = v.downcast_ref::<PyBytes>() {
let (converted, ptr) = super::base::ensure_z_null_terminated(bytes, vm);
let (kept_alive, ptr) = super::base::ensure_z_null_terminated(bytes, vm);
let buffer = ptr.to_ne_bytes().to_vec();
let cdata = PyCData::from_bytes(buffer, Some(converted));
let cdata = PyCData::from_bytes(buffer, Some(v.clone()));
*cdata.base.write() = Some(kept_alive);
return PyCSimple(cdata).into_ref_with_type(vm, cls).map(Into::into);
}
} else if _type_ == "Z"
@@ -1258,9 +1273,10 @@ impl PyCSimple {
// Handle z/Z types with PyBytes/PyStr separately to avoid memory leak
if type_code == "z" {
if let Some(bytes) = value.downcast_ref::<PyBytes>() {
let (converted, ptr) = super::base::ensure_z_null_terminated(bytes, vm);
let (kept_alive, ptr) = super::base::ensure_z_null_terminated(bytes, vm);
*zelf.0.buffer.write() = alloc::borrow::Cow::Owned(ptr.to_ne_bytes().to_vec());
*zelf.0.objects.write() = Some(converted);
*zelf.0.objects.write() = Some(value);
*zelf.0.base.write() = Some(kept_alive);
return Ok(());
}
} else if type_code == "Z"

View File

@@ -1,9 +1,9 @@
use super::base::{CDATA_BUFFER_METHODS, PyCData, PyCField, StgInfo, StgInfoFlags};
use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef};
use crate::convert::ToPyObject;
use crate::function::FuncArgs;
use crate::function::PySetterValue;
use crate::function::{FuncArgs, OptionalArg, PySetterValue};
use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods};
use crate::stdlib::warnings;
use crate::types::{AsBuffer, AsNumber, Constructor, Initializer, SetAttr};
use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine};
use alloc::borrow::Cow;
@@ -103,6 +103,7 @@ impl Initializer for PyCStructType {
let mut stg_info = baseinfo.clone();
stg_info.flags &= !StgInfoFlags::DICTFLAG_FINAL; // Clear FINAL in subclass
stg_info.initialized = true;
stg_info.pointer_type = None; // Non-inheritable
stg_info
});
@@ -130,6 +131,16 @@ impl Initializer for PyCStructType {
#[pyclass(flags(BASETYPE), with(AsNumber, Constructor, Initializer, SetAttr))]
impl PyCStructType {
#[pygetset(name = "__pointer_type__")]
fn pointer_type(zelf: PyTypeRef, vm: &VirtualMachine) -> PyResult {
super::base::pointer_type_get(&zelf, vm)
}
#[pygetset(name = "__pointer_type__", setter)]
fn set_pointer_type(zelf: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
super::base::pointer_type_set(&zelf, value, vm)
}
#[pymethod]
fn from_param(zelf: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// zelf is the structure type class that from_param was called on
@@ -154,6 +165,55 @@ impl PyCStructType {
)))
}
// CDataType methods - delegated to PyCData implementations
#[pymethod]
fn from_address(zelf: PyObjectRef, address: isize, vm: &VirtualMachine) -> PyResult {
let cls: PyTypeRef = zelf
.downcast()
.map_err(|_| vm.new_type_error("expected a type"))?;
PyCData::from_address(cls, address, vm)
}
#[pymethod]
fn from_buffer(
zelf: PyObjectRef,
source: PyObjectRef,
offset: OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult {
let cls: PyTypeRef = zelf
.downcast()
.map_err(|_| vm.new_type_error("expected a type"))?;
PyCData::from_buffer(cls, source, offset, vm)
}
#[pymethod]
fn from_buffer_copy(
zelf: PyObjectRef,
source: crate::function::ArgBytesLike,
offset: OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult {
let cls: PyTypeRef = zelf
.downcast()
.map_err(|_| vm.new_type_error("expected a type"))?;
PyCData::from_buffer_copy(cls, source, offset, vm)
}
#[pymethod]
fn in_dll(
zelf: PyObjectRef,
dll: PyObjectRef,
name: crate::builtins::PyStrRef,
vm: &VirtualMachine,
) -> PyResult {
let cls: PyTypeRef = zelf
.downcast()
.map_err(|_| vm.new_type_error("expected a type"))?;
PyCData::in_dll(cls, dll, name, vm)
}
/// Called when a new Structure subclass is created
#[pyclassmethod]
fn __init_subclass__(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult<()> {
@@ -185,6 +245,24 @@ impl PyCStructType {
};
let pack = super::base::get_usize_attr(cls.as_object(), "_pack_", 0, vm)?;
// Emit DeprecationWarning on non-Windows when _pack_ is set without _layout_
if pack > 0 && !cfg!(windows) {
let has_layout = cls.as_object().get_attr("_layout_", vm).is_ok();
if !has_layout {
let base_type_name = "Structure";
let msg = format!(
"Due to '_pack_', the '{}' {} will use memory layout compatible with \
MSVC (Windows). If this is intended, set _layout_ to 'ms'. \
The implicit default is deprecated and slated to become an error in \
Python 3.19.",
cls.name(),
base_type_name,
);
warnings::warn(vm.ctx.exceptions.deprecation_warning, msg, 1, vm)?;
}
}
let forced_alignment =
super::base::get_usize_attr(cls.as_object(), "_align_", 1, vm)?.max(1);
@@ -221,6 +299,11 @@ impl PyCStructType {
let mut format = String::from("T{");
let mut last_end = 0usize; // Track end of last field for padding calculation
// Bitfield layout tracking
let mut bitfield_bit_offset: u16 = 0; // Current bit position within bitfield group
let mut last_field_bit_size: u16 = 0; // For MSVC: bit size of previous storage unit
let use_msvc_bitfields = pack > 0; // MSVC layout when _pack_ is set
for (index, field) in fields.iter().enumerate() {
let field_tuple = field
.downcast_ref::<PyTuple>()
@@ -336,14 +419,93 @@ impl PyCStructType {
.clone()
.downcast::<PyType>()
.map_err(|_| vm.new_type_error("_fields_ type must be a ctypes type"))?;
let c_field = PyCField::new(field_type_ref, offset as isize, size as isize, index);
// Check for bitfield size (optional 3rd element in tuple)
let (c_field, field_advances_offset) = if field_tuple.len() > 2 {
let bit_size_obj = field_tuple.get(2).expect("len checked");
let bit_size = bit_size_obj
.try_int(vm)?
.as_bigint()
.to_u16()
.ok_or_else(|| {
vm.new_value_error("number of bits invalid for bit field".to_string())
})?;
has_bitfield = true;
let type_bits = (size * 8) as u16;
let (advances, bit_offset);
if use_msvc_bitfields {
// MSVC layout: different types start new storage unit
if bitfield_bit_offset + bit_size > type_bits
|| type_bits != last_field_bit_size
{
// Close previous bitfield, start new allocation unit
bitfield_bit_offset = 0;
advances = true;
} else {
advances = false;
}
bit_offset = bitfield_bit_offset;
bitfield_bit_offset += bit_size;
last_field_bit_size = type_bits;
} else {
// GCC System V layout: pack within same type
let fits_in_current = bitfield_bit_offset + bit_size <= type_bits;
advances = if fits_in_current && bitfield_bit_offset > 0 {
false
} else if !fits_in_current {
bitfield_bit_offset = 0;
true
} else {
true
};
bit_offset = bitfield_bit_offset;
bitfield_bit_offset += bit_size;
}
// For packed bitfields that share offset, use the same offset as previous
let field_offset = if !advances {
offset - size // Reuse the previous field's offset
} else {
offset
};
(
PyCField::new_bitfield(
name.clone(),
field_type_ref,
field_offset as isize,
size as isize,
bit_size,
bit_offset,
index,
),
advances,
)
} else {
bitfield_bit_offset = 0; // Reset on non-bitfield
last_field_bit_size = 0;
(
PyCField::new(
name.clone(),
field_type_ref,
offset as isize,
size as isize,
index,
),
true,
)
};
// Set the CField as a class attribute
cls.set_attr(vm.ctx.intern_str(name.clone()), c_field.to_pyobject(vm));
// Update tracking
last_end = offset + size;
offset += size;
// Update tracking - don't advance offset for packed bitfields
if field_advances_offset {
last_end = offset + size;
offset += size;
}
}
// Calculate total_align = max(max_align, forced_alignment)
@@ -366,6 +528,16 @@ impl PyCStructType {
}
format.push('}');
// Check for circular self-reference: if a field of the same type as this
// structure was encountered, it would have marked this type's stginfo as FINAL.
if let Some(stg_info) = cls.get_type_data::<StgInfo>()
&& stg_info.is_final()
{
return Err(
vm.new_attribute_error("Structure or union cannot contain itself".to_string())
);
}
// Store StgInfo with aligned size and total alignment
let mut stg_info = StgInfo::new(aligned_size, total_align);
stg_info.length = fields.len();

View File

@@ -2,12 +2,13 @@ use super::base::{CDATA_BUFFER_METHODS, StgInfoFlags};
use super::{PyCData, PyCField, StgInfo};
use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef};
use crate::convert::ToPyObject;
use crate::function::FuncArgs;
use crate::function::PySetterValue;
use crate::function::{ArgBytesLike, FuncArgs, OptionalArg, PySetterValue};
use crate::protocol::{BufferDescriptor, PyBuffer};
use crate::stdlib::warnings;
use crate::types::{AsBuffer, Constructor, Initializer, SetAttr};
use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine};
use alloc::borrow::Cow;
use num_traits::ToPrimitive;
/// Calculate Union type size from _fields_ (max field size)
pub(super) fn calculate_union_size(cls: &Py<PyType>, vm: &VirtualMachine) -> PyResult<usize> {
@@ -106,6 +107,7 @@ impl Initializer for PyCUnionType {
let mut stg_info = baseinfo.clone();
stg_info.flags &= !StgInfoFlags::DICTFLAG_FINAL; // Clear FINAL flag in subclass
stg_info.initialized = true;
stg_info.pointer_type = None; // Non-inheritable
stg_info
});
@@ -163,6 +165,22 @@ impl PyCUnionType {
};
let pack = super::base::get_usize_attr(cls.as_object(), "_pack_", 0, vm)?;
// Emit DeprecationWarning on non-Windows when _pack_ is set without _layout_
if pack > 0 && !cfg!(windows) {
let has_layout = cls.as_object().get_attr("_layout_", vm).is_ok();
if !has_layout {
let msg = format!(
"Due to '_pack_', the '{}' Union will use memory layout compatible with \
MSVC (Windows). If this is intended, set _layout_ to 'ms'. \
The implicit default is deprecated and slated to become an error in \
Python 3.19.",
cls.name(),
);
warnings::warn(vm.ctx.exceptions.deprecation_warning, msg, 1, vm)?;
}
}
let forced_alignment =
super::base::get_usize_attr(cls.as_object(), "_align_", 1, vm)?.max(1);
@@ -258,7 +276,42 @@ impl PyCUnionType {
.clone()
.downcast::<PyType>()
.map_err(|_| vm.new_type_error("_fields_ type must be a ctypes type"))?;
let c_field = PyCField::new(field_type_ref, 0, size as isize, index);
// Check for bitfield size (optional 3rd element in tuple)
// For unions, each field starts fresh (CPython: _layout.py)
let c_field = if field_tuple.len() > 2 {
let bit_size_obj = field_tuple.get(2).expect("len checked");
let bit_size = bit_size_obj
.try_int(vm)?
.as_bigint()
.to_u16()
.ok_or_else(|| {
vm.new_value_error("number of bits invalid for bit field".to_string())
})?;
has_bitfield = true;
// Union fields all start at offset 0, so bit_offset = 0
let mut bit_offset: u16 = 0;
let type_bits = (size * 8) as u16;
// Big-endian: bit_offset = type_bits - bit_size
let big_endian = is_swapped != cfg!(target_endian = "big");
if big_endian && type_bits >= bit_size {
bit_offset = type_bits - bit_size;
}
PyCField::new_bitfield(
name.clone(),
field_type_ref,
0, // Union fields always at offset 0
size as isize,
bit_size,
bit_offset,
index,
)
} else {
PyCField::new(name.clone(), field_type_ref, 0, size as isize, index)
};
cls.set_attr(vm.ctx.intern_str(name), c_field.to_pyobject(vm));
}
@@ -271,6 +324,15 @@ impl PyCUnionType {
max_size
};
// Check for circular self-reference
if let Some(stg_info) = cls.get_type_data::<StgInfo>()
&& stg_info.is_final()
{
return Err(
vm.new_attribute_error("Structure or union cannot contain itself".to_string())
);
}
// Store StgInfo with aligned size
let mut stg_info = StgInfo::new(aligned_size, total_align);
stg_info.length = fields.len();
@@ -299,6 +361,16 @@ impl PyCUnionType {
#[pyclass(flags(BASETYPE), with(Constructor, Initializer, SetAttr))]
impl PyCUnionType {
#[pygetset(name = "__pointer_type__")]
fn pointer_type(zelf: PyTypeRef, vm: &VirtualMachine) -> PyResult {
super::base::pointer_type_get(&zelf, vm)
}
#[pygetset(name = "__pointer_type__", setter)]
fn set_pointer_type(zelf: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
super::base::pointer_type_set(&zelf, value, vm)
}
#[pymethod]
fn from_param(zelf: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// zelf is the union type class that from_param was called on
@@ -344,6 +416,55 @@ impl PyCUnionType {
)))
}
// CDataType methods - delegated to PyCData implementations
#[pymethod]
fn from_address(zelf: PyObjectRef, address: isize, vm: &VirtualMachine) -> PyResult {
let cls: PyTypeRef = zelf
.downcast()
.map_err(|_| vm.new_type_error("expected a type"))?;
PyCData::from_address(cls, address, vm)
}
#[pymethod]
fn from_buffer(
zelf: PyObjectRef,
source: PyObjectRef,
offset: OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult {
let cls: PyTypeRef = zelf
.downcast()
.map_err(|_| vm.new_type_error("expected a type"))?;
PyCData::from_buffer(cls, source, offset, vm)
}
#[pymethod]
fn from_buffer_copy(
zelf: PyObjectRef,
source: ArgBytesLike,
offset: OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult {
let cls: PyTypeRef = zelf
.downcast()
.map_err(|_| vm.new_type_error("expected a type"))?;
PyCData::from_buffer_copy(cls, source, offset, vm)
}
#[pymethod]
fn in_dll(
zelf: PyObjectRef,
dll: PyObjectRef,
name: crate::builtins::PyStrRef,
vm: &VirtualMachine,
) -> PyResult {
let cls: PyTypeRef = zelf
.downcast()
.map_err(|_| vm.new_type_error("expected a type"))?;
PyCData::in_dll(cls, dll, name, vm)
}
/// Called when a new Union subclass is created
#[pyclassmethod]
fn __init_subclass__(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult<()> {
@@ -383,6 +504,14 @@ impl SetAttr for PyCUnionType {
}
}
// 2. If _fields_, call process_fields (which checks FINAL internally)
// Check BEFORE writing to dict to avoid storing _fields_ when FINAL
if attr_name.as_str() == "_fields_"
&& let PySetterValue::Assign(ref fields_value) = value
{
PyCUnionType::process_fields(pytype, fields_value.clone(), vm)?;
}
// Store in type's attributes dict
match &value {
PySetterValue::Assign(v) => {
@@ -403,13 +532,6 @@ impl SetAttr for PyCUnionType {
}
}
// 2. If _fields_, call process_fields (which checks FINAL internally)
if attr_name.as_str() == "_fields_"
&& let PySetterValue::Assign(fields_value) = value
{
PyCUnionType::process_fields(pytype, fields_value, vm)?;
}
Ok(())
}
}

View File

@@ -827,8 +827,7 @@ mod sys {
Ok(exc) => {
// PyErr_Display: try traceback._print_exception_bltin first
if let Ok(tb_mod) = vm.import("traceback", 0)
&& let Ok(print_exc_builtin) =
tb_mod.get_attr("_print_exception_bltin", vm)
&& let Ok(print_exc_builtin) = tb_mod.get_attr("_print_exception_bltin", vm)
&& print_exc_builtin
.call((exc.as_object().to_owned(),), vm)
.is_ok()

View File

@@ -211,7 +211,7 @@ impl VirtualMachine {
op: &str,
) -> PyBaseExceptionRef {
self.new_type_error(format!(
"'{}' not supported between instances of '{}' and '{}'",
"unsupported operand type(s) for {}: '{}' and '{}'",
op,
a.class().name(),
b.class().name()