cpython/Modules/hashlib.h
Bénédikt Tran fe0e921817
gh-131876: Revert "gh-131876: extract _hashlib helpers into a separate directory (#136995) (#137307)
Revert "gh-131876: extract `_hashlib` helpers into a separate directory (#136995)"

This reverts commit 45138d3584.
2025-08-01 10:45:40 -07:00

173 lines
6.6 KiB
C

/* Common code for use by all hashlib related modules. */
#include "pycore_lock.h" // PyMutex
/*
* Internal error messages used for reporting an unsupported hash algorithm.
* The algorithm can be given by its name, a callable or a PEP-247 module.
* The same message is raised by Lib/hashlib.py::__get_builtin_constructor()
* and _hmacmodule.c::find_hash_info().
*/
#define HASHLIB_UNSUPPORTED_ALGORITHM "unsupported hash algorithm %S"
#define HASHLIB_UNSUPPORTED_STR_ALGORITHM "unsupported hash algorithm %s"
/*
* Obtain a buffer view from a buffer-like object 'obj'.
*
* On success, store the result in 'view' and return 0.
* On error, set an exception and return -1.
*/
static inline int
_Py_hashlib_get_buffer_view(PyObject *obj, Py_buffer *view)
{
if (PyUnicode_Check(obj)) {
PyErr_SetString(PyExc_TypeError,
"Strings must be encoded before hashing");
return -1;
}
if (!PyObject_CheckBuffer(obj)) {
PyErr_SetString(PyExc_TypeError,
"object supporting the buffer API required");
return -1;
}
if (PyObject_GetBuffer(obj, view, PyBUF_SIMPLE) == -1) {
return -1;
}
if (view->ndim > 1) {
PyErr_SetString(PyExc_BufferError,
"Buffer must be single dimension");
PyBuffer_Release(view);
return -1;
}
return 0;
}
/*
* Call _Py_hashlib_get_buffer_view() and check if it succeeded.
*
* On error, set an exception and execute the ERRACTION statements.
*/
#define GET_BUFFER_VIEW_OR_ERROR(OBJ, VIEW, ERRACTION) \
do { \
if (_Py_hashlib_get_buffer_view(OBJ, VIEW) < 0) { \
assert(PyErr_Occurred()); \
ERRACTION; \
} \
} while (0)
#define GET_BUFFER_VIEW_OR_ERROUT(OBJ, VIEW) \
GET_BUFFER_VIEW_OR_ERROR(OBJ, VIEW, return NULL)
/*
* Helper code to synchronize access to the hash object when the GIL is
* released around a CPU consuming hashlib operation.
*
* Code accessing a mutable part of the hash object must be enclosed in
* an HASHLIB_{ACQUIRE,RELEASE}_LOCK block or explicitly acquire and release
* the mutex inside a Py_BEGIN_ALLOW_THREADS -- Py_END_ALLOW_THREADS block if
* they wish to release the GIL for an operation.
*/
#define HASHLIB_OBJECT_HEAD \
PyObject_HEAD \
/* Guard against race conditions during incremental update(). */ \
PyMutex mutex;
#define HASHLIB_INIT_MUTEX(OBJ) \
do { \
(OBJ)->mutex = (PyMutex){0}; \
} while (0)
#define HASHLIB_ACQUIRE_LOCK(OBJ) PyMutex_Lock(&(OBJ)->mutex)
#define HASHLIB_RELEASE_LOCK(OBJ) PyMutex_Unlock(&(OBJ)->mutex)
/*
* Message length above which the GIL is to be released
* when performing hashing operations.
*/
#define HASHLIB_GIL_MINSIZE 2048
// Macros for executing code while conditionally holding the GIL.
//
// These only drop the GIL if the lock acquisition itself is likely to
// block. Thus the non-blocking acquire gating the GIL release for a
// blocking lock acquisition. The intent of these macros is to surround
// the assumed always "fast" operations that you aren't releasing the
// GIL around.
/*
* Execute a suite of C statements 'STATEMENTS'.
*
* The GIL is held if 'SIZE' is below the HASHLIB_GIL_MINSIZE threshold.
*/
#define HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(SIZE, STATEMENTS) \
do { \
if ((SIZE) > HASHLIB_GIL_MINSIZE) { \
Py_BEGIN_ALLOW_THREADS \
STATEMENTS; \
Py_END_ALLOW_THREADS \
} \
else { \
STATEMENTS; \
} \
} while (0)
/*
* Lock 'OBJ' and execute a suite of C statements 'STATEMENTS'.
*
* The GIL is held if 'SIZE' is below the HASHLIB_GIL_MINSIZE threshold.
*/
#define HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(OBJ, SIZE, STATEMENTS) \
do { \
if ((SIZE) > HASHLIB_GIL_MINSIZE) { \
Py_BEGIN_ALLOW_THREADS \
HASHLIB_ACQUIRE_LOCK(OBJ); \
STATEMENTS; \
HASHLIB_RELEASE_LOCK(OBJ); \
Py_END_ALLOW_THREADS \
} \
else { \
HASHLIB_ACQUIRE_LOCK(OBJ); \
STATEMENTS; \
HASHLIB_RELEASE_LOCK(OBJ); \
} \
} while (0)
static inline int
_Py_hashlib_data_argument(PyObject **res, PyObject *data, PyObject *string)
{
if (data != NULL && string == NULL) {
// called as H(data) or H(data=...)
*res = data;
return 1;
}
else if (data == NULL && string != NULL) {
// called as H(string=...)
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"the 'string' keyword parameter is deprecated since "
"Python 3.15 and slated for removal in Python 3.19; "
"use the 'data' keyword parameter or pass the data "
"to hash as a positional argument instead", 1) < 0)
{
*res = NULL;
return -1;
}
*res = string;
return 1;
}
else if (data == NULL && string == NULL) {
// fast path when no data is given
assert(!PyErr_Occurred());
*res = NULL;
return 0;
}
else {
// called as H(data=..., string)
*res = NULL;
PyErr_SetString(PyExc_TypeError,
"'data' and 'string' are mutually exclusive "
"and support for 'string' keyword parameter "
"is slated for removal in a future version.");
return -1;
}
}