Coverage Report

Created: 2026-06-01 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython3/Python/structmember.c
Line
Count
Source
1
2
/* Map C struct members to Python object attributes */
3
4
#include "Python.h"
5
#include "pycore_abstract.h"      // _PyNumber_Index()
6
#include "pycore_descrobject.h"   // _PyMember_GetOffset()
7
#include "pycore_long.h"          // _PyLong_IsNegative()
8
#include "pycore_object.h"        // _Py_TryIncrefCompare(), FT_ATOMIC_*()
9
#include "pycore_critical_section.h"
10
11
12
static inline PyObject *
13
member_get_object(const char *addr, const char *obj_addr, PyMemberDef *l)
14
80
{
15
80
    PyObject *v = FT_ATOMIC_LOAD_PTR(*(PyObject **) addr);
16
80
    if (v == NULL) {
17
0
        PyErr_Format(PyExc_AttributeError,
18
0
                     "'%T' object has no attribute '%s'",
19
0
                     (PyObject *)obj_addr, l->name);
20
0
    }
21
80
    return v;
22
80
}
23
24
void *
25
_PyMember_GetOffset(PyObject *obj, PyMemberDef *mp)
26
116k
{
27
116k
    unsigned char *addr = (unsigned char *)obj + mp->offset;
28
116k
    if (mp->flags & _Py_AFTER_ITEMS) {
29
0
        PyTypeObject *type = Py_TYPE(obj);
30
0
        addr += _Py_SIZE_ROUND_UP(Py_SIZE(obj) * type->tp_itemsize, SIZEOF_VOID_P);
31
0
    }
32
116k
    return addr;
33
116k
}
34
35
PyObject *
36
PyMember_GetOne(const char *obj_addr, PyMemberDef *l)
37
6.29k
{
38
6.29k
    PyObject *v;
39
6.29k
    if (l->flags & Py_RELATIVE_OFFSET) {
40
0
        PyErr_SetString(
41
0
            PyExc_SystemError,
42
0
            "PyMember_GetOne used with Py_RELATIVE_OFFSET");
43
0
        return NULL;
44
0
    }
45
46
6.29k
    const void *addr = _PyMember_GetOffset((PyObject *)obj_addr, l);
47
6.29k
    switch (l->type) {
48
0
    case Py_T_BOOL:
49
0
        v = PyBool_FromLong(FT_ATOMIC_LOAD_CHAR_RELAXED(*(char*)addr));
50
0
        break;
51
0
    case Py_T_BYTE:
52
0
        v = PyLong_FromLong(FT_ATOMIC_LOAD_CHAR_RELAXED(*(char*)addr));
53
0
        break;
54
0
    case Py_T_UBYTE:
55
0
        v = PyLong_FromUnsignedLong(FT_ATOMIC_LOAD_UCHAR_RELAXED(*(unsigned char*)addr));
56
0
        break;
57
0
    case Py_T_SHORT:
58
0
        v = PyLong_FromLong(FT_ATOMIC_LOAD_SHORT_RELAXED(*(short*)addr));
59
0
        break;
60
0
    case Py_T_USHORT:
61
0
        v = PyLong_FromUnsignedLong(FT_ATOMIC_LOAD_USHORT_RELAXED(*(unsigned short*)addr));
62
0
        break;
63
0
    case Py_T_INT:
64
0
        v = PyLong_FromLong(FT_ATOMIC_LOAD_INT_RELAXED(*(int*)addr));
65
0
        break;
66
0
    case Py_T_UINT:
67
0
        v = PyLong_FromUnsignedLong(FT_ATOMIC_LOAD_UINT_RELAXED(*(unsigned int*)addr));
68
0
        break;
69
0
    case Py_T_LONG:
70
0
        v = PyLong_FromLong(FT_ATOMIC_LOAD_LONG_RELAXED(*(long*)addr));
71
0
        break;
72
0
    case Py_T_ULONG:
73
0
        v = PyLong_FromUnsignedLong(FT_ATOMIC_LOAD_ULONG_RELAXED(*(unsigned long*)addr));
74
0
        break;
75
1.39k
    case Py_T_PYSSIZET:
76
1.39k
        v = PyLong_FromSsize_t(FT_ATOMIC_LOAD_SSIZE_RELAXED(*(Py_ssize_t*)addr));
77
1.39k
        break;
78
0
    case Py_T_FLOAT:
79
0
        v = PyFloat_FromDouble((double)FT_ATOMIC_LOAD_FLOAT_RELAXED(*(float*)addr));
80
0
        break;
81
0
    case Py_T_DOUBLE:
82
0
        v = PyFloat_FromDouble(FT_ATOMIC_LOAD_DOUBLE_RELAXED(*(double*)addr));
83
0
        break;
84
1
    case Py_T_STRING:
85
1
        if (*(char**)addr == NULL) {
86
0
            v = Py_NewRef(Py_None);
87
0
        }
88
1
        else
89
1
            v = PyUnicode_FromString(*(char**)addr);
90
1
        break;
91
0
    case Py_T_STRING_INPLACE:
92
0
        v = PyUnicode_FromString((char*)addr);
93
0
        break;
94
0
    case Py_T_CHAR: {
95
0
        char char_val = FT_ATOMIC_LOAD_CHAR_RELAXED(*(char*)addr);
96
0
        v = PyUnicode_FromStringAndSize(&char_val, 1);
97
0
        break;
98
0
    }
99
4.81k
    case _Py_T_OBJECT:
100
4.81k
        v = FT_ATOMIC_LOAD_PTR(*(PyObject **) addr);
101
4.81k
        if (v != NULL) {
102
#ifdef Py_GIL_DISABLED
103
            if (!_Py_TryIncrefCompare((PyObject **) addr, v)) {
104
                Py_BEGIN_CRITICAL_SECTION((PyObject *) obj_addr);
105
                v = FT_ATOMIC_LOAD_PTR(*(PyObject **) addr);
106
                Py_XINCREF(v);
107
                Py_END_CRITICAL_SECTION();
108
            }
109
#else
110
4.81k
            Py_INCREF(v);
111
4.81k
#endif
112
4.81k
        }
113
4.81k
        if (v == NULL) {
114
0
            v = Py_None;
115
0
        }
116
4.81k
        break;
117
80
    case Py_T_OBJECT_EX:
118
80
        v = member_get_object(addr, obj_addr, l);
119
80
#ifndef Py_GIL_DISABLED
120
80
        Py_XINCREF(v);
121
#else
122
        if (v != NULL) {
123
            if (!_Py_TryIncrefCompare((PyObject **) addr, v)) {
124
                Py_BEGIN_CRITICAL_SECTION((PyObject *) obj_addr);
125
                v = member_get_object(addr, obj_addr, l);
126
                Py_XINCREF(v);
127
                Py_END_CRITICAL_SECTION();
128
            }
129
        }
130
#endif
131
80
        break;
132
0
    case Py_T_LONGLONG:
133
0
        v = PyLong_FromLongLong(FT_ATOMIC_LOAD_LLONG_RELAXED(*(long long *)addr));
134
0
        break;
135
0
    case Py_T_ULONGLONG:
136
0
        v = PyLong_FromUnsignedLongLong(FT_ATOMIC_LOAD_ULLONG_RELAXED(*(unsigned long long *)addr));
137
0
        break;
138
0
    case _Py_T_NONE:
139
        // doesn't require free-threading code path
140
0
        v = Py_NewRef(Py_None);
141
0
        break;
142
0
    default:
143
0
        PyErr_SetString(PyExc_SystemError, "bad memberdescr type");
144
0
        v = NULL;
145
6.29k
    }
146
6.29k
    return v;
147
6.29k
}
148
149
#define WARN(msg)                                               \
150
0
    do {                                                        \
151
0
    if (PyErr_WarnEx(PyExc_RuntimeWarning, msg, 1) < 0)         \
152
0
        return -1;                                              \
153
0
    } while (0)
