Skip to content

Commit 3ddc515

Browse files
gh-114388: Fix warnings when assign an unsigned integer member (GH-114391)
* 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.
1 parent 0ea3662 commit 3ddc515

File tree

3 files changed

+97
-28
lines changed

3 files changed

+97
-28
lines changed

Lib/test/test_capi/test_structmembers.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414
PY_SSIZE_T_MAX, PY_SSIZE_T_MIN,
1515
)
1616

17+
18+
class Index:
19+
def __init__(self, value):
20+
self.value = value
21+
def __index__(self):
22+
return self.value
23+
1724
# There are two classes: one using <structmember.h> and another using
1825
# `Py_`-prefixed API. They should behave the same in Python
1926

@@ -72,6 +79,10 @@ def test_int(self):
7279
self.assertEqual(ts.T_INT, INT_MIN)
7380
ts.T_UINT = UINT_MAX
7481
self.assertEqual(ts.T_UINT, UINT_MAX)
82+
ts.T_UINT = Index(0)
83+
self.assertEqual(ts.T_UINT, 0)
84+
ts.T_UINT = Index(INT_MAX)
85+
self.assertEqual(ts.T_UINT, INT_MAX)
7586

7687
def test_long(self):
7788
ts = self.ts
@@ -81,6 +92,10 @@ def test_long(self):
8192
self.assertEqual(ts.T_LONG, LONG_MIN)
8293
ts.T_ULONG = ULONG_MAX
8394
self.assertEqual(ts.T_ULONG, ULONG_MAX)
95+
ts.T_ULONG = Index(0)
96+
self.assertEqual(ts.T_ULONG, 0)
97+
ts.T_ULONG = Index(LONG_MAX)
98+
self.assertEqual(ts.T_ULONG, LONG_MAX)
8499

85100
def test_py_ssize_t(self):
86101
ts = self.ts
@@ -173,6 +188,28 @@ def test_ushort_max(self):
173188
with warnings_helper.check_warnings(('', RuntimeWarning)):
174189
ts.T_USHORT = USHRT_MAX+1
175190

191+
def test_int(self):
192+
ts = self.ts
193+
if LONG_MIN < INT_MIN:
194+
with self.assertWarns(RuntimeWarning):
195+
ts.T_INT = INT_MIN-1
196+
if LONG_MAX > INT_MAX:
197+
with self.assertWarns(RuntimeWarning):
198+
ts.T_INT = INT_MAX+1
199+
200+
def test_uint(self):
201+
ts = self.ts
202+
with self.assertWarns(RuntimeWarning):
203+
ts.T_UINT = -1
204+
if ULONG_MAX > UINT_MAX:
205+
with self.assertWarns(RuntimeWarning):
206+
ts.T_UINT = UINT_MAX+1
207+
208+
def test_ulong(self):
209+
ts = self.ts
210+
with self.assertWarns(RuntimeWarning):
211+
ts.T_ULONG = -1
212+
176213
class TestWarnings_OldAPI(TestWarnings, unittest.TestCase):
177214
cls = _test_structmembersType_OldAPI
178215

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix a :exc:`RuntimeWarning` emitted when assign an integer-like value that
2+
is not an instance of :class:`int` to an attribute that corresponds to a C
3+
struct member of :ref:`type <PyMemberDef-types>` T_UINT and T_ULONG. Fix a
4+
double :exc:`RuntimeWarning` emitted when assign a negative integer value to
5+
an attribute that corresponds to a C struct member of type T_UINT.

Python/structmember.c

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -197,45 +197,72 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
197197
WARN("Truncation of value to int");
198198
break;
199199
}
200-
case Py_T_UINT:{
201-
unsigned long ulong_val = PyLong_AsUnsignedLong(v);
202-
if ((ulong_val == (unsigned long)-1) && PyErr_Occurred()) {
203-
/* XXX: For compatibility, accept negative int values
204-
as well. */
205-
PyErr_Clear();
206-
ulong_val = PyLong_AsLong(v);
207-
if ((ulong_val == (unsigned long)-1) &&
208-
PyErr_Occurred())
200+
case Py_T_UINT: {
201+
/* XXX: For compatibility, accept negative int values
202+
as well. */
203+
int overflow;
204+
long long_val = PyLong_AsLongAndOverflow(v, &overflow);
205+
if (long_val == -1 && PyErr_Occurred()) {
206+
return -1;
207+
}
208+
if (overflow < 0) {
209+
PyErr_SetString(PyExc_OverflowError,
210+
"Python int too large to convert to C long");
211+
}
212+
else if (!overflow) {
213+
*(unsigned int *)addr = (unsigned int)(unsigned long)long_val;
214+
if (long_val < 0) {
215+
WARN("Writing negative value into unsigned field");
216+
}
217+
else if ((unsigned long)long_val > UINT_MAX) {
218+
WARN("Truncation of value to unsigned short");
219+
}
220+
}
221+
else {
222+
unsigned long ulong_val = PyLong_AsUnsignedLong(v);
223+
if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) {
209224
return -1;
210-
*(unsigned int *)addr = (unsigned int)ulong_val;
211-
WARN("Writing negative value into unsigned field");
212-
} else
213-
*(unsigned int *)addr = (unsigned int)ulong_val;
214-
if (ulong_val > UINT_MAX)
215-
WARN("Truncation of value to unsigned int");
216-
break;
225+
}
226+
*(unsigned int*)addr = (unsigned int)ulong_val;
227+
if (ulong_val > UINT_MAX) {
228+
WARN("Truncation of value to unsigned int");
229+
}
217230
}
231+
break;
232+
}
218233
case Py_T_LONG:{
219234
*(long*)addr = PyLong_AsLong(v);
220235
if ((*(long*)addr == -1) && PyErr_Occurred())
221236
return -1;
222237
break;
223238
}
224-
case Py_T_ULONG:{
225-
*(unsigned long*)addr = PyLong_AsUnsignedLong(v);
226-
if ((*(unsigned long*)addr == (unsigned long)-1)
227-
&& PyErr_Occurred()) {
228-
/* XXX: For compatibility, accept negative int values
229-
as well. */
230-
PyErr_Clear();
231-
*(unsigned long*)addr = PyLong_AsLong(v);
232-
if ((*(unsigned long*)addr == (unsigned long)-1)
233-
&& PyErr_Occurred())
239+
case Py_T_ULONG: {
240+
/* XXX: For compatibility, accept negative int values
241+
as well. */
242+
int overflow;
243+
long long_val = PyLong_AsLongAndOverflow(v, &overflow);
244+
if (long_val == -1 && PyErr_Occurred()) {
245+
return -1;
246+
}
247+
if (overflow < 0) {
248+
PyErr_SetString(PyExc_OverflowError,
249+
"Python int too large to convert to C long");
250+
}
251+
else if (!overflow) {
252+
*(unsigned long *)addr = (unsigned long)long_val;
253+
if (long_val < 0) {
254+
WARN("Writing negative value into unsigned field");
255+
}
256+
}
257+
else {
258+
unsigned long ulong_val = PyLong_AsUnsignedLong(v);
259+
if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) {
234260
return -1;
235-
WARN("Writing negative value into unsigned field");
261+
}
262+
*(unsigned long*)addr = ulong_val;
236263
}
237264
break;
238-
}
265+
}
239266
case Py_T_PYSSIZET:{
240267
*(Py_ssize_t*)addr = PyLong_AsSsize_t(v);
241268
if ((*(Py_ssize_t*)addr == (Py_ssize_t)-1)

0 commit comments

Comments
 (0)