Coverage Report

Created: 2026-10-17 06:15

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
172k
{
10
172k
    const char *sepstr = STRINGLIB_STR(sep);
11
172k
    Py_ssize_t seplen = STRINGLIB_LEN(sep);
12
172k
    PyObject *res = NULL;
13
172k
    char *p;
14
172k
    Py_ssize_t seqlen = 0;
15
172k
    Py_ssize_t sz = 0;
16
172k
    Py_ssize_t i, nbufs;
17
172k
    PyObject *item;
18
172k
    Py_buffer *buffers = NULL;
19
172k
#define NB_STATIC_BUFFERS 10
20
172k
    Py_buffer static_buffers[NB_STATIC_BUFFERS];
21
172k
#define GIL_THRESHOLD 1048576
22
172k
    int drop_gil = 1;
23
172k
    PyThreadState *save = NULL;
24
25
172k
    seqlen = PySequence_Fast_GET_SIZE(seq);
26
172k
    if (seqlen == 0) {
27
31
        return STRINGLIB_NEW(NULL, 0);
28
31
    }
29
#if !STRINGLIB_MUTABLE
30
172k
    if (seqlen == 1) {
31
45.1k
        item = PySequence_Fast_GET_ITEM(seq, 0);
32
45.1k
        if (STRINGLIB_CHECK_EXACT(item)) {
33
45.1k
            return Py_NewRef(item);
34
45.1k
        }
35
45.1k
    }
36
126k
#endif
37
126k
    if (seqlen > NB_STATIC_BUFFERS) {
38
6.87k
        buffers = PyMem_NEW(Py_buffer, seqlen);
39
6.87k
        if (buffers == NULL) {
40
0
            PyErr_NoMemory();
41
0
            return NULL;
42
0
        }
43
6.87k
    }
44
120k
    else {
45
120k
        buffers = static_buffers;
46
120k
    }
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
538k
    for (i = 0, nbufs = 0; i < seqlen; i++) {
53
411k
        Py_ssize_t itemlen;
54
411k
        item = PySequence_Fast_GET_ITEM(seq, i);
55
411k
        if (PyBytes_CheckExact(item)) {
56
            /* Fast path. */
57
410k
            buffers[i].obj = Py_NewRef(item);
58
410k
            buffers[i].buf = PyBytes_AS_STRING(item);
59
410k
            buffers[i].len = PyBytes_GET_SIZE(item);
60
410k
        }
61
1.27k
        else {
62
            /* item is only borrowed; its __buffer__() may run Python that
63
               drops the sequence's last reference to it. */
64
1.27k
            Py_INCREF(item);
65
1.27k
            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
1.27k
            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
1.27k
            drop_gil = 0;
81
1.27k
        }
82
411k
        nbufs = i + 1;  /* for error cleanup */
83
411k
        itemlen = buffers[i].len;
84
411k
        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
411k
        sz += itemlen;
90
411k
        if (i != 0) {
91
284k
            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
284k
            sz += seplen;
97
284k
        }
98
411k
        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
411k
    }
104
105
    /* Allocate result space. */
106
126k
    res = STRINGLIB_NEW(NULL, sz);
107
126k
    if (res == NULL)
108
0
        goto error;
109
110
    /* Catenate everything. */
111
126k
    p = STRINGLIB_STR(res);
112
126k
    if (sz < GIL_THRESHOLD) {
113
126k
        drop_gil = 0;   /* Benefits are likely outweighed by the overheads */
114
126k
    }
115
126k
    if (drop_gil) {
116
0
        save = PyEval_SaveThread();
117
0
    }
118
126k
    if (!seplen) {
119
        /* fast path */
120
538k
        for (i = 0; i < nbufs; i++) {
121
411k
            Py_ssize_t n = buffers[i].len;
122
411k
            char *q = buffers[i].buf;
123
411k
            memcpy(p, q, n);
124
411k
            p += n;
125
411k
        }
126
126k
    }
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
126k
    if (drop_gil) {
144
0
        PyEval_RestoreThread(save);
145
0
    }
146
126k
    goto done;
147
148
0
error:
149
0
    res = NULL;
150
126k
done:
151
538k
    for (i = 0; i < nbufs; i++)
152
411k
        PyBuffer_Release(&buffers[i]);
153
126k
    if (buffers != static_buffers)
154
6.87k
        PyMem_Free(buffers);
155
126k
    return res;
156
0
}
bytesobject.c:stringlib_bytes_join_lock_held
Line
Count
Source
9
172k
{
10
172k
    const char *sepstr = STRINGLIB_STR(sep);
11
172k
    Py_ssize_t seplen = STRINGLIB_LEN(sep);
12
172k
    PyObject *res = NULL;
13
172k
    char *p;
14
172k
    Py_ssize_t seqlen = 0;
15
172k
    Py_ssize_t sz = 0;
16
172k
    Py_ssize_t i, nbufs;
17
172k
    PyObject *item;
18
172k
    Py_buffer *buffers = NULL;
19
172k
#define NB_STATIC_BUFFERS 10
20
172k
    Py_buffer static_buffers[NB_STATIC_BUFFERS];
21
172k
#define GIL_THRESHOLD 1048576
22
172k
    int drop_gil = 1;
23
172k
    PyThreadState *save = NULL;
24
25
172k
    seqlen = PySequence_Fast_GET_SIZE(seq);
26
172k
    if (seqlen == 0) {
27
31
        return STRINGLIB_NEW(NULL, 0);
28
31
    }
29
172k
#if !STRINGLIB_MUTABLE
30
172k
    if (seqlen == 1) {
31
45.1k
        item = PySequence_Fast_GET_ITEM(seq, 0);
32
45.1k
        if (STRINGLIB_CHECK_EXACT(item)) {
33
45.1k
            return Py_NewRef(item);
34
45.1k
        }
35
45.1k
    }
36
126k
#endif
37
126k
    if (seqlen > NB_STATIC_BUFFERS) {
38
6.87k
        buffers = PyMem_NEW(Py_buffer, seqlen);
39
6.87k
        if (buffers == NULL) {
40
0
            PyErr_NoMemory();
41
0
            return NULL;
42
0
        }
43
6.87k
    }
44
120k
    else {
45
120k
        buffers = static_buffers;
46
120k
    }
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
538k
    for (i = 0, nbufs = 0; i < seqlen; i++) {
53
411k
        Py_ssize_t itemlen;
54
411k
        item = PySequence_Fast_GET_ITEM(seq, i);
55
411k
        if (PyBytes_CheckExact(item)) {
56
            /* Fast path. */
57
410k
            buffers[i].obj = Py_NewRef(item);
58
410k
            buffers[i].buf = PyBytes_AS_STRING(item);
59
410k
            buffers[i].len = PyBytes_GET_SIZE(item);
60
410k
        }
61
1.27k
        else {
62
            /* item is only borrowed; its __buffer__() may run Python that
63
               drops the sequence's last reference to it. */
64
1.27k
            Py_INCREF(item);
65
1.27k
            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
1.27k
            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
1.27k
            drop_gil = 0;
81
1.27k
        }
82
411k
        nbufs = i + 1;  /* for error cleanup */
83
411k
        itemlen = buffers[i].len;
84
411k
        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
411k
        sz += itemlen;
90
411k
        if (i != 0) {
91
284k
            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
284k
            sz += seplen;
97
284k
        }
98
411k
        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
411k
    }
104
105
    /* Allocate result space. */
106
126k
    res = STRINGLIB_NEW(NULL, sz);
107
126k
    if (res == NULL)
108
0
        goto error;
109
110
    /* Catenate everything. */
111
126k
    p = STRINGLIB_STR(res);
112
126k
    if (sz < GIL_THRESHOLD) {
113
126k
        drop_gil = 0;   /* Benefits are likely outweighed by the overheads */
114
126k
    }
115
126k
    if (drop_gil) {
116
0
        save = PyEval_SaveThread();
117
0
    }
118
126k
    if (!seplen) {
119
        /* fast path */
120
538k
        for (i = 0; i < nbufs; i++) {
121
411k
            Py_ssize_t n = buffers[i].len;
122
411k
            char *q = buffers[i].buf;
123
411k
            memcpy(p, q, n);
124
411k
            p += n;
125
411k
        }
126
126k
    }
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
126k
    if (drop_gil) {
144
0
        PyEval_RestoreThread(save);
145
0
    }
146
126k
    goto done;
147
148
0
error:
149
0
    res = NULL;
150
126k
done:
151
538k
    for (i = 0; i < nbufs; i++)
152
411k
        PyBuffer_Release(&buffers[i]);
153
126k
    if (buffers != static_buffers)
154
6.87k
        PyMem_Free(buffers);
155
126k
    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
172k
{
161
172k
    PyObject *seq, *res;
162
163
172k
    seq = PySequence_Fast(iterable, "can only join an iterable");
164
172k
    if (seq == NULL) {
165
0
        return NULL;
166
0
    }
167
168
172k
    Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(iterable);
169
172k
    res = STRINGLIB(bytes_join_lock_held)(sep, seq);
170
172k
    Py_END_CRITICAL_SECTION_SEQUENCE_FAST();
171
172
172k
    Py_DECREF(seq);
173
172k
    return res;
174
172k
}
bytesobject.c:stringlib_bytes_join
Line
Count
Source
160
172k
{
161
172k
    PyObject *seq, *res;
162
163
172k
    seq = PySequence_Fast(iterable, "can only join an iterable");
164
172k
    if (seq == NULL) {
165
0
        return NULL;
166
0
    }
167
168
172k
    Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(iterable);
169
172k
    res = STRINGLIB(bytes_join_lock_held)(sep, seq);
170
172k
    Py_END_CRITICAL_SECTION_SEQUENCE_FAST();
171
172
172k
    Py_DECREF(seq);
173
172k
    return res;
174
172k
}
Unexecuted instantiation: bytearrayobject.c:stringlib_bytes_join
175
176
#undef NB_STATIC_BUFFERS
177
#undef GIL_THRESHOLD