Skip to content

Commit 2894aa1

Browse files
authored
gh-121115: Skip __index__ in PyLong_AsNativeBytes by default (GH-121118)
1 parent 81a654a commit 2894aa1

File tree

5 files changed

+33
-9
lines changed

5 files changed

+33
-9
lines changed

Doc/c-api/long.rst

+11-4
Original file line numberDiff line numberDiff line change
@@ -405,14 +405,13 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
405405
406406
Passing zero to *n_bytes* will return the size of a buffer that would
407407
be large enough to hold the value. This may be larger than technically
408-
necessary, but not unreasonably so.
408+
necessary, but not unreasonably so. If *n_bytes=0*, *buffer* may be
409+
``NULL``.
409410
410411
.. note::
411412
412413
Passing *n_bytes=0* to this function is not an accurate way to determine
413-
the bit length of a value.
414-
415-
If *n_bytes=0*, *buffer* may be ``NULL``.
414+
the bit length of the value.
416415
417416
To get at the entire Python value of an unknown size, the function can be
418417
called twice: first to determine the buffer size, then to fill it::
@@ -462,6 +461,7 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
462461
.. c:macro:: Py_ASNATIVEBYTES_NATIVE_ENDIAN ``3``
463462
.. c:macro:: Py_ASNATIVEBYTES_UNSIGNED_BUFFER ``4``
464463
.. c:macro:: Py_ASNATIVEBYTES_REJECT_NEGATIVE ``8``
464+
.. c:macro:: Py_ASNATIVEBYTES_ALLOW_INDEX ``16``
465465
============================================= ======
466466
467467
Specifying ``Py_ASNATIVEBYTES_NATIVE_ENDIAN`` will override any other endian
@@ -483,6 +483,13 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
483483
provided there is enough space for at least one sign bit, regardless of
484484
whether ``Py_ASNATIVEBYTES_UNSIGNED_BUFFER`` was specified.
485485
486+
If ``Py_ASNATIVEBYTES_ALLOW_INDEX`` is specified and a non-integer value is
487+
passed, its :meth:`~object.__index__` method will be called first. This may
488+
result in Python code executing and other threads being allowed to run, which
489+
could cause changes to other objects or values in use. When *flags* is
490+
``-1``, this option is not set, and non-integer values will raise
491+
:exc:`TypeError`.
492+
486493
.. note::
487494
488495
With the default *flags* (``-1``, or *UNSIGNED_BUFFER* without

Include/cpython/longobject.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ PyAPI_FUNC(PyObject*) PyLong_FromUnicodeObject(PyObject *u, int base);
1010
#define Py_ASNATIVEBYTES_NATIVE_ENDIAN 3
1111
#define Py_ASNATIVEBYTES_UNSIGNED_BUFFER 4
1212
#define Py_ASNATIVEBYTES_REJECT_NEGATIVE 8
13+
#define Py_ASNATIVEBYTES_ALLOW_INDEX 16
1314

1415
/* PyLong_AsNativeBytes: Copy the integer value to a native variable.
1516
buffer points to the first byte of the variable.
@@ -20,8 +21,10 @@ PyAPI_FUNC(PyObject*) PyLong_FromUnicodeObject(PyObject *u, int base);
2021
* 2 - native endian
2122
* 4 - unsigned destination (e.g. don't reject copying 255 into one byte)
2223
* 8 - raise an exception for negative inputs
23-
If flags is -1 (all bits set), native endian is used and value truncation
24-
behaves most like C (allows negative inputs and allow MSB set).
24+
* 16 - call __index__ on non-int types
25+
If flags is -1 (all bits set), native endian is used, value truncation
26+
behaves most like C (allows negative inputs and allow MSB set), and non-int
27+
objects will raise a TypeError.
2528
Big endian mode will write the most significant byte into the address
2629
directly referenced by buffer; little endian will write the least significant
2730
byte into that address.

Lib/test/test_capi/test_long.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -496,8 +496,9 @@ def test_long_asnativebytes(self):
496496
"PyLong_AsNativeBytes(v, <unknown>, 0, -1)")
497497
self.assertEqual(buffer, b"\x5a",
498498
"buffer overwritten when it should not have been")
499-
# Also check via the __index__ path
500-
self.assertEqual(expect, asnativebytes(Index(v), buffer, 0, -1),
499+
# Also check via the __index__ path.
500+
# We pass Py_ASNATIVEBYTES_NATIVE_ENDIAN | ALLOW_INDEX
501+
self.assertEqual(expect, asnativebytes(Index(v), buffer, 0, 3 | 16),
501502
"PyLong_AsNativeBytes(Index(v), <unknown>, 0, -1)")
502503
self.assertEqual(buffer, b"\x5a",
503504
"buffer overwritten when it should not have been")
@@ -607,6 +608,12 @@ def test_long_asnativebytes(self):
607608
with self.assertRaises(ValueError):
608609
asnativebytes(-1, buffer, 0, 8)
609610

611+
# Ensure omitting Py_ASNATIVEBYTES_ALLOW_INDEX raises on __index__ value
612+
with self.assertRaises(TypeError):
613+
asnativebytes(Index(1), buffer, 0, -1)
614+
with self.assertRaises(TypeError):
615+
asnativebytes(Index(1), buffer, 0, 3)
616+
610617
# Check a few error conditions. These are validated in code, but are
611618
# unspecified in docs, so if we make changes to the implementation, it's
612619
# fine to just update these tests rather than preserve the behaviour.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:c:func:`PyLong_AsNativeBytes` no longer uses :meth:`~object.__index__`
2+
methods by default. The ``Py_ASNATIVEBYTES_ALLOW_INDEX`` flag has been added
3+
to allow it.

Objects/longobject.c

+5-1
Original file line numberDiff line numberDiff line change
@@ -1128,13 +1128,17 @@ PyLong_AsNativeBytes(PyObject* vv, void* buffer, Py_ssize_t n, int flags)
11281128
if (PyLong_Check(vv)) {
11291129
v = (PyLongObject *)vv;
11301130
}
1131-
else {
1131+
else if (flags != -1 && (flags & Py_ASNATIVEBYTES_ALLOW_INDEX)) {
11321132
v = (PyLongObject *)_PyNumber_Index(vv);
11331133
if (v == NULL) {
11341134
return -1;
11351135
}
11361136
do_decref = 1;
11371137
}
1138+
else {
1139+
PyErr_Format(PyExc_TypeError, "expect int, got %T", vv);
1140+
return -1;
1141+
}
11381142

11391143
if ((flags != -1 && (flags & Py_ASNATIVEBYTES_REJECT_NEGATIVE))
11401144
&& _PyLong_IsNegative(v)) {

0 commit comments

Comments
 (0)