]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-121115: Skip __index__ in PyLong_AsNativeBytes by default (GH-121118)
authorSteve Dower <steve.dower@python.org>
Fri, 28 Jun 2024 15:26:21 +0000 (16:26 +0100)
committerGitHub <noreply@github.com>
Fri, 28 Jun 2024 15:26:21 +0000 (16:26 +0100)
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 a0e111af5996d70372933586361dfd9d03e16eb2..42162914c0aec81c36c8775305a6adb531650246 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 19a6722d07734a366c6185338a400b2c86ff3338..e7e0c3d9764f20e1a17b6dc2c1fa3b486b2a05cd 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 06a29b5a0505b427c60806815875365b99f87692..7e8d571ae234d1f97b490948ffd8419edaa93c6e 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 86afec9a414134153831392ce28f68cd318bd587..4ca259fb08e8c7b7a13dd202b0b7fe40dd376a73 100644 (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)) {