summaryrefslogtreecommitdiffstats
path: root/Modules/hashlib.h
blob: 5ada4ef4b863ec42889b5b5858e51e7a3dbddb3c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/* 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;
    }
}