mirror of
https://github.com/python/cpython.git
synced 2026-10-28 18:35:21 +08:00
In the free-threaded build, bytes.join() and bytearray.join() read items from the list with borrowed references and without holding its lock, so another thread could replace and free an item before it was increfed. Run the join under Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST, as PyUnicode_Join() already does.
178 lines
5.0 KiB
C
178 lines
5.0 KiB
C
/* stringlib: bytes joining implementation */
|
|
|
|
#if STRINGLIB_IS_UNICODE
|
|
#error join.h only compatible with byte-wise strings
|
|
#endif
|
|
|
|
Py_LOCAL_INLINE(PyObject *)
|
|
STRINGLIB(bytes_join_lock_held)(PyObject *sep, PyObject *seq)
|
|
{
|
|
const char *sepstr = STRINGLIB_STR(sep);
|
|
Py_ssize_t seplen = STRINGLIB_LEN(sep);
|
|
PyObject *res = NULL;
|
|
char *p;
|
|
Py_ssize_t seqlen = 0;
|
|
Py_ssize_t sz = 0;
|
|
Py_ssize_t i, nbufs;
|
|
PyObject *item;
|
|
Py_buffer *buffers = NULL;
|
|
#define NB_STATIC_BUFFERS 10
|
|
Py_buffer static_buffers[NB_STATIC_BUFFERS];
|
|
#define GIL_THRESHOLD 1048576
|
|
int drop_gil = 1;
|
|
PyThreadState *save = NULL;
|
|
|
|
seqlen = PySequence_Fast_GET_SIZE(seq);
|
|
if (seqlen == 0) {
|
|
return STRINGLIB_NEW(NULL, 0);
|
|
}
|
|
#if !STRINGLIB_MUTABLE
|
|
if (seqlen == 1) {
|
|
item = PySequence_Fast_GET_ITEM(seq, 0);
|
|
if (STRINGLIB_CHECK_EXACT(item)) {
|
|
return Py_NewRef(item);
|
|
}
|
|
}
|
|
#endif
|
|
if (seqlen > NB_STATIC_BUFFERS) {
|
|
buffers = PyMem_NEW(Py_buffer, seqlen);
|
|
if (buffers == NULL) {
|
|
PyErr_NoMemory();
|
|
return NULL;
|
|
}
|
|
}
|
|
else {
|
|
buffers = static_buffers;
|
|
}
|
|
|
|
/* Here is the general case. Do a pre-pass to figure out the total
|
|
* amount of space we'll need (sz), and see whether all arguments are
|
|
* bytes-like.
|
|
*/
|
|
for (i = 0, nbufs = 0; i < seqlen; i++) {
|
|
Py_ssize_t itemlen;
|
|
item = PySequence_Fast_GET_ITEM(seq, i);
|
|
if (PyBytes_CheckExact(item)) {
|
|
/* Fast path. */
|
|
buffers[i].obj = Py_NewRef(item);
|
|
buffers[i].buf = PyBytes_AS_STRING(item);
|
|
buffers[i].len = PyBytes_GET_SIZE(item);
|
|
}
|
|
else {
|
|
/* item is only borrowed; its __buffer__() may run Python that
|
|
drops the sequence's last reference to it. */
|
|
Py_INCREF(item);
|
|
if (PyObject_GetBuffer(item, &buffers[i], PyBUF_SIMPLE) != 0) {
|
|
PyErr_Format(PyExc_TypeError,
|
|
"sequence item %zd: expected a bytes-like object, "
|
|
"%.80s found",
|
|
i, Py_TYPE(item)->tp_name);
|
|
Py_DECREF(item);
|
|
goto error;
|
|
}
|
|
Py_DECREF(item);
|
|
/* If the backing objects are mutable, then dropping the GIL
|
|
* opens up race conditions where another thread tries to modify
|
|
* the object which we hold a buffer on it. Such code has data
|
|
* races anyway, but this is a conservative approach that avoids
|
|
* changing the behaviour of that data race.
|
|
*/
|
|
drop_gil = 0;
|
|
}
|
|
nbufs = i + 1; /* for error cleanup */
|
|
itemlen = buffers[i].len;
|
|
if (itemlen > PY_SSIZE_T_MAX - sz) {
|
|
PyErr_SetString(PyExc_OverflowError,
|
|
"join() result is too long");
|
|
goto error;
|
|
}
|
|
sz += itemlen;
|
|
if (i != 0) {
|
|
if (seplen > PY_SSIZE_T_MAX - sz) {
|
|
PyErr_SetString(PyExc_OverflowError,
|
|
"join() result is too long");
|
|
goto error;
|
|
}
|
|
sz += seplen;
|
|
}
|
|
if (seqlen != PySequence_Fast_GET_SIZE(seq)) {
|
|
PyErr_SetString(PyExc_RuntimeError,
|
|
"sequence changed size during iteration");
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* Allocate result space. */
|
|
res = STRINGLIB_NEW(NULL, sz);
|
|
if (res == NULL)
|
|
goto error;
|
|
|
|
/* Catenate everything. */
|
|
p = STRINGLIB_STR(res);
|
|
if (sz < GIL_THRESHOLD) {
|
|
drop_gil = 0; /* Benefits are likely outweighed by the overheads */
|
|
}
|
|
if (drop_gil) {
|
|
save = PyEval_SaveThread();
|
|
}
|
|
if (!seplen) {
|
|
/* fast path */
|
|
for (i = 0; i < nbufs; i++) {
|
|
Py_ssize_t n = buffers[i].len;
|
|
char *q = buffers[i].buf;
|
|
memcpy(p, q, n);
|
|
p += n;
|
|
}
|
|
}
|
|
else {
|
|
Py_ssize_t n = buffers[0].len;
|
|
char *q = buffers[0].buf;
|
|
memcpy(p, q, n);
|
|
p += n;
|
|
|
|
for (i = 1; i < nbufs; i++) {
|
|
memcpy(p, sepstr, seplen);
|
|
p += seplen;
|
|
|
|
n = buffers[i].len;
|
|
q = buffers[i].buf;
|
|
memcpy(p, q, n);
|
|
p += n;
|
|
}
|
|
}
|
|
if (drop_gil) {
|
|
PyEval_RestoreThread(save);
|
|
}
|
|
goto done;
|
|
|
|
error:
|
|
res = NULL;
|
|
done:
|
|
for (i = 0; i < nbufs; i++)
|
|
PyBuffer_Release(&buffers[i]);
|
|
if (buffers != static_buffers)
|
|
PyMem_Free(buffers);
|
|
return res;
|
|
}
|
|
|
|
Py_LOCAL_INLINE(PyObject *)
|
|
STRINGLIB(bytes_join)(PyObject *sep, PyObject *iterable)
|
|
{
|
|
PyObject *seq, *res;
|
|
|
|
seq = PySequence_Fast(iterable, "can only join an iterable");
|
|
if (seq == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(iterable);
|
|
res = STRINGLIB(bytes_join_lock_held)(sep, seq);
|
|
Py_END_CRITICAL_SECTION_SEQUENCE_FAST();
|
|
|
|
Py_DECREF(seq);
|
|
return res;
|
|
}
|
|
|
|
#undef NB_STATIC_BUFFERS
|
|
#undef GIL_THRESHOLD
|