gh-121115: Skip __index__ in PyLong_AsNativeBytes by default (GH-121118)

This commit is contained in:
Steve Dower 2024-06-28 16:26:21 +01:00 committed by GitHub
parent 81a654a342
commit 2894aa14f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 33 additions and 9 deletions

View File

@ -405,14 +405,13 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
Passing zero to *n_bytes* will return the size of a buffer that would
be large enough to hold the value. This may be larger than technically
necessary, but not unreasonably so.
necessary, but not unreasonably so. If *n_bytes=0*, *buffer* may be
``NULL``.
.. note::
Passing *n_bytes=0* to this function is not an accurate way to determine
the bit length of a value.
If *n_bytes=0*, *buffer* may be ``NULL``.
the bit length of the value.
To get at the entire Python value of an unknown size, the function can be
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.
.. c:macro:: Py_ASNATIVEBYTES_NATIVE_ENDIAN ``3``
.. c:macro:: Py_ASNATIVEBYTES_UNSIGNED_BUFFER ``4``
.. c:macro:: Py_ASNATIVEBYTES_REJECT_NEGATIVE ``8``
.. c:macro:: Py_ASNATIVEBYTES_ALLOW_INDEX ``16``
============================================= ======
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.
provided there is enough space for at least one sign bit, regardless of
whether ``Py_ASNATIVEBYTES_UNSIGNED_BUFFER`` was specified.
If ``Py_ASNATIVEBYTES_ALLOW_INDEX`` is specified and a non-integer value is
passed, its :meth:`~object.__index__` method will be called first. This may
result in Python code executing and other threads being allowed to run, which
could cause changes to other objects or values in use. When *flags* is
``-1``, this option is not set, and non-integer values will raise
:exc:`TypeError`.
.. note::
With the default *flags* (``-1``, or *UNSIGNED_BUFFER* without

View File

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

View File

@ -496,8 +496,9 @@ class LongTests(unittest.TestCase):
"PyLong_AsNativeBytes(v, <unknown>, 0, -1)")
self.assertEqual(buffer, b"\x5a",
"buffer overwritten when it should not have been")
# Also check via the __index__ path
self.assertEqual(expect, asnativebytes(Index(v), buffer, 0, -1),
# Also check via the __index__ path.
# We pass Py_ASNATIVEBYTES_NATIVE_ENDIAN | ALLOW_INDEX
self.assertEqual(expect, asnativebytes(Index(v), buffer, 0, 3 | 16),
"PyLong_AsNativeBytes(Index(v), <unknown>, 0, -1)")
self.assertEqual(buffer, b"\x5a",
"buffer overwritten when it should not have been")
@ -607,6 +608,12 @@ class LongTests(unittest.TestCase):
with self.assertRaises(ValueError):
asnativebytes(-1, buffer, 0, 8)
# Ensure omitting Py_ASNATIVEBYTES_ALLOW_INDEX raises on __index__ value
with self.assertRaises(TypeError):
asnativebytes(Index(1), buffer, 0, -1)
with self.assertRaises(TypeError):
asnativebytes(Index(1), buffer, 0, 3)
# Check a few error conditions. These are validated in code, but are
# unspecified in docs, so if we make changes to the implementation, it's
# fine to just update these tests rather than preserve the behaviour.

View File

@ -0,0 +1,3 @@
:c:func:`PyLong_AsNativeBytes` no longer uses :meth:`~object.__index__`
methods by default. The ``Py_ASNATIVEBYTES_ALLOW_INDEX`` flag has been added
to allow it.

View File

@ -1128,13 +1128,17 @@ PyLong_AsNativeBytes(PyObject* vv, void* buffer, Py_ssize_t n, int flags)
if (PyLong_Check(vv)) {
v = (PyLongObject *)vv;
}
else {
else if (flags != -1 && (flags & Py_ASNATIVEBYTES_ALLOW_INDEX)) {
v = (PyLongObject *)_PyNumber_Index(vv);
if (v == NULL) {
return -1;
}
do_decref = 1;
}
else {
PyErr_Format(PyExc_TypeError, "expect int, got %T", vv);
return -1;
}
if ((flags != -1 && (flags & Py_ASNATIVEBYTES_REJECT_NEGATIVE))
&& _PyLong_IsNegative(v)) {