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