154
155
int
156
PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
157
107k
{
158
107k
    PyObject *oldv;
159
107k
    if (l->flags & Py_RELATIVE_OFFSET) {
160
0
        PyErr_SetString(
161
0
            PyExc_SystemError,
162
0
            "PyMember_SetOne used with Py_RELATIVE_OFFSET");
163
0
        return -1;
164
0
    }
165
166
107k
    PyObject *obj = (PyObject *)addr;
167
107k
    addr = _PyMember_GetOffset(obj, l);
168
169
107k
    if ((l->flags & Py_READONLY))
170
0
    {
171
0
        PyErr_SetString(PyExc_AttributeError, "readonly attribute");
172
0
        return -1;
173
0
    }
174
107k
    if (v == NULL && l->type != Py_T_OBJECT_EX && l->type != _Py_T_OBJECT) {
175
0
        PyErr_SetString(PyExc_TypeError,
176
0
                        "can't delete numeric/char attribute");
177
0
        return -1;
178
0
    }
179
107k
    switch (l->type) {
180
0
    case Py_T_BOOL:{
181
0
        if (!PyBool_Check(v)) {
182
0
            PyErr_SetString(PyExc_TypeError,
183
0
                            "attribute value type must be bool");
184
0
            return -1;
185
0
        }
186
0
        if (v == Py_True)
187
0
            FT_ATOMIC_STORE_CHAR_RELAXED(*(char*)addr, 1);
188
0
        else
189
0
            FT_ATOMIC_STORE_CHAR_RELAXED(*(char*)addr, 0);
190
0
        break;
191
0
        }
192
0
    case Py_T_BYTE:{
193
0
        long long_val = PyLong_AsLong(v);
194
0
        if ((long_val == -1) && PyErr_Occurred())
195
0
            return -1;
196
0
        FT_ATOMIC_STORE_CHAR_RELAXED(*(char*)addr, (char)long_val);
197
        /* XXX: For compatibility, only warn about truncations
198
           for now. */
199
0
        if ((long_val > CHAR_MAX) || (long_val < CHAR_MIN))
200
0
            WARN("Truncation of value to char");
201
0
        break;
202
0
        }
203
0
    case Py_T_UBYTE:{
204
0
        long long_val = PyLong_AsLong(v);
205
0
        if ((long_val == -1) && PyErr_Occurred())
206
0
            return -1;
207
0
        FT_ATOMIC_STORE_UCHAR_RELAXED(*(unsigned char*)addr, (unsigned char)long_val);
208
0
        if ((long_val > UCHAR_MAX) || (long_val < 0))
209
0
            WARN("Truncation of value to unsigned char");
210
0
        break;
211
0
        }
212
0
    case Py_T_SHORT:{
213
0
        long long_val = PyLong_AsLong(v);
214
0
        if ((long_val == -1) && PyErr_Occurred())
215
0
            return -1;
216
0
        FT_ATOMIC_STORE_SHORT_RELAXED(*(short*)addr, (short)long_val);
217
0
        if ((long_val > SHRT_MAX) || (long_val < SHRT_MIN))
218
0
            WARN("Truncation of value to short");
219
0
        break;
220
0
        }
221
0
    case Py_T_USHORT:{
222
0
        long long_val = PyLong_AsLong(v);
223
0
        if ((long_val == -1) && PyErr_Occurred())
224
0
            return -1;
225
0
        FT_ATOMIC_STORE_USHORT_RELAXED(*(unsigned short*)addr, (unsigned short)long_val);
226
0
        if ((long_val > USHRT_MAX) || (long_val < 0))
227
0
            WARN("Truncation of value to unsigned short");
228
0
        break;
229
0
        }
230
0
    case Py_T_INT:{
231
0
        long long_val = PyLong_AsLong(v);
232
0
        if ((long_val == -1) && PyErr_Occurred())
233
0
            return -1;
234
0
        FT_ATOMIC_STORE_INT_RELAXED(*(int *)addr, (int)long_val);
235
0
        if ((long_val > INT_MAX) || (long_val < INT_MIN))
236
0
            WARN("Truncation of value to int");
237
0
        break;
238
0
        }
239
0
    case Py_T_UINT: {
240
        /* XXX: For compatibility, accept negative int values
241
           as well. */
242
0
        v = _PyNumber_Index(v);
243
0
        if (v == NULL) {
244
0
            return -1;
245
0
        }
246
0
        if (_PyLong_IsNegative((PyLongObject *)v)) {
247
0
            long long_val = PyLong_AsLong(v);
248
0
            Py_DECREF(v);
249
0
            if (long_val == -1 && PyErr_Occurred()) {
250
0
                return -1;
251
0
            }
252
0
            FT_ATOMIC_STORE_UINT_RELAXED(*(unsigned int *)addr, (unsigned int)(unsigned long)long_val);
253
0
            WARN("Writing negative value into unsigned field");
254
0
        }
255
0
        else {
256
0
            unsigned long ulong_val = PyLong_AsUnsignedLong(v);
257
0
            Py_DECREF(v);
258
0
            if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) {
259
0
                return -1;
260
0
            }
261
0
            FT_ATOMIC_STORE_UINT_RELAXED(*(unsigned int *)addr, (unsigned int)ulong_val);
262
0
            if (ulong_val > UINT_MAX) {
263
0
                WARN("Truncation of value to unsigned int");
264
0
            }
265
0
        }
266
0
        break;
267
0
    }
268
0
    case Py_T_LONG: {
269
0
        const long long_val = PyLong_AsLong(v);
270
0
        if ((long_val == -1) && PyErr_Occurred())
271
0
            return -1;
272
0
        FT_ATOMIC_STORE_LONG_RELAXED(*(long*)addr, long_val);
273
0
        break;
274
0
    }
275
0
    case Py_T_ULONG: {
276
        /* XXX: For compatibility, accept negative int values
277
           as well. */
278
0
        v = _PyNumber_Index(v);
279
0
        if (v == NULL) {
280
0
            return -1;
281
0
        }
282
0
        if (_PyLong_IsNegative((PyLongObject *)v)) {
283
0
            long long_val = PyLong_AsLong(v);
284
0
            Py_DECREF(v);
285
0
            if (long_val == -1 && PyErr_Occurred()) {
286
0
                return -1;
287
0
            }
288
0
            FT_ATOMIC_STORE_ULONG_RELAXED(*(unsigned long *)addr, (unsigned long)long_val);
289
0
            WARN("Writing negative value into unsigned field");
290
0
        }
291
0
        else {
292
0
            unsigned long ulong_val = PyLong_AsUnsignedLong(v);
293
0
            Py_DECREF(v);
294
0
            if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) {
295
0
                return -1;
296
0
            }
297
0
            FT_ATOMIC_STORE_ULONG_RELAXED(*(unsigned long *)addr, ulong_val);
298
0
        }
299
0
        break;
300
0
    }
