https://github.com/python/cpython/commit/2894aa14f22430e9b6d4676afead6da7c79209ca
commit: 2894aa14f22430e9b6d4676afead6da7c79209ca
branch: main
author: Steve Dower <[email protected]>
committer: zooba <[email protected]>
date: 2024-06-28T16:26:21+01:00
summary:

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

files:
A Misc/NEWS.d/next/Core and 
Builtins/2024-06-28-10-02-58.gh-issue-121115.EeSLfc.rst
M Doc/c-api/long.rst
M Include/cpython/longobject.h
M Lib/test/test_capi/test_long.py
M Objects/longobject.c

diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst
index a0e111af5996d7..42162914c0aec8 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 19a6722d07734a..e7e0c3d9764f20 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 06a29b5a0505b4..7e8d571ae234d1 100644
--- a/Lib/test/test_capi/test_long.py
+++ b/Lib/test/test_capi/test_long.py
@@ -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")
@@ -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.
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 00000000000000..aaecc873551cc7
--- /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 86afec9a414134..4ca259fb08e8c7 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)) {

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to