summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@python.org>2024-06-28 15:26:21 (GMT)
committerGitHub <noreply@github.com>2024-06-28 15:26:21 (GMT)
commit2894aa14f22430e9b6d4676afead6da7c79209ca (patch)
tree19684e156c152deb1d36614bd2749ecd6a3a4792
parent81a654a3425eaa05a51342509089533c1f623f1b (diff)
downloadcpython-2894aa14f22430e9b6d4676afead6da7c79209ca.zip
cpython-2894aa14f22430e9b6d4676afead6da7c79209ca.tar.gz
cpython-2894aa14f22430e9b6d4676afead6da7c79209ca.tar.bz2
gh-121115: Skip __index__ in PyLong_AsNativeBytes by default (GH-121118)
-rw-r--r--Doc/c-api/long.rst15
-rw-r--r--Include/cpython/longobject.h7
-rw-r--r--Lib/test/test_capi/test_long.py11
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2024-06-28-10-02-58.gh-issue-121115.EeSLfc.rst3
-rw-r--r--Objects/longobject.c6
5 files changed, 33 insertions, 9 deletions
diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst
index a0e111a..4216291 100644
--- a/Doc/c-api/long.rst
+++ b/Doc/c-api/long.rst
@@ -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
diff --git a/Include/cpython/longobject.h b/Include/cpython/longobject.h
index 19a6722..e7e0c3d 100644
--- a/Include/cpython/longobject.h
+++ b/Include/cpython/longobject.h
@@ -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.
diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py
index 06a29b5..7e8d571 100644
--- a/Lib/test/test_capi/test_long.py
+++ b/Lib/test/test_capi/test_long.py
@@ -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.
diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-06-28-10-02-58.gh-issue-121115.EeSLfc.rst b/Misc/NEWS.d/next/Core and Builtins/2024-06-28-10-02-58.gh-issue-121115.EeSLfc.rst
new file mode 100644
index 0000000..aaecc87
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2024-06-28-10-02-58.gh-issue-121115.EeSLfc.rst
@@ -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.
diff --git a/Objects/longobject.c b/Objects/longobject.c
index 86afec9..4ca259f 100644
--- a/Objects/longobject.c
+++ b/Objects/longobject.c
@@ -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)) {