301
0
    case Py_T_PYSSIZET: {
302
0
        const Py_ssize_t ssize_val = PyLong_AsSsize_t(v);
303
0
        if ((ssize_val == (Py_ssize_t)-1) && PyErr_Occurred())
304
0
            return -1;
305
0
        FT_ATOMIC_STORE_SSIZE_RELAXED(*(Py_ssize_t*)addr, ssize_val);
306
0
        break;
307
0
    }
308
0
    case Py_T_FLOAT:{
309
0
        double double_val = PyFloat_AsDouble(v);
310
0
        if ((double_val == -1) && PyErr_Occurred())
311
0
            return -1;
312
0
        FT_ATOMIC_STORE_FLOAT_RELAXED(*(float*)addr, (float)double_val);
313
0
        break;
314
0
        }
315
0
    case Py_T_DOUBLE: {
316
0
        const double double_val = PyFloat_AsDouble(v);
317
0
        if ((double_val == -1) && PyErr_Occurred())
318
0
            return -1;
319
0
        FT_ATOMIC_STORE_DOUBLE_RELAXED(*(double *) addr, double_val);
320
0
        break;
321
0
    }
322
107k
    case _Py_T_OBJECT:
323
107k
    case Py_T_OBJECT_EX:
324
107k
        Py_BEGIN_CRITICAL_SECTION(obj);
