Skip to content

Commit

Permalink
gh-114388: Fix warnings when assign an unsigned integer member (GH-11…
Browse files Browse the repository at this point in the history
…4391)

* Fix a RuntimeWarning emitted when assign an integer-like value that
  is not an instance of int to an attribute that corresponds to a C
  struct member of type T_UINT and T_ULONG.
* Fix a double RuntimeWarning emitted when assign a negative integer value
  to an attribute that corresponds to a C struct member of type T_UINT.
  • Loading branch information
serhiy-storchaka authored Feb 4, 2024
1 parent 0ea3662 commit 3ddc515
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 28 deletions.
37 changes: 37 additions & 0 deletions Lib/test/test_capi/test_structmembers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
PY_SSIZE_T_MAX, PY_SSIZE_T_MIN,
)


class Index:
def __init__(self, value):
self.value = value
def __index__(self):
return self.value

# There are two classes: one using <structmember.h> and another using
# `Py_`-prefixed API. They should behave the same in Python

Expand Down Expand Up @@ -72,6 +79,10 @@ def test_int(self):
self.assertEqual(ts.T_INT, INT_MIN)
ts.T_UINT = UINT_MAX
self.assertEqual(ts.T_UINT, UINT_MAX)
ts.T_UINT = Index(0)
self.assertEqual(ts.T_UINT, 0)
ts.T_UINT = Index(INT_MAX)
self.assertEqual(ts.T_UINT, INT_MAX)

def test_long(self):
ts = self.ts
Expand All @@ -81,6 +92,10 @@ def test_long(self):
self.assertEqual(ts.T_LONG, LONG_MIN)
ts.T_ULONG = ULONG_MAX
self.assertEqual(ts.T_ULONG, ULONG_MAX)
ts.T_ULONG = Index(0)
self.assertEqual(ts.T_ULONG, 0)
ts.T_ULONG = Index(LONG_MAX)
self.assertEqual(ts.T_ULONG, LONG_MAX)

def test_py_ssize_t(self):
ts = self.ts
Expand Down Expand Up @@ -173,6 +188,28 @@ def test_ushort_max(self):
with warnings_helper.check_warnings(('', RuntimeWarning)):
ts.T_USHORT = USHRT_MAX+1

def test_int(self):
ts = self.ts
if LONG_MIN < INT_MIN:
with self.assertWarns(RuntimeWarning):
ts.T_INT = INT_MIN-1
if LONG_MAX > INT_MAX:
with self.assertWarns(RuntimeWarning):
ts.T_INT = INT_MAX+1

def test_uint(self):
ts = self.ts
with self.assertWarns(RuntimeWarning):
ts.T_UINT = -1
if ULONG_MAX > UINT_MAX:
with self.assertWarns(RuntimeWarning):
ts.T_UINT = UINT_MAX+1

def test_ulong(self):
ts = self.ts
with self.assertWarns(RuntimeWarning):
ts.T_ULONG = -1

class TestWarnings_OldAPI(TestWarnings, unittest.TestCase):
cls = _test_structmembersType_OldAPI

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Fix a :exc:`RuntimeWarning` emitted when assign an integer-like value that
is not an instance of :class:`int` to an attribute that corresponds to a C
struct member of :ref:`type <PyMemberDef-types>` T_UINT and T_ULONG. Fix a
double :exc:`RuntimeWarning` emitted when assign a negative integer value to
an attribute that corresponds to a C struct member of type T_UINT.
83 changes: 55 additions & 28 deletions Python/structmember.c
Original file line number Diff line number Diff line change
Expand Up @@ -197,45 +197,72 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
WARN("Truncation of value to int");
break;
}
case Py_T_UINT:{
unsigned long ulong_val = PyLong_AsUnsignedLong(v);
if ((ulong_val == (unsigned long)-1) && PyErr_Occurred()) {
/* XXX: For compatibility, accept negative int values
as well. */
PyErr_Clear();
ulong_val = PyLong_AsLong(v);
if ((ulong_val == (unsigned long)-1) &&
PyErr_Occurred())
case Py_T_UINT: {
/* XXX: For compatibility, accept negative int values
as well. */
int overflow;
long long_val = PyLong_AsLongAndOverflow(v, &overflow);
if (long_val == -1 && PyErr_Occurred()) {
return -1;
}
if (overflow < 0) {
PyErr_SetString(PyExc_OverflowError,
"Python int too large to convert to C long");
}
else if (!overflow) {
*(unsigned int *)addr = (unsigned int)(unsigned long)long_val;
if (long_val < 0) {
WARN("Writing negative value into unsigned field");
}
else if ((unsigned long)long_val > UINT_MAX) {
WARN("Truncation of value to unsigned short");
}
}
else {
unsigned long ulong_val = PyLong_AsUnsignedLong(v);
if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) {
return -1;
*(unsigned int *)addr = (unsigned int)ulong_val;
WARN("Writing negative value into unsigned field");
} else
*(unsigned int *)addr = (unsigned int)ulong_val;
if (ulong_val > UINT_MAX)
WARN("Truncation of value to unsigned int");
break;
}
*(unsigned int*)addr = (unsigned int)ulong_val;
if (ulong_val > UINT_MAX) {
WARN("Truncation of value to unsigned int");
}
}
break;
}
case Py_T_LONG:{
*(long*)addr = PyLong_AsLong(v);
if ((*(long*)addr == -1) && PyErr_Occurred())
return -1;
break;
}
case Py_T_ULONG:{
*(unsigned long*)addr = PyLong_AsUnsignedLong(v);
if ((*(unsigned long*)addr == (unsigned long)-1)
&& PyErr_Occurred()) {
/* XXX: For compatibility, accept negative int values
as well. */
PyErr_Clear();
*(unsigned long*)addr = PyLong_AsLong(v);
if ((*(unsigned long*)addr == (unsigned long)-1)
&& PyErr_Occurred())
case Py_T_ULONG: {
/* XXX: For compatibility, accept negative int values
as well. */
int overflow;
long long_val = PyLong_AsLongAndOverflow(v, &overflow);
if (long_val == -1 && PyErr_Occurred()) {
return -1;
}
if (overflow < 0) {
PyErr_SetString(PyExc_OverflowError,
"Python int too large to convert to C long");
}
else if (!overflow) {
*(unsigned long *)addr = (unsigned long)long_val;
if (long_val < 0) {
WARN("Writing negative value into unsigned field");
}
}
else {
unsigned long ulong_val = PyLong_AsUnsignedLong(v);
if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) {
return -1;
WARN("Writing negative value into unsigned field");
}
*(unsigned long*)addr = ulong_val;
}
break;
}
}
case Py_T_PYSSIZET:{
*(Py_ssize_t*)addr = PyLong_AsSsize_t(v);
if ((*(Py_ssize_t*)addr == (Py_ssize_t)-1)
Expand Down

0 comments on commit 3ddc515

Please sign in to comment.