LCOV - code coverage report
Current view: | top level - Objects/stringlib - join.h (source / functions) |  |  | Hit | Total | Coverage
Test: | CPython 3.12 LCOV report [commit 5e6661bce9] |  | Lines: | 19 | 88 | 21.6 %
Date: | 2023-03-20 08:15:36 |  | Functions: | 1 | 1 | 100.0 %
|  |  | Branches: | 6 | 56 | 10.7 %

               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

Generated by: LCOV version 1.14