325
107k
        oldv = *(PyObject **)addr;
326
107k
        FT_ATOMIC_STORE_PTR_RELEASE(*(PyObject **)addr, Py_XNewRef(v));
327
107k
        Py_END_CRITICAL_SECTION();
328
107k
        if (v == NULL && oldv == NULL && l->type == Py_T_OBJECT_EX) {
329
            // Raise an exception when attempting to delete an already deleted
330
            // attribute.
331
            // Differently from Py_T_OBJECT_EX, _Py_T_OBJECT does not raise an
332
            // exception here (PyMember_GetOne will return Py_None instead of
333
            // NULL).
334
0
            PyErr_SetString(PyExc_AttributeError, l->name);
335
0
            return -1;
336
0
        }
337
107k
        Py_XDECREF(oldv);
338
107k
        break;
339
0
    case Py_T_CHAR: {
340
0
        const char *string;
341
0
        Py_ssize_t len;
342
343
0
        string = PyUnicode_AsUTF8AndSize(v, &len);
344
0
        if (string == NULL || len != 1) {
345
0
            PyErr_BadArgument();
346
0
            return -1;
347
0
        }
348
0
        FT_ATOMIC_STORE_CHAR_RELAXED(*(char*)addr, string[0]);
349
0
        break;
350
0
        }
