]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-121115: Skip __index__ in PyLong_AsNativeBytes by default (GH-121118)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 28 Jun 2024 15:52:26 +0000 (17:52 +0200)
committerGitHub <noreply@github.com>
Fri, 28 Jun 2024 15:52:26 +0000 (15:52 +0000)
(cherry picked from commit 2894aa14f22430e9b6d4676afead6da7c79209ca)

Co-authored-by: Steve Dower <steve.dower@python.org>
Doc/c-api/long.rst
Include/cpython/longobject.h
Lib/test/test_capi/test_long.py
Misc/NEWS.d/next/Core and Builtins/2024-06-28-10-02-58.gh-issue-121115.EeSLfc.rst [new file with mode: 0644]
Objects/longobject.c

index 522c028cfb8d40663ecc220af70ffad7b40b26cf..c502a825182889db4f3bc214403412cb8d302862 100644 (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
index 96815938c8277a59a5673718b5e117f190f69d23..0d49242ff6808cac1e24d1780b51945b3caf1869 100644 (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.
index 83f894e552f9832dcb67514f009bbb2c59ad1f9e..dd456f40ae6ea1ecdd4222e8f99d2a5d361c6474 100644 (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.
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 (file)
index 0000000..aaecc87
--- /dev/null
@@ -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.
index ce3fd6b711dcd40cf3eb20f441c22376219f1a0a..d4c11e9c27e6f572d2c3ec9d132dc2b82de20996 100644 (file)
@@ -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)) {