Skip to content

Commit

Permalink
pythongh-126937: ctypes: fix TypeError when a field's size is >65535 …
Browse files Browse the repository at this point in the history
…bytes (pythonGH-126938)


Co-authored-by: Peter Bierma <[email protected]>
Co-authored-by: Terry Jan Reedy <[email protected]>
Co-authored-by: Petr Viktorin <[email protected]>
  • Loading branch information
4 people authored Dec 10, 2024
1 parent f4b31ed commit cef0a90
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 4 deletions.
23 changes: 23 additions & 0 deletions Lib/test/test_ctypes/test_struct_fields.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
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)
Expand Down Expand Up @@ -75,6 +76,28 @@ def __init_subclass__(cls, **kwargs):
'ctypes state is not initialized'):
class Subclass(BrokenStructure): ...

def test_max_field_size_gh126937(self):
# Classes for big structs should be created successfully.
# (But they most likely can't be instantiated.)
# Here we test the exact limit: the number of *bits* must fit
# in Py_ssize_t.

class X(self.cls):
_fields_ = [('char', c_char),]
max_field_size = sys.maxsize // 8

class Y(self.cls):
_fields_ = [('largeField', X * max_field_size)]
class Z(self.cls):
_fields_ = [('largeField', c_char * max_field_size)]

with self.assertRaises(ValueError):
class TooBig(self.cls):
_fields_ = [('largeField', X * (max_field_size + 1))]
with self.assertRaises(ValueError):
class TooBig(self.cls):
_fields_ = [('largeField', c_char * (max_field_size + 1))]

# __set__ and __get__ should raise a TypeError in case their self
# argument is not a ctype instance.
def test___set__(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix :exc:`TypeError` when a :class:`ctypes.Structure` has a field size
that doesn't fit into an unsigned 16-bit integer.
Instead, the maximum number of *bits* is :data:`sys.maxsize`.
10 changes: 8 additions & 2 deletions Modules/_ctypes/cfield.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,16 @@ PyCField_new_impl(PyTypeObject *type, PyObject *name, PyObject *proto,
goto error;
}

Py_ssize_t bit_size = NUM_BITS(size);
if (bit_size) {
if (bit_size_obj != Py_None) {
#ifdef Py_DEBUG
Py_ssize_t bit_size = NUM_BITS(size);
assert(bit_size > 0);
assert(bit_size <= info->size * 8);
// Currently, the bit size is specified redundantly
// in NUM_BITS(size) and bit_size_obj.
// Verify that they match.
assert(PyLong_AsSsize_t(bit_size_obj) == bit_size);
#endif
switch(info->ffi_type_pointer.type) {
case FFI_TYPE_UINT8:
case FFI_TYPE_UINT16:
Expand Down
4 changes: 2 additions & 2 deletions Modules/_ctypes/stgdict.c
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
if (!tmp) {
goto error;
}
Py_ssize_t total_align = PyLong_AsInt(tmp);
Py_ssize_t total_align = PyLong_AsSsize_t(tmp);
Py_DECREF(tmp);
if (total_align < 0) {
if (!PyErr_Occurred()) {
Expand All @@ -306,7 +306,7 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
if (!tmp) {
goto error;
}
Py_ssize_t total_size = PyLong_AsInt(tmp);
Py_ssize_t total_size = PyLong_AsSsize_t(tmp);
Py_DECREF(tmp);
if (total_size < 0) {
if (!PyErr_Occurred()) {
Expand Down

0 comments on commit cef0a90

Please sign in to comment.