Skip to content

Commit fbc7873

Browse files
gh-132629: Deprecate acception out of range values for unsigned integers in PyArg_Parse
For unsigned integer formats in the PyArg_Parse* funcions, 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 62ff86f commit fbc7873

File tree

19 files changed

+657
-178
lines changed

19 files changed

+657
-178
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 the C type or less than the
248+
minimal value for the corresponding signed integer type.
247249

248250
``b`` (:class:`int`) [unsigned char]
249251
Convert a nonnegative Python integer to an unsigned tiny integer, stored in a C
@@ -252,34 +254,31 @@ 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
``L`` (:class:`int`) [long long]
278278
Convert a Python integer to a C :c:expr:`long long`.
279279

280280
``K`` (:class:`int`) [unsigned long long]
281-
Convert a Python integer to a C :c:expr:`unsigned long long`
282-
without overflow checking.
281+
Convert a Python integer to a C :c:expr:`unsigned long long`.
283282

284283
``n`` (:class:`int`) [:c:type:`Py_ssize_t`]
285284
Convert a Python integer to a C :c:type:`Py_ssize_t`.
@@ -304,6 +303,14 @@ small to receive the value.
304303
``D`` (:class:`complex`) [Py_complex]
305304
Convert a Python complex number to a C :c:type:`Py_complex` structure.
306305

306+
.. deprecated:: next
307+
308+
For unsigned integer formats ``B``, ``H``, ``I``, ``k`` and ``K``,
309+
:exc:`DeprecationWarning` is emitted when the value is larger than
310+
the maximal value the C type or less than the minimal value for
311+
the corresponding signed integer type.
312+
313+
307314
Other objects
308315
-------------
309316

Doc/whatsnew/3.14.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2000,6 +2000,13 @@ Deprecated
20002000
or a :term:`borrowed reference`.
20012001
(Contributed by Serhiy Storchaka in :gh:`50333`.)
20022002

2003+
* For unsigned integer formats in :c:func:`PyArg_ParseTuple`,
2004+
accepting Python integers with value that is larger than
2005+
the maximal value the corresponding C type or less than
2006+
the minimal value for the corresponding signed integer type
2007+
is now deprecated.
2008+
(Contributed by Serhiy Storchaka in :gh:`132629`.)
2009+
20032010
* The previously undocumented function :c:func:`PySequence_In` is :term:`soft deprecated`.
20042011
Use :c:func:`PySequence_Contains` instead.
20052012
(Contributed by Yuki Kobayashi in :gh:`127896`.)

Lib/test/clinic.test.c

Lines changed: 76 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,21 @@ 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_UNSIGNED_BUFFER);
1452+
if (_bytes < 0) {
1453+
goto exit;
1454+
}
1455+
if ((size_t)_bytes > sizeof(unsigned long)) {
1456+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
1457+
"integer value out of range", 1) < 0)
1458+
{
1459+
goto exit;
1460+
}
1461+
}
1462+
}
14181463
skip_optional:
14191464
return_value = test_unsigned_long_converter_impl(module, a, b, c);
14201465

@@ -1425,7 +1470,7 @@ test_unsigned_long_converter(PyObject *module, PyObject *const *args, Py_ssize_t
14251470
static PyObject *
14261471
test_unsigned_long_converter_impl(PyObject *module, unsigned long a,
14271472
unsigned long b, unsigned long c)
1428-
/*[clinic end generated code: output=540bb0ba2894e1fe input=f450d94cae1ef73b]*/
1473+
/*[clinic end generated code: output=a7c8b071bc71cf81 input=f450d94cae1ef73b]*/
14291474

14301475

14311476
/*[clinic input]
@@ -1529,7 +1574,21 @@ test_unsigned_long_long_converter(PyObject *module, PyObject *const *args, Py_ss
15291574
_PyArg_BadArgument("test_unsigned_long_long_converter", "argument 3", "int", args[2]);
15301575
goto exit;
15311576
}
1532-
c = PyLong_AsUnsignedLongLongMask(args[2]);
1577+
{
1578+
Py_ssize_t _bytes = PyLong_AsNativeBytes(args[2], &c, sizeof(unsigned long long),
1579+
Py_ASNATIVEBYTES_NATIVE_ENDIAN |
1580+
Py_ASNATIVEBYTES_UNSIGNED_BUFFER);
1581+
if (_bytes < 0) {
1582+
goto exit;
1583+
}
1584+
if ((size_t)_bytes > sizeof(unsigned long long)) {
1585+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
1586+
"integer value out of range", 1) < 0)
1587+
{
1588+
goto exit;
1589+
}
1590+
}
1591+
}
15331592
skip_optional:
15341593
return_value = test_unsigned_long_long_converter_impl(module, a, b, c);
15351594

@@ -1542,7 +1601,7 @@ test_unsigned_long_long_converter_impl(PyObject *module,
15421601
unsigned long long a,
15431602
unsigned long long b,
15441603
unsigned long long c)
1545-
/*[clinic end generated code: output=3d69994f618b46bb input=a15115dc41866ff4]*/
1604+
/*[clinic end generated code: output=bf30fe0bb51cb037 input=a15115dc41866ff4]*/
15461605

15471606

15481607
/*[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):
@@ -432,11 +465,18 @@ def test_K(self):
432465

433466
self.assertEqual(ULLONG_MAX, getargs_K(ULLONG_MAX))
434467
self.assertEqual(0, getargs_K(0))
435-
self.assertEqual(0, getargs_K(ULLONG_MAX+1))
468+
with self.assertWarns(DeprecationWarning):
469+
self.assertEqual(0, getargs_K(ULLONG_MAX+1))
470+
self.assertEqual(LLONG_MAX+1, getargs_K(LLONG_MIN))
471+
with self.assertWarns(DeprecationWarning):
472+
self.assertEqual(LLONG_MAX, getargs_K(LLONG_MIN-1))
436473

437474
self.assertEqual(42, getargs_K(42))
438475

439-
self.assertEqual(VERY_LARGE & ULLONG_MAX, getargs_K(VERY_LARGE))
476+
with self.assertWarns(DeprecationWarning):
477+
self.assertEqual(ULLONG_MAX & VERY_LARGE, getargs_K(VERY_LARGE))
478+
with self.assertWarns(DeprecationWarning):
479+
self.assertEqual(ULLONG_MAX & -VERY_LARGE, getargs_K(-VERY_LARGE))
440480

441481

442482
class Float_TestCase(unittest.TestCase, FloatsAreIdenticalMixin):

0 commit comments

Comments
 (0)