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