]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-122854: Add Py_HashBuffer() function (#122855)
authorVictor Stinner <vstinner@python.org>
Fri, 30 Aug 2024 15:42:27 +0000 (17:42 +0200)
committerGitHub <noreply@github.com>
Fri, 30 Aug 2024 15:42:27 +0000 (15:42 +0000)
17 files changed:
Doc/c-api/hash.rst
Doc/whatsnew/3.14.rst
Include/cpython/pyhash.h
Include/internal/pycore_pyhash.h
Lib/test/test_capi/test_hash.py
Misc/NEWS.d/next/C_API/2024-08-09-13-12-20.gh-issue-122854.-1OgvU.rst [new file with mode: 0644]
Modules/_datetimemodule.c
Modules/_hashopenssl.c
Modules/_sre/sre.c
Modules/_testcapi/hash.c
Modules/_xxtestfuzz/fuzzer.c
Objects/bytesobject.c
Objects/codeobject.c
Objects/memoryobject.c
Objects/unicodeobject.c
Python/import.c
Python/pyhash.c

index 7345a048a4128b9e0c7dec8148818073f9b2a682..00f8cb887dc7ebfb0634b32efac02098355bcf5c 100644 (file)
@@ -89,6 +89,25 @@ See also the :c:member:`PyTypeObject.tp_hash` member and :ref:`numeric-hash`.
 
    .. versionadded:: 3.13
 
+
+.. c:function:: Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len)
+
+   Compute and return the hash value of a buffer of *len* bytes
+   starting at address *ptr*. The hash is guaranteed to match that of
+   :class:`bytes`, :class:`memoryview`, and other built-in objects
+   that implement the :ref:`buffer protocol <bufferobjects>`.
+
+   Use this function to implement hashing for immutable objects whose
+   :c:member:`~PyTypeObject.tp_richcompare` function compares to another
+   object's buffer.
+
+   *len* must be greater than or equal to ``0``.
+
+   This function always succeeds.
+
+   .. versionadded:: 3.14
+
+
 .. c:function:: Py_hash_t PyObject_GenericHash(PyObject *obj)
 
    Generic hashing function that is meant to be put into a type
index 975af420f9b375ce4a1176b0ef4192fe137532fe..bbff8ecdd121171444e4186020696917c28d520d 100644 (file)
@@ -489,6 +489,9 @@ New Features
   similar to ``sep.join(iterable)`` in Python.
   (Contributed by Victor Stinner in :gh:`121645`.)
 
+* Add :c:func:`Py_HashBuffer` to compute and return the hash value of a buffer.
+  (Contributed by Antoine Pitrou and Victor Stinner in :gh:`122854`.)
+
 
 Porting to Python 3.14
 ----------------------
index 825c034a8d8474843102de497f4f973125e0674c..876a7f0ea44f4dcc7fbb734dd781d08d281022ad 100644 (file)
@@ -45,3 +45,5 @@ PyAPI_FUNC(PyHash_FuncDef*) PyHash_GetFuncDef(void);
 
 PyAPI_FUNC(Py_hash_t) Py_HashPointer(const void *ptr);
 PyAPI_FUNC(Py_hash_t) PyObject_GenericHash(PyObject *);
+
+PyAPI_FUNC(Py_hash_t) Py_HashBuffer(const void *ptr, Py_ssize_t len);
index 0ce08900e96f0b28b0f2c34a0151ccbe2906798b..9414e7761171d2ebe529fdc7bc1381f9daca2fc8 100644 (file)
@@ -20,9 +20,6 @@ _Py_HashPointerRaw(const void *ptr)
     return (Py_hash_t)x;
 }
 
