Skip to content

Commit b9937a6

Browse files
[3.12] gh-114388: Fix warnings when assign an unsigned integer member (GH-114391) (GH-115001)
* 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. (cherry picked from commit 3ddc515)
1 parent 4548ae7 commit b9937a6

File tree

3 files changed

+99
-28
lines changed

3 files changed

+99
-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: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -197,45 +197,74 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
197197
WARN("Truncation of value to int");
198198
break;
199199
}
200-
case 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 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+
return -1;
212+
}
213+
else if (!overflow) {
214+
*(unsigned int *)addr = (unsigned int)(unsigned long)long_val;
215+
if (long_val < 0) {
216+
WARN("Writing negative value into unsigned field");
217+
}
218+
else if ((unsigned long)long_val > UINT_MAX) {
219+
WARN("Truncation of value to unsigned short");
220+
}
221+
}
222+
else {
223+
unsigned long ulong_val = PyLong_AsUnsignedLong(v);
224+
if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) {
209225
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;
226+
}
227+
*(unsigned int*)addr = (unsigned int)ulong_val;
228+
if (ulong_val > UINT_MAX) {
229+
WARN("Truncation of value to unsigned int");
230+
}
217231
}
232+
break;
233+
}
218234
case T_LONG:{
219235
*(long*)addr = PyLong_AsLong(v);
220236
if ((*(long*)addr == -1) && PyErr_Occurred())
221237
return -1;
222238
break;
223239
}
224-
case 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())
240+
case T_ULONG: {
241+
/* XXX: For compatibility, accept negative int values
242+
as well. */
243+
int overflow;
244+
long long_val = PyLong_AsLongAndOverflow(v, &overflow);
245+
if (long_val == -1 && PyErr_Occurred()) {
246+
return -1;
247+
}
248+
if (overflow < 0) {
249+
PyErr_SetString(PyExc_OverflowError,
250+
"Python int too large to convert to C long");
251+
return -1;
252+
}
253+
else if (!overflow) {
254+
*(unsigned long *)addr = (unsigned long)long_val;
255+
if (long_val < 0) {
256+
WARN("Writing negative value into unsigned field");
257+
}
258+
}
259+
else {
260+
unsigned long ulong_val = PyLong_AsUnsignedLong(v);
261+
if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) {
234262
return -1;
235-
WARN("Writing negative value into unsigned field");
263+
}
264+
*(unsigned long*)addr = ulong_val;
236265
}
237266
break;
238-
}
267+
}
239268
case T_PYSSIZET:{
240269
*(Py_ssize_t*)addr = PyLong_AsSsize_t(v);
241270
if ((*(Py_ssize_t*)addr == (Py_ssize_t)-1)

0 commit comments

Comments
 (0)