351
0
    case Py_T_STRING:
352
0
    case Py_T_STRING_INPLACE:
353
0
        PyErr_SetString(PyExc_TypeError, "readonly attribute");
354
0
        return -1;
355
0
    case Py_T_LONGLONG:{
356
0
        long long value = PyLong_AsLongLong(v);
357
0
        if ((value == -1) && PyErr_Occurred())
358
0
            return -1;
359
0
        FT_ATOMIC_STORE_LLONG_RELAXED(*(long long*)addr, value);
360
0
        break;
361
0
        }
362
0
    case Py_T_ULONGLONG: {
363
0
        v = _PyNumber_Index(v);
364
0
        if (v == NULL) {
365
0
            return -1;
366
0
        }
367
0
        if (_PyLong_IsNegative((PyLongObject *)v)) {
368
0
            long long_val = PyLong_AsLong(v);
369
0
            Py_DECREF(v);
370
0
            if (long_val == -1 && PyErr_Occurred()) {
371
0
                return -1;
372
0
            }
373
0
            FT_ATOMIC_STORE_ULLONG_RELAXED(*(unsigned long long *)addr, (unsigned long long)(long long)long_val);
374
0
            WARN("Writing negative value into unsigned field");
375
0
        }
376
0
        else {
377
0
            unsigned long long ulonglong_val = PyLong_AsUnsignedLongLong(v);
378
0
            Py_DECREF(v);
379
0
            if (ulonglong_val == (unsigned long long)-1 && PyErr_Occurred()) {
380
0
                return -1;
381
0
            }
382
0
            FT_ATOMIC_STORE_ULLONG_RELAXED(*(unsigned long long *)addr, ulonglong_val);
383
0
        }
384
0
        break;
385
0
    }
386
0
    default:
387
0
        PyErr_Format(PyExc_SystemError,
388
0
                     "bad memberdescr type for %s", l->name);
389
0
        return -1;
390
107k
    }
391
107k
    return 0;
392
107k
}