-// Export for '_datetime' shared extension
-PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void*, Py_ssize_t);
-
 /* Hash secret
  *
  * memory layout on 64 bit systems
index cb2b3635f013287df23765093c10dfd83d26fd2b..f553ffb0d90f33bc17175e987b8de077a44f33a0 100644 (file)
@@ -78,6 +78,16 @@ class CAPITest(unittest.TestCase):
         VOID_P_MAX = -1 & (2 ** (8 * SIZEOF_VOID_P) - 1)
         self.assertEqual(hash_pointer(VOID_P_MAX), -2)
 
+    def test_hash_buffer(self):
+        hash_buffer = _testcapi.hash_buffer
+
+        def check(data):
+            self.assertEqual(hash_buffer(data), hash(data))
+
+        check(b'')
+        check(b'abc')
+        check(b'x' * 1024)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/C_API/2024-08-09-13-12-20.gh-issue-122854.-1OgvU.rst b/Misc/NEWS.d/next/C_API/2024-08-09-13-12-20.gh-issue-122854.-1OgvU.rst
new file mode 100644 (file)
index 0000000..b94d8f4
--- /dev/null
@@ -0,0 +1,2 @@
+Add :c:func:`Py_HashBuffer` to compute and return the hash value of a buffer.
+Patch by Antoine Pitrou and Victor Stinner.
index 1f7d44bffe668306f09b9942400d7b9c90b76419..f6e6ba4e8eb2976654b269b32026701e832fae54 100644 (file)
@@ -3842,7 +3842,7 @@ datetime_date_replace_impl(PyDateTime_Date *self, int year, int month,
 static Py_hash_t
 generic_hash(unsigned char *data, int len)
 {
-    return _Py_HashBytes(data, len);
+    return Py_HashBuffer(data, len);
 }
 
 
index 14d9c186151232c48fe1662c6721957cf0a77044..2c9a9feecc79f0b4b4dac360bb0dbb97bf47d087 100644 (file)
@@ -25,7 +25,6 @@
 #include <stdbool.h>
 #include "Python.h"
 #include "pycore_hashtable.h"
-#include "pycore_pyhash.h"        // _Py_HashBytes()
 #include "pycore_strhex.h"        // _Py_strhex()
 #include "hashlib.h"
 
@@ -186,7 +185,7 @@ static const py_hashentry_t py_hashes[] = {
 
 static Py_uhash_t
 py_hashentry_t_hash_name(const void *key) {
-    return _Py_HashBytes(key, strlen((const char *)key));
+    return Py_HashBuffer(key, strlen((const char *)key));
 }
 
 static int
index 32f91af8dcf869e2ffc0aef37d8a46c8481d154f..2c86f8869d8e588d35fc672abe1055ad7cdc9456 100644 (file)
@@ -2944,7 +2944,7 @@ pattern_hash(PatternObject *self)
         return -1;
     }
 
-    hash2 = _Py_HashBytes(self->code, sizeof(self->code[0]) * self->codesize);
+    hash2 = Py_HashBuffer(self->code, sizeof(self->code[0]) * self->codesize);
     hash ^= hash2;
 
     hash ^= self->flags;
index 809d537bfef0d3391a9cfd66b5d36d2f62b09324..1525344a93fbcf04c98581b69d413e538ca2b08d 100644 (file)
@@ -45,6 +45,14 @@ hash_getfuncdef(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
 }
 
 
+static PyObject *
+long_from_hash(Py_hash_t hash)
+{
+    Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
+    return PyLong_FromLongLong(hash);
+}
+
+
 static PyObject *
 hash_pointer(PyObject *Py_UNUSED(module), PyObject *arg)
 {
@@ -54,8 +62,21 @@ hash_pointer(PyObject *Py_UNUSED(module), PyObject *arg)
     }
 
     Py_hash_t hash = Py_HashPointer(ptr);
-    Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
-    return PyLong_FromLongLong(hash);
+    return long_from_hash(hash);
+}
+
+
+static PyObject *
+hash_buffer(PyObject *Py_UNUSED(module), PyObject *args)
+{
+    char *ptr;
+    Py_ssize_t len;
+    if (!PyArg_ParseTuple(args, "y#", &ptr, &len)) {
+        return NULL;
+    }
+
+    Py_hash_t hash = Py_HashBuffer(ptr, len);
+    return long_from_hash(hash);
 }
 
 
@@ -64,14 +85,14 @@ object_generichash(PyObject *Py_UNUSED(module), PyObject *arg)
 {
     NULLABLE(arg);
     Py_hash_t hash = PyObject_GenericHash(arg);
-    Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
-    return PyLong_FromLongLong(hash);
+    return long_from_hash(hash);
 }
 
 
 static PyMethodDef test_methods[] = {
     {"hash_getfuncdef", hash_getfuncdef, METH_NOARGS},
     {"hash_pointer", hash_pointer, METH_O},
+    {"hash_buffer", hash_buffer, METH_VARARGS},
     {"object_generichash", object_generichash, METH_O},
     {NULL},
 };
index 6ea9f64d628530400c26a0126088375bdfffae65..a04f1412eefda1d5a9e5394e2574a15692384d9e 100644 (file)
@@ -15,7 +15,6 @@
 #endif
 
 #include <Python.h>
-#include "pycore_pyhash.h"        // _Py_HashBytes()
 #include <stdlib.h>
 #include <inttypes.h>
 
@@ -45,7 +44,7 @@ static int fuzz_builtin_int(const char* data, size_t size) {
     /* Pick a random valid base. (When the fuzzed function takes extra
        parameters, it's somewhat normal to hash the input to generate those
        parameters. We want to exercise all code paths, so we do so here.) */
