Skip to content

Commit e18829a

Browse files
gh-132629: Deprecate accepting out-of-range values for unsigned integers in PyArg_Parse (GH-132630)
For unsigned integer formats in the PyArg_Parse* functions, accepting Python integers with value that is larger than the maximal value the corresponding C type or less than the minimal value for the corresponding signed integer type is now deprecated.
1 parent 3dbe02c commit e18829a

File tree

19 files changed

+674
-211
lines changed

19 files changed

+674
-211
lines changed

Doc/c-api/arg.rst

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,11 @@ the Python object to the required type.
241241

242242
For signed integer formats, :exc:`OverflowError` is raised if the value
243243
is out of range for the C type.
244-
For unsigned integer formats, no range checking is done --- the
244+
For unsigned integer formats, the
245245
most significant bits are silently truncated when the receiving field is too
246-
small to receive the value.
246+
small to receive the value, and :exc:`DeprecationWarning` is emitted when
247+
the value is larger than the maximal value for the C type or less than
248+
the minimal value for the corresponding signed integer type of the same size.
247249

248250
``b`` (:class:`int`) [unsigned char]
249251
Convert a nonnegative Python integer to an unsigned tiny integer, stored in a C
@@ -252,27 +254,25 @@ small to receive the value.
252254
``B`` (:class:`int`) [unsigned char]
253255
Convert a Python integer to a tiny integer without overflow checking, stored in a C
254256
:c:expr:`unsigned char`.
257+
Convert a Python integer to a C :c:expr:`unsigned char`.
255258

256259
``h`` (:class:`int`) [short int]
257260
Convert a Python integer to a C :c:expr:`short int`.
258261

259262
``H`` (:class:`int`) [unsigned short int]
260-
Convert a Python integer to a C :c:expr:`unsigned short int`, without overflow
261-
checking.
263+
Convert a Python integer to a C :c:expr:`unsigned short int`.
262264

263265
``i`` (:class:`int`) [int]
264266
Convert a Python integer to a plain C :c:expr:`int`.
265267

266268
``I`` (:class:`int`) [unsigned int]
267-
Convert a Python integer to a C :c:expr:`unsigned int`, without overflow
268-
checking.
269+
Convert a Python integer to a C :c:expr:`unsigned int`.
269270

270271
``l`` (:class:`int`) [long int]
271272
Convert a Python integer to a C :c:expr:`long int`.
272273

273274
``k`` (:class:`int`) [unsigned long]
274-
Convert a Python integer to a C :c:expr:`unsigned long` without
275-
overflow checking.
275+
Convert a Python integer to a C :c:expr:`unsigned long`.
276276

277277
.. versionchanged:: 3.14
278278
Use :meth:`~object.__index__` if available.
@@ -281,8 +281,7 @@ small to receive the value.
281281
Convert a Python integer to a C :c:expr:`long long`.
282282

283283
``K`` (:class:`int`) [unsigned long long]
284-
Convert a Python integer to a C :c:expr:`unsigned long long`
285-
without overflow checking.
284+
Convert a Python integer to a C :c:expr:`unsigned long long`.
286285

287286
.. versionchanged:: 3.14
288287
Use :meth:`~object.__index__` if available.
@@ -310,6 +309,14 @@ small to receive the value.
310309
``D`` (:class:`complex`) [Py_complex]
311310
Convert a Python complex number to a C :c:type:`Py_complex` structure.
312311

312+
.. deprecated:: next
313+
314+
For unsigned integer formats ``B``, ``H``, ``I``, ``k`` and ``K``,
315+
:exc:`DeprecationWarning` is emitted when the value is larger than
316+
the maximal value for the C type or less than the minimal value for
317+
the corresponding signed integer type of the same size.
318+
319+
313320
Other objects
314321
-------------
315322

Doc/whatsnew/3.15.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,11 @@ Porting to Python 3.15
462462
Deprecated C APIs
463463
-----------------
464464

465-
* TODO
465+
* For unsigned integer formats in :c:func:`PyArg_ParseTuple`,
466+
accepting Python integers with value that is larger than the maximal value
467+
for the C type or less than the minimal value for the corresponding
468+
signed integer type of the same size is now deprecated.
469+
(Contributed by Serhiy Storchaka in :gh:`132629`.)
466470

467471
.. Add C API deprecations above alphabetically, not here at the end.
468472

