Coverage Report

Created: 2026-10-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Objects/stringlib/join.h
Line
Count
Source
1
/* stringlib: bytes joining implementation */
2
3
#if STRINGLIB_IS_UNICODE
4
#error join.h only compatible with byte-wise strings
5
#endif
6
7
Py_LOCAL_INLINE(PyObject *)
8
STRINGLIB(bytes_join_lock_held)(PyObject *sep, PyObject *seq)
9
47.8k
{
10
47.8k
    const char *sepstr = STRINGLIB_STR(sep);
11
47.8k
    Py_ssize_t seplen = STRINGLIB_LEN(sep);
12
47.8k
    PyObject *res = NULL;
13
47.8k
    char *p;
14
47.8k
    Py_ssize_t seqlen = 0;
15
47.8k
    Py_ssize_t sz = 0;
16
47.8k
    Py_ssize_t i, nbufs;
17
47.8k
    PyObject *item;
18
47.8k
    Py_buffer *buffers = NULL;
19
47.8k
#define NB_STATIC_BUFFERS 10
20
47.8k
    Py_buffer static_buffers[NB_STATIC_BUFFERS];
21
47.8k
#define GIL_THRESHOLD 1048576
22
47.8k
    int drop_gil = 1;
23
47.8k
    PyThreadState *save = NULL;
24
25
47.8k
    seqlen = PySequence_Fast_GET_SIZE(seq);
26
47.8k
    if (seqlen == 0) {
27
28
        return STRINGLIB_NEW(NULL, 0);
28
28
    }
29
#if !STRINGLIB_MUTABLE
30
47.8k
    if (seqlen == 1) {
31
12.6k
        item = PySequence_Fast_GET_ITEM(seq, 0);
32
12.6k
        if (STRINGLIB_CHECK_EXACT(item)) {
33
12.6k
            return Py_NewRef(item);
34
12.6k
        }
35
12.6k
    }
36
35.2k
#endif
37
35.2k
    if (seqlen > NB_STATIC_BUFFERS) {
38
9.82k
        buffers = PyMem_NEW(Py_buffer, seqlen);
39
9.82k
        if (buffers == NULL) {
40
0
            PyErr_NoMemory();
41
0
            return NULL;
42
0
        }
43
9.82k
    }
44
25.4k
    else {
45
25.4k
        buffers = static_buffers;
46
25.4k
    }
47
48
    /* Here is the general case.  Do a pre-pass to figure out the total
49
     * amount of space we'll need (sz), and see whether all arguments are
50
     * bytes-like.
51
     */
52
307k
    for (i = 0, nbufs = 0; i < seqlen; i++) {
53
271k
        Py_ssize_t itemlen;
54
271k
        item = PySequence_Fast_GET_ITEM(seq, i);
55
271k
        if (PyBytes_CheckExact(item)) {
56
            /* Fast path. */
57
269k
            buffers[i].obj = Py_NewRef(item);
58
269k
            buffers[i].buf = PyBytes_AS_STRING(item);
59
269k
            buffers[i].len = PyBytes_GET_SIZE(item);
60
269k
        }
61
2.07k
        else {
62
            /* item is only borrowed; its __buffer__() may run Python that
63
               drops the sequence's last reference to it. */
64
2.07k
            Py_INCREF(item);
65
2.07k
            if (PyObject_GetBuffer(item, &buffers[i], PyBUF_SIMPLE) != 0) {
66
0
                PyErr_Format(PyExc_TypeError,
67
0
                             "sequence item %zd: expected a bytes-like object, "
68
0
                             "%.80s found",
69
0
                             i, Py_TYPE(item)->tp_name);
70
0
                Py_DECREF(item);
71
0
                goto error;
72
0
            }
73
2.07k
            Py_DECREF(item);
74
            /* If the backing objects are mutable, then dropping the GIL
75
             * opens up race conditions where another thread tries to modify
76
             * the object which we hold a buffer on it. Such code has data
77
             * races anyway, but this is a conservative approach that avoids
78
             * changing the behaviour of that data race.
79
             */
80
2.07k
            drop_gil = 0;
81
2.07k
        }
82
271k
        nbufs = i + 1;  /* for error cleanup */
83
271k
        itemlen = buffers[i].len;
84
271k
        if (itemlen > PY_SSIZE_T_MAX - sz) {
85
0
            PyErr_SetString(PyExc_OverflowError,
86
0
                            "join() result is too long");
87
0
            goto error;
88
0
        }
89
271k
        sz += itemlen;
90
271k
        if (i != 0) {
91
236k
            if (seplen > PY_SSIZE_T_MAX - sz) {
92
0
                PyErr_SetString(PyExc_OverflowError,
93
0
                                "join() result is too long");
94
0
                goto error;
95
0
            }
96
236k
            sz += seplen;
97
236k
        }
98
271k
        if (seqlen != PySequence_Fast_GET_SIZE(seq)) {
99
0
            PyErr_SetString(PyExc_RuntimeError,
100
0
                            "sequence changed size during iteration");
101
0
            goto error;
102
0
        }
103
271k
    }
104
105
    /* Allocate result space. */
106
35.2k
    res = STRINGLIB_NEW(NULL, sz);
107
35.2k
    if (res == NULL)
108
0
        goto error;
109
110
    /* Catenate everything. */
111
35.2k
    p = STRINGLIB_STR(res);
112
35.2k
    if (sz < GIL_THRESHOLD) {
113
35.2k
        drop_gil = 0;   /* Benefits are likely outweighed by the overheads */
114
35.2k
    }
115
35.2k
    if (drop_gil) {
116
0
        save = PyEval_SaveThread();
117
0
    }
118
35.2k
    if (!seplen) {
119
        /* fast path */
120
307k
        for (i = 0; i < nbufs; i++) {
121
271k
            Py_ssize_t n = buffers[i].len;
122
271k
            char *q = buffers[i].buf;
123
271k
            memcpy(p, q, n);
124
271k
            p += n;
125
271k
        }
126
35.2k
    }
127
0
    else {
128
0
        Py_ssize_t n = buffers[0].len;
129
0
        char *q = buffers[0].buf;
130
0
        memcpy(p, q, n);
131
0
        p += n;
132
133
0
        for (i = 1; i < nbufs; i++) {
134
0
            memcpy(p, sepstr, seplen);
135
0
            p += seplen;
136
137
0
            n = buffers[i].len;
138
0
            q = buffers[i].buf;
139
0
            memcpy(p, q, n);
140
0
            p += n;
141
0
        }
142
0
    }
143
35.2k
    if (drop_gil) {
144
0
        PyEval_RestoreThread(save);
145
0
    }
146
35.2k
    goto done;
147
148
0
error:
149
0
    res = NULL;
150
35.2k
done:
151
307k
    for (i = 0; i < nbufs; i++)
152
271k
        PyBuffer_Release(&buffers[i]);
153
35.2k
    if (buffers != static_buffers)
154
9.82k
        PyMem_Free(buffers);
155
35.2k
    return res;
156
0
}
bytesobject.c:stringlib_bytes_join_lock_held
Line
Count
Source
9
47.8k
{
10
47.8k
    const char *sepstr = STRINGLIB_STR(sep);
11
47.8k
    Py_ssize_t seplen = STRINGLIB_LEN(sep);
12
47.8k
    PyObject *res = NULL;
13
47.8k
    char *p;
14
47.8k
    Py_ssize_t seqlen = 0;
15
47.8k
    Py_ssize_t sz = 0;
16
47.8k
    Py_ssize_t i, nbufs;
17
47.8k
    PyObject *item;
18
47.8k
    Py_buffer *buffers = NULL;
19
47.8k
#define NB_STATIC_BUFFERS 10
20
47.8k
    Py_buffer static_buffers[NB_STATIC_BUFFERS];
21
47.8k
#define GIL_THRESHOLD 1048576
22
47.8k
    int drop_gil = 1;
23
47.8k
    PyThreadState *save = NULL;
24
25
47.8k
    seqlen = PySequence_Fast_GET_SIZE(seq);
26
47.8k
    if (seqlen == 0) {
27
28
        return STRINGLIB_NEW(NULL, 0);
28
28
    }
29
47.8k
#if !STRINGLIB_MUTABLE
30
47.8k
    if (seqlen == 1) {
31
12.6k
        item = PySequence_Fast_GET_ITEM(seq, 0);
32
12.6k
        if (STRINGLIB_CHECK_EXACT(item)) {
33
12.6k
            return Py_NewRef(item);
34
12.6k
        }
35
12.6k
    }
36
35.2k
#endif
37
35.2k
    if (seqlen > NB_STATIC_BUFFERS) {
38
9.82k
        buffers = PyMem_NEW(Py_buffer, seqlen);
39
9.82k
        if (buffers == NULL) {
40
0
            PyErr_NoMemory();
41
0
            return NULL;
42
0
        }
43
9.82k
    }
44
25.4k
    else {
45
25.4k
        buffers = static_buffers;
46
25.4k
    }
47
48
    /* Here is the general case.  Do a pre-pass to figure out the total
49
     * amount of space we'll need (sz), and see whether all arguments are
50
     * bytes-like.
51
     */
52
307k
    for (i = 0, nbufs = 0; i < seqlen; i++) {
53
271k
        Py_ssize_t itemlen;
54
271k
        item = PySequence_Fast_GET_ITEM(seq, i);
55
271k
        if (PyBytes_CheckExact(item)) {
56
            /* Fast path. */
57
269k
            buffers[i].obj = Py_NewRef(item);
58
269k
            buffers[i].buf = PyBytes_AS_STRING(item);
59
269k
            buffers[i].len = PyBytes_GET_SIZE(item);
60
269k
        }
61
2.07k
        else {
62
            /* item is only borrowed; its __buffer__() may run Python that
63
               drops the sequence's last reference to it. */
64
2.07k
            Py_INCREF(item);
65
2.07k
            if (PyObject_GetBuffer(item, &buffers[i], PyBUF_SIMPLE) != 0) {
66
0
                PyErr_Format(PyExc_TypeError,
67
0
                             "sequence item %zd: expected a bytes-like object, "
68
0
                             "%.80s found",
69
0
                             i, Py_TYPE(item)->tp_name);
70
0
                Py_DECREF(item);
71
0
                goto error;
72
0
            }
73
2.07k
            Py_DECREF(item);
74
            /* If the backing objects are mutable, then dropping the GIL
75
             * opens up race conditions where another thread tries to modify
76
             * the object which we hold a buffer on it. Such code has data
77
             * races anyway, but this is a conservative approach that avoids
78
             * changing the behaviour of that data race.
79
             */
80
2.07k
            drop_gil = 0;
81
2.07k
        }
82
271k
        nbufs = i + 1;  /* for error cleanup */
83
271k
        itemlen = buffers[i].len;
84
271k
        if (itemlen > PY_SSIZE_T_MAX - sz) {
85
0
            PyErr_SetString(PyExc_OverflowError,
86
0
                            "join() result is too long");
87
0
            goto error;
88
0
        }
89
271k
        sz += itemlen;
90
271k
        if (i != 0) {
91
236k
            if (seplen > PY_SSIZE_T_MAX - sz) {
92
0
                PyErr_SetString(PyExc_OverflowError,
93
0
                                "join() result is too long");
94
0
                goto error;
95
0
            }
96
236k
            sz += seplen;
97
236k
        }
98
271k
        if (seqlen != PySequence_Fast_GET_SIZE(seq)) {
99
0
            PyErr_SetString(PyExc_RuntimeError,
100
0
                            "sequence changed size during iteration");
101
0
            goto error;
102
0
        }
103
271k
    }
104
105
    /* Allocate result space. */
106
35.2k
    res = STRINGLIB_NEW(NULL, sz);
107
35.2k
    if (res == NULL)
108
0
        goto error;
109
110
    /* Catenate everything. */
111
35.2k
    p = STRINGLIB_STR(res);
112
35.2k
    if (sz < GIL_THRESHOLD) {
113
35.2k
        drop_gil = 0;   /* Benefits are likely outweighed by the overheads */
114
35.2k
    }
115
35.2k
    if (drop_gil) {
116
0
        save = PyEval_SaveThread();
117
0
    }
118
35.2k
    if (!seplen) {
119
        /* fast path */
120
307k
        for (i = 0; i < nbufs; i++) {
121
271k
            Py_ssize_t n = buffers[i].len;
122
271k
            char *q = buffers[i].buf;
123
271k
            memcpy(p, q, n);
124
271k
            p += n;
125
271k
        }
126
35.2k
    }
127
0
    else {
128
0
        Py_ssize_t n = buffers[0].len;
129
0
        char *q = buffers[0].buf;
130
0
        memcpy(p, q, n);
131
0
        p += n;
132
133
0
        for (i = 1; i < nbufs; i++) {
134
0
            memcpy(p, sepstr, seplen);
135
0
            p += seplen;
136
137
0
            n = buffers[i].len;
138
0
            q = buffers[i].buf;
139
0
            memcpy(p, q, n);
140
0
            p += n;
141
0
        }
142
0
    }
143
35.2k
    if (drop_gil) {
144
0
        PyEval_RestoreThread(save);
145
0
    }
146
35.2k
    goto done;
147
148
0
error:
149
0
    res = NULL;
150
35.2k
done:
151
307k
    for (i = 0; i < nbufs; i++)
152
271k
        PyBuffer_Release(&buffers[i]);
153
35.2k
    if (buffers != static_buffers)
154
9.82k
        PyMem_Free(buffers);
155
35.2k
    return res;
156
0
}
Unexecuted instantiation: bytearrayobject.c:stringlib_bytes_join_lock_held
157
158
Py_LOCAL_INLINE(PyObject *)
159
STRINGLIB(bytes_join)(PyObject *sep, PyObject *iterable)
160
47.8k
{
161
47.8k
    PyObject *seq, *res;
162
163
47.8k
    seq = PySequence_Fast(iterable, "can only join an iterable");
164
47.8k
    if (seq == NULL) {
165
0
        return NULL;
166
0
    }
167
168
47.8k
    Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(iterable);
169
47.8k
    res = STRINGLIB(bytes_join_lock_held)(sep, seq);
170
47.8k
    Py_END_CRITICAL_SECTION_SEQUENCE_FAST();
171
172
47.8k
    Py_DECREF(seq);
173
47.8k
    return res;
174
47.8k
}
bytesobject.c:stringlib_bytes_join
Line
Count
Source
160
47.8k
{
161
47.8k
    PyObject *seq, *res;
162
163
47.8k
    seq = PySequence_Fast(iterable, "can only join an iterable");
164
47.8k
    if (seq == NULL) {
165
0
        return NULL;
166
0
    }
167
168
47.8k
    Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(iterable);
169
47.8k
    res = STRINGLIB(bytes_join_lock_held)(sep, seq);
170
47.8k
    Py_END_CRITICAL_SECTION_SEQUENCE_FAST();
171
172
47.8k
    Py_DECREF(seq);
173
47.8k
    return res;
174
47.8k
}
Unexecuted instantiation: bytearrayobject.c:stringlib_bytes_join
175
176
#undef NB_STATIC_BUFFERS
177
#undef GIL_THRESHOLD