-    int base = _Py_HashBytes(data, size) % 37;
+    int base = Py_HashBuffer(data, size) % 37;
     if (base == 1) {
         // 1 is the only number between 0 and 36 that is not a valid base.
         base = 0;
index c467b242b4cfc24090e0ae7e4f4a303c0e51d93a..4a7c21f2f9d94c67239bd736a7c3db68bfb5a154 100644 (file)
@@ -1598,7 +1598,7 @@ _Py_COMP_DIAG_PUSH
 _Py_COMP_DIAG_IGNORE_DEPR_DECLS
     if (a->ob_shash == -1) {
         /* Can't fail */
-        a->ob_shash = _Py_HashBytes(a->ob_sval, Py_SIZE(a));
+        a->ob_shash = Py_HashBuffer(a->ob_sval, Py_SIZE(a));
     }
     return a->ob_shash;
 _Py_COMP_DIAG_POP
index ef24b51b961eeb2fb3215fd45a0852bf65530386..6f0b3f8b9a326211434576706a79968ccd0b9cbb 100644 (file)
@@ -2561,12 +2561,12 @@ hash_const(const void *key)
     if (PySlice_Check(op)) {
         PySliceObject *s = (PySliceObject *)op;
         PyObject *data[3] = { s->start, s->stop, s->step };
-        return _Py_HashBytes(&data, sizeof(data));
+        return Py_HashBuffer(&data, sizeof(data));
     }
     else if (PyTuple_CheckExact(op)) {
         Py_ssize_t size = PyTuple_GET_SIZE(op);
         PyObject **data = _PyTuple_ITEMS(op);
-        return _Py_HashBytes(data, sizeof(PyObject *) * size);
+        return Py_HashBuffer(data, sizeof(PyObject *) * size);
     }
     Py_hash_t h = PyObject_Hash(op);
     if (h == -1) {
index 226bd6defdec5a4ea62a7458f0f64a3ce2f71ee1..498a37c1a3d86936b2debe54849be79a507e2dfd 100644 (file)
@@ -3087,7 +3087,7 @@ memory_hash(PyObject *_self)
         }
 
         /* Can't fail */
-        self->hash = _Py_HashBytes(mem, view->len);
+        self->hash = Py_HashBuffer(mem, view->len);
 
         if (mem != view->buf)
             PyMem_Free(mem);
index f4239cee8f76628781a21784457cbb329a3d055c..fc1fb78b7cab95dc450983ed665e7b9576ca97e4 100644 (file)
@@ -11688,7 +11688,7 @@ unicode_hash(PyObject *self)
     if (hash != -1) {
         return hash;
     }
-    x = _Py_HashBytes(PyUnicode_DATA(self),
+    x = Py_HashBuffer(PyUnicode_DATA(self),
                       PyUnicode_GET_LENGTH(self) * PyUnicode_KIND(self));
 
     FT_ATOMIC_STORE_SSIZE_RELAXED(_PyUnicode_HASH(self), x);
index f4c0d544fbdefac39cbc73cf9815a839cb04ad2f..c9212ec1ac0d7ca50059bec81f29deaa7e39babe 100644 (file)
@@ -1174,7 +1174,7 @@ hashtable_key_from_2_strings(PyObject *str1, PyObject *str2, const char sep)
 static Py_uhash_t
 hashtable_hash_str(const void *key)
 {
-    return _Py_HashBytes(key, strlen((const char *)key));
+    return Py_HashBuffer(key, strlen((const char *)key));
 }
 
 static int
index 1504fa201c9902f9a2fc6a870a92eff2fc75072e..216f437dd9a2d488906c74d3de332a2d59b4668e 100644 (file)
@@ -22,7 +22,7 @@ extern PyHash_FuncDef PyHash_Func;
 static PyHash_FuncDef PyHash_Func;
 #endif
 
-/* Count _Py_HashBytes() calls */
+/* Count Py_HashBuffer() calls */
 #ifdef Py_HASH_STATS
 #define Py_HASH_STATS_MAX 32
 static Py_ssize_t hashstats[Py_HASH_STATS_MAX + 1] = {0};
@@ -146,9 +146,8 @@ PyObject_GenericHash(PyObject *obj)
 }
 
 Py_hash_t
-_Py_HashBytes(const void *src, Py_ssize_t len)
+Py_HashBuffer(const void *ptr, Py_ssize_t len)
 {
-    Py_hash_t x;
     /*
       We make the hash of the empty string be 0, rather than using
       (prefix ^ suffix), since this slightly obfuscates the hash secret
@@ -161,11 +160,12 @@ _Py_HashBytes(const void *src, Py_ssize_t len)
     hashstats[(len <= Py_HASH_STATS_MAX) ? len : 0]++;
 #endif
 
+    Py_hash_t x;
 #if Py_HASH_CUTOFF > 0
     if (len < Py_HASH_CUTOFF) {
         /* Optimize hashing of very small strings with inline DJBX33A. */
         Py_uhash_t hash;
-        const unsigned char *p = src;
+        const unsigned char *p = ptr;
         hash = 5381; /* DJBX33A starts with 5381 */
 
         switch(len) {
@@ -186,10 +186,13 @@ _Py_HashBytes(const void *src, Py_ssize_t len)
     }
     else
 #endif /* Py_HASH_CUTOFF */
-        x = PyHash_Func.hash(src, len);
+    {
+        x = PyHash_Func.hash(ptr, len);
+    }
 
-    if (x == -1)
+    if (x == -1) {
         return -2;
+    }
     return x;
 }