Skip to content

Commit

Permalink
gh-121115: Skip __index__ in PyLong_AsNativeBytes by default (GH-121118)
Browse files Browse the repository at this point in the history
(cherry picked from commit 2894aa1)

Co-authored-by: Steve Dower <[email protected]>
  • Loading branch information
miss-islington and zooba authored Jun 28, 2024
1 parent 99de20d commit 58a3c3c
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 9 deletions.
15 changes: 11 additions & 4 deletions Doc/c-api/long.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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::
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
7 changes: 5 additions & 2 deletions Include/cpython/longobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
11 changes: 9 additions & 2 deletions Lib/test/test_capi/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,9 @@ def test_long_asnativebytes(self):
"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")
Expand Down Expand Up @@ -607,6 +608,12 @@ def test_long_asnativebytes(self):
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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 5 additions & 1 deletion Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1116,13 +1116,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)) {
Expand Down

0 comments on commit 58a3c3c

Please sign in to comment.