Lib/test/clinic.test.c

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,12 +1020,19 @@ test_unsigned_char_converter(PyObject *module, PyObject *const *args, Py_ssize_t
10201020
goto skip_optional;
10211021
}
10221022
{
1023-
unsigned long ival = PyLong_AsUnsignedLongMask(args[2]);
1024-
if (ival == (unsigned long)-1 && PyErr_Occurred()) {
1023+
Py_ssize_t _bytes = PyLong_AsNativeBytes(args[2], &c, sizeof(unsigned char),
1024+
Py_ASNATIVEBYTES_NATIVE_ENDIAN |
1025+
Py_ASNATIVEBYTES_ALLOW_INDEX |
1026+
Py_ASNATIVEBYTES_UNSIGNED_BUFFER);
1027+
if (_bytes < 0) {
10251028
goto exit;
10261029
}
1027-
else {
1028-
c = (unsigned char) ival;
1030+
if ((size_t)_bytes > sizeof(unsigned char)) {
1031+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
1032+
"integer value out of range", 1) < 0)
1033+
{
1034+
goto exit;
1035+
}
10291036
}
10301037
}
10311038
skip_optional:
@@ -1038,7 +1045,7 @@ test_unsigned_char_converter(PyObject *module, PyObject *const *args, Py_ssize_t
10381045
static PyObject *
10391046
test_unsigned_char_converter_impl(PyObject *module, unsigned char a,
10401047
unsigned char b, unsigned char c)
1041-
/*[clinic end generated code: output=45920dbedc22eb55 input=021414060993e289]*/
1048+
/*[clinic end generated code: output=49eda9faaf53372a input=021414060993e289]*/
10421049

10431050

10441051
/*[clinic input]
@@ -1151,9 +1158,21 @@ test_unsigned_short_converter(PyObject *module, PyObject *const *args, Py_ssize_
11511158
if (nargs < 3) {
11521159
goto skip_optional;
11531160
}
1154-
c = (unsigned short)PyLong_AsUnsignedLongMask(args[2]);
1155-
if (c == (unsigned short)-1 && PyErr_Occurred()) {
1156-
goto exit;
1161+
{
1162+
Py_ssize_t _bytes = PyLong_AsNativeBytes(args[2], &c, sizeof(unsigned short),
1163+
Py_ASNATIVEBYTES_NATIVE_ENDIAN |
1164+
Py_ASNATIVEBYTES_ALLOW_INDEX |
1165+
Py_ASNATIVEBYTES_UNSIGNED_BUFFER);
1166+
if (_bytes < 0) {
1167+
goto exit;
1168+
}
1169+
if ((size_t)_bytes > sizeof(unsigned short)) {
1170+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
1171+
"integer value out of range", 1) < 0)
1172+
{
1173+
goto exit;
1174+
}
1175+
}
11571176
}
11581177
skip_optional:
11591178
return_value = test_unsigned_short_converter_impl(module, a, b, c);
@@ -1165,7 +1184,7 @@ test_unsigned_short_converter(PyObject *module, PyObject *const *args, Py_ssize_
11651184
static PyObject *
11661185
test_unsigned_short_converter_impl(PyObject *module, unsigned short a,
11671186
unsigned short b, unsigned short c)
1168-
/*[clinic end generated code: output=e6e990df729114fc input=cdfd8eff3d9176b4]*/
1187+
/*[clinic end generated code: output=f591c7797e150f49 input=cdfd8eff3d9176b4]*/
11691188

11701189

11711190
/*[clinic input]
@@ -1298,9 +1317,21 @@ test_unsigned_int_converter(PyObject *module, PyObject *const *args, Py_ssize_t
12981317
if (nargs < 3) {
12991318
goto skip_optional;
13001319
}
1301-
c = (unsigned int)PyLong_AsUnsignedLongMask(args[2]);
1302-
if (c == (unsigned int)-1 && PyErr_Occurred()) {
1303-
goto exit;
1320+
{
1321+
Py_ssize_t _bytes = PyLong_AsNativeBytes(args[2], &c, sizeof(unsigned int),
1322+
Py_ASNATIVEBYTES_NATIVE_ENDIAN |
1323+
Py_ASNATIVEBYTES_ALLOW_INDEX |
1324+
Py_ASNATIVEBYTES_UNSIGNED_BUFFER);
1325+
if (_bytes < 0) {
1326+
goto exit;
1327+
}
1328+
if ((size_t)_bytes > sizeof(unsigned int)) {
1329+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
1330+
"integer value out of range", 1) < 0)
1331+
{
1332+
goto exit;
1333+
}
1334+
}
13041335
}
13051336
skip_optional:
13061337
return_value = test_unsigned_int_converter_impl(module, a, b, c);
@@ -1312,7 +1343,7 @@ test_unsigned_int_converter(PyObject *module, PyObject *const *args, Py_ssize_t
13121343
static PyObject *
13131344
test_unsigned_int_converter_impl(PyObject *module, unsigned int a,
13141345
unsigned int b, unsigned int c)
1315-
/*[clinic end generated code: output=f9cdbe410ccc98a3 input=5533534828b62fc0]*/
1346+
/*[clinic end generated code: output=50a413f1cc82dc11 input=5533534828b62fc0]*/
13161347

13171348

13181349
/*[clinic input]
@@ -1414,7 +1445,22 @@ test_unsigned_long_converter(PyObject *module, PyObject *const *args, Py_ssize_t
14141445
_PyArg_BadArgument("test_unsigned_long_converter", "argument 3", "int", args[2]);
14151446
goto exit;
14161447
}
1417-
c = PyLong_AsUnsignedLongMask(args[2]);
1448+
{
1449+
Py_ssize_t _bytes = PyLong_AsNativeBytes(args[2], &c, sizeof(unsigned long),
1450+
Py_ASNATIVEBYTES_NATIVE_ENDIAN |
1451+
Py_ASNATIVEBYTES_ALLOW_INDEX |
1452+
Py_ASNATIVEBYTES_UNSIGNED_BUFFER);
1453+
if (_bytes < 0) {
1454+
goto exit;
1455+
}
1456+
if ((size_t)_bytes > sizeof(unsigned long)) {
1457+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
1458+
"integer value out of range", 1) < 0)
1459+
{
1460+
goto exit;
1461+
}
1462+
}
1463+
}
14181464
skip_optional:
14191465
return_value = test_unsigned_long_converter_impl(module, a, b, c);
14201466

@@ -1425,7 +1471,7 @@ test_unsigned_long_converter(PyObject *module, PyObject *const *args, Py_ssize_t
14251471
static PyObject *
14261472
test_unsigned_long_converter_impl(PyObject *module, unsigned long a,
14271473
unsigned long b, unsigned long c)
1428-
/*[clinic end generated code: output=d74eed227d77a31b input=f450d94cae1ef73b]*/
1474+
/*[clinic end generated code: output=1bbf5620093cc914 input=f450d94cae1ef73b]*/
14291475

14301476

14311477
/*[clinic input]
@@ -1529,7 +1575,22 @@ test_unsigned_long_long_converter(PyObject *module, PyObject *const *args, Py_ss
15291575
_PyArg_BadArgument("test_unsigned_long_long_converter", "argument 3", "int", args[2]);
15301576
goto exit;
15311577
}
1532-
c = PyLong_AsUnsignedLongLongMask(args[2]);
1578+
{
1579+
Py_ssize_t _bytes = PyLong_AsNativeBytes(args[2], &c, sizeof(unsigned long long),
1580+
Py_ASNATIVEBYTES_NATIVE_ENDIAN |
1581+
Py_ASNATIVEBYTES_ALLOW_INDEX |
1582+
Py_ASNATIVEBYTES_UNSIGNED_BUFFER);
1583+
if (_bytes < 0) {
1584+
goto exit;
1585+
}
1586+
if ((size_t)_bytes > sizeof(unsigned long long)) {
1587+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
1588+
"integer value out of range", 1) < 0)
1589+
{
1590+
goto exit;
1591+
}
1592+
}
1593+
}
15331594
skip_optional:
15341595
return_value = test_unsigned_long_long_converter_impl(module, a, b, c);
15351596

@@ -1542,7 +1603,7 @@ test_unsigned_long_long_converter_impl(PyObject *module,
15421603
unsigned long long a,
15431604
unsigned long long b,
15441605
unsigned long long c)
1545-
/*[clinic end generated code: output=5ca4e4dfb3db644b input=a15115dc41866ff4]*/
1606+
/*[clinic end generated code: output=582a6623dc845824 input=a15115dc41866ff4]*/
15461607

15471608

15481609
/*[clinic input]

Lib/test/test_capi/test_getargs.py

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,17 @@
4848
LARGE = 0x7FFFFFFF
4949
VERY_LARGE = 0xFF0000121212121212121242
5050

51-
from _testcapi import UCHAR_MAX, USHRT_MAX, UINT_MAX, ULONG_MAX, INT_MAX, \
52-
INT_MIN, LONG_MIN, LONG_MAX, PY_SSIZE_T_MIN, PY_SSIZE_T_MAX, \
51+
from _testcapi import UCHAR_MAX, USHRT_MAX, UINT_MAX, ULONG_MAX, ULLONG_MAX, INT_MAX, \
52+
INT_MIN, LONG_MIN, LONG_MAX, LLONG_MIN, LLONG_MAX, PY_SSIZE_T_MIN, PY_SSIZE_T_MAX, \
5353
SHRT_MIN, SHRT_MAX, FLT_MIN, FLT_MAX, DBL_MIN, DBL_MAX
5454

5555
DBL_MAX_EXP = sys.float_info.max_exp
5656
INF = float('inf')
5757
NAN = float('nan')
5858

5959
# fake, they are not defined in Python's header files
60-
LLONG_MAX = 2**63-1
61-
LLONG_MIN = -2**63
62-
ULLONG_MAX = 2**64-1
60+
SCHAR_MAX = UCHAR_MAX // 2
61+
SCHAR_MIN = SCHAR_MAX - UCHAR_MAX
6362

6463
NULL = None
6564

@@ -209,10 +208,23 @@ def test_B(self):
209208
self.assertEqual(UCHAR_MAX, getargs_B(-1))
210209
self.assertEqual(0, getargs_B(0))
211210
self.assertEqual(UCHAR_MAX, getargs_B(UCHAR_MAX))
212-
self.assertEqual(0, getargs_B(UCHAR_MAX+1))
211+
with self.assertWarns(DeprecationWarning):
212+
self.assertEqual(0, getargs_B(UCHAR_MAX+1))
213+
with self.assertWarns(DeprecationWarning):
214+
self.assertEqual(1, getargs_B(-UCHAR_MAX))
215+
self.assertEqual(SCHAR_MAX+1, getargs_B(SCHAR_MIN))
216+
with self.assertWarns(DeprecationWarning):
217+
self.assertEqual(SCHAR_MAX, getargs_B(SCHAR_MIN-1))
218+
219+
self.assertEqual(128, getargs_B(-2**7))
220+
with self.assertWarns(DeprecationWarning):
221+
self.assertEqual(127, getargs_B(-2**7-1))
213222

214223
self.assertEqual(42, getargs_B(42))
215-
self.assertEqual(UCHAR_MAX & VERY_LARGE, getargs_B(VERY_LARGE))
224+
with self.assertWarns(DeprecationWarning):
225+
self.assertEqual(UCHAR_MAX & VERY_LARGE, getargs_B(VERY_LARGE))
226+
with self.assertWarns(DeprecationWarning):
227+
self.assertEqual(UCHAR_MAX & -VERY_LARGE, getargs_B(-VERY_LARGE))
216228

217229
def test_H(self):
218230
from _testcapi import getargs_H
@@ -233,11 +245,18 @@ def test_H(self):
233245
self.assertEqual(USHRT_MAX, getargs_H(-1))
234246
self.assertEqual(0, getargs_H(0))
235247
self.assertEqual(USHRT_MAX, getargs_H(USHRT_MAX))
236-
self.assertEqual(0, getargs_H(USHRT_MAX+1))
248+
with self.assertWarns(DeprecationWarning):
249+
self.assertEqual(0, getargs_H(USHRT_MAX+1))
250+
self.assertEqual(SHRT_MAX+1, getargs_H(SHRT_MIN))
251+
with self.assertWarns(DeprecationWarning):
252+
self.assertEqual(SHRT_MAX, getargs_H(SHRT_MIN-1))
237253

238254
self.assertEqual(42, getargs_H(42))
239255

240-
self.assertEqual(VERY_LARGE & USHRT_MAX, getargs_H(VERY_LARGE))
256+
with self.assertWarns(DeprecationWarning):
257+
self.assertEqual(USHRT_MAX & VERY_LARGE, getargs_H(VERY_LARGE))
258+
with self.assertWarns(DeprecationWarning):
259+
self.assertEqual(USHRT_MAX & -VERY_LARGE, getargs_H(-VERY_LARGE))
241260

242261
def test_I(self):
243262
from _testcapi import getargs_I
@@ -258,11 +277,18 @@ def test_I(self):
258277
self.assertEqual(UINT_MAX, getargs_I(-1))
259278
self.assertEqual(0, getargs_I(0))
260279
self.assertEqual(UINT_MAX, getargs_I(UINT_MAX))
261-
self.assertEqual(0, getargs_I(UINT_MAX+1))
280+
with self.assertWarns(DeprecationWarning):
281+
self.assertEqual(0, getargs_I(UINT_MAX+1))
282+
self.assertEqual(INT_MAX+1, getargs_I(INT_MIN))
283+
with self.assertWarns(DeprecationWarning):
284+
self.assertEqual(INT_MAX, getargs_I(INT_MIN-1))
262285

263286
self.assertEqual(42, getargs_I(42))
264287

265-
self.assertEqual(VERY_LARGE & UINT_MAX, getargs_I(VERY_LARGE))
288+
with self.assertWarns(DeprecationWarning):
289+
self.assertEqual(UINT_MAX & VERY_LARGE, getargs_I(VERY_LARGE))
290+
with self.assertWarns(DeprecationWarning):
291+
self.assertEqual(UINT_MAX & -VERY_LARGE, getargs_I(-VERY_LARGE))
266292

267293
def test_k(self):
268294
from _testcapi import getargs_k
@@ -283,11 +309,18 @@ def test_k(self):
283309
self.assertEqual(ULONG_MAX, getargs_k(-1))
284310
self.assertEqual(0, getargs_k(0))
285311
self.assertEqual(ULONG_MAX, getargs_k(ULONG_MAX))
286-
self.assertEqual(0, getargs_k(ULONG_MAX+1))
312+
with self.assertWarns(DeprecationWarning):
313+
self.assertEqual(0, getargs_k(ULONG_MAX+1))
314+
self.assertEqual(LONG_MAX+1, getargs_k(LONG_MIN))
315+
with self.assertWarns(DeprecationWarning):
316+
self.assertEqual(LONG_MAX, getargs_k(LONG_MIN-1))
287317

288318
self.assertEqual(42, getargs_k(42))
289319

290-
self.assertEqual(VERY_LARGE & ULONG_MAX, getargs_k(VERY_LARGE))
320+
with self.assertWarns(DeprecationWarning):
321+
self.assertEqual(ULONG_MAX & VERY_LARGE, getargs_k(VERY_LARGE))
322+
with self.assertWarns(DeprecationWarning):
323+
self.assertEqual(ULONG_MAX & -VERY_LARGE, getargs_k(-VERY_LARGE))
291324

292325
class Signed_TestCase(unittest.TestCase):
293326
def test_h(self):
@@ -434,11 +467,18 @@ def test_K(self):
434467
self.assertEqual(ULLONG_MAX, getargs_K(ULLONG_MAX))
435468
self.assertEqual(0, getargs_K(0))
436469
self.assertEqual(ULLONG_MAX, getargs_K(ULLONG_MAX))
437-
self.assertEqual(0, getargs_K(ULLONG_MAX+1))
470+
with self.assertWarns(DeprecationWarning):
471+
self.assertEqual(0, getargs_K(ULLONG_MAX+1))
472+
self.assertEqual(LLONG_MAX+1, getargs_K(LLONG_MIN))
473+
with self.assertWarns(DeprecationWarning):
474+
self.assertEqual(LLONG_MAX, getargs_K(LLONG_MIN-1))
438475

439476
self.assertEqual(42, getargs_K(42))
440477

441-
self.assertEqual(VERY_LARGE & ULLONG_MAX, getargs_K(VERY_LARGE))
478+
with self.assertWarns(DeprecationWarning):
479+
self.assertEqual(ULLONG_MAX & VERY_LARGE, getargs_K(VERY_LARGE))
480+
with self.assertWarns(DeprecationWarning):
481+
self.assertEqual(ULLONG_MAX & -VERY_LARGE, getargs_K(-VERY_LARGE))
442482

443483

444484
class Float_TestCase(unittest.TestCase, FloatsAreIdenticalMixin):

0 commit comments

Comments
 (0)