LCOV - code coverage report
Current view: top level - Objects/stringlib - join.h (source / functions) Hit Total Coverage
Test: CPython 3.12 LCOV report [commit acb105a7c1f] Lines: 79 88 89.8 %
Date: 2022-07-20 13:12:14 Functions: 1 1 100.0 %
Branches: 50 56 89.3 %

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

Generated by: LCOV version 1.14