LCOV - code coverage report
Current view: | top level - Include/internal - pycore_blocks_output_buffer.h (source / functions) |  |  | Hit | Total | Coverage
Test: | CPython 3.12 LCOV report [commit 5e6661bce9] |  | Lines: | 0 | 84 | 0.0 %
Date: | 2023-03-20 08:15:36 |  | Functions: | 0 | 6 | 0.0 %
|  |  | Branches: | 0 | 46 | 0.0 %

               Branch data     Line data    Source code
           1                 :            : /*
           2                 :            :    _BlocksOutputBuffer is used to maintain an output buffer
           3                 :            :    that has unpredictable size. Suitable for compression/decompression
           4                 :            :    API (bz2/lzma/zlib) that has stream->next_out and stream->avail_out:
           5                 :            :
           6                 :            :         stream->next_out:  point to the next output position.
           7                 :            :         stream->avail_out: the number of available bytes left in the buffer.
           8                 :            :
           9                 :            :    It maintains a list of bytes object, so there is no overhead of resizing
          10                 :            :    the buffer.
          11                 :            :
          12                 :            :    Usage:
          13                 :            :
          14                 :            :    1, Initialize the struct instance like this:
          15                 :            :         _BlocksOutputBuffer buffer = {.list = NULL};
          16                 :            :       Set .list to NULL for _BlocksOutputBuffer_OnError()
          17                 :            :
          18                 :            :    2, Initialize the buffer use one of these functions:
          19                 :            :         _BlocksOutputBuffer_InitAndGrow()
          20                 :            :         _BlocksOutputBuffer_InitWithSize()
          21                 :            :
          22                 :            :    3, If (avail_out == 0), grow the buffer:
          23                 :            :         _BlocksOutputBuffer_Grow()
          24                 :            :
          25                 :            :    4, Get the current outputted data size:
          26                 :            :         _BlocksOutputBuffer_GetDataSize()
          27                 :            :
          28                 :            :    5, Finish the buffer, and return a bytes object:
          29                 :            :         _BlocksOutputBuffer_Finish()
          30                 :            :
          31                 :            :    6, Clean up the buffer when an error occurred:
          32                 :            :         _BlocksOutputBuffer_OnError()
          33                 :            : */
          34                 :            :
          35                 :            : #ifndef Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H
          36                 :            : #define Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H
          37                 :            : #ifdef __cplusplus
          38                 :            : extern "C" {
          39                 :            : #endif
          40                 :            :
          41                 :            : #include "Python.h"
          42                 :            :
          43                 :            : typedef struct {
          44                 :            :     // List of bytes objects
          45                 :            :     PyObject *list;
          46                 :            :     // Number of whole allocated size
          47                 :            :     Py_ssize_t allocated;
          48                 :            :     // Max length of the buffer, negative number means unlimited length.
          49                 :            :     Py_ssize_t max_length;
          50                 :            : } _BlocksOutputBuffer;
          51                 :            :
          52                 :            : static const char unable_allocate_msg[] = "Unable to allocate output buffer.";
          53                 :            :
          54                 :            : /* In 32-bit build, the max block size should <= INT32_MAX. */
          55                 :            : #define OUTPUT_BUFFER_MAX_BLOCK_SIZE (256*1024*1024)
          56                 :            :
          57                 :            : /* Block size sequence */
          58                 :            : #define KB (1024)
          59                 :            : #define MB (1024*1024)
          60                 :            : static const Py_ssize_t BUFFER_BLOCK_SIZE[] =
          61                 :            :     { 32*KB, 64*KB, 256*KB, 1*MB, 4*MB, 8*MB, 16*MB, 16*MB,
          62                 :            :       32*MB, 32*MB, 32*MB, 32*MB, 64*MB, 64*MB, 128*MB, 128*MB,
          63                 :            :       OUTPUT_BUFFER_MAX_BLOCK_SIZE };
          64                 :            : #undef KB
          65                 :            : #undef MB
          66                 :            :
          67                 :            : /* According to the block sizes defined by BUFFER_BLOCK_SIZE, the whole
          68                 :            :    allocated size growth step is:
          69                 :            :     1   32 KB       +32 KB
          70                 :            :     2   96 KB       +64 KB
          71                 :            :     3   352 KB      +256 KB
          72                 :            :     4   1.34 MB     +1 MB
          73                 :            :     5   5.34 MB     +4 MB
          74                 :            :     6   13.34 MB    +8 MB
          75                 :            :     7   29.34 MB    +16 MB
          76                 :            :     8   45.34 MB    +16 MB
          77                 :            :     9   77.34 MB    +32 MB
          78                 :            :     10  109.34 MB   +32 MB
          79                 :            :     11  141.34 MB   +32 MB
          80                 :            :     12  173.34 MB   +32 MB
          81                 :            :     13  237.34 MB   +64 MB
          82                 :            :     14  301.34 MB   +64 MB
          83                 :            :     15  429.34 MB   +128 MB
          84                 :            :     16  557.34 MB   +128 MB
          85                 :            :     17  813.34 MB   +256 MB
          86                 :            :     18  1069.34 MB  +256 MB
          87                 :            :     19  1325.34 MB  +256 MB
          88                 :            :     20  1581.34 MB  +256 MB
          89                 :            :     21  1837.34 MB  +256 MB
          90                 :            :     22  2093.34 MB  +256 MB
          91                 :            :     ...
          92                 :            : */
          93                 :            :
          94                 :            : /* Initialize the buffer, and grow the buffer.
          95                 :            :
          96                 :            :    max_length: Max length of the buffer, -1 for unlimited length.
          97                 :            :
          98                 :            :    On success, return allocated size (>=0)
          99                 :            :    On failure, return -1
         100                 :            : */
         101                 :            : static inline Py_ssize_t
         102                 :          0 : _BlocksOutputBuffer_InitAndGrow(_BlocksOutputBuffer *buffer,
         103                 :            :                                 const Py_ssize_t max_length,
         104                 :            :                                 void **next_out)
         105                 :            : {
         106                 :            :     PyObject *b;
         107                 :            :     Py_ssize_t block_size;
         108                 :            :
         109                 :            :     // ensure .list was set to NULL
         110                 :            :     assert(buffer->list == NULL);
         111                 :            :
         112                 :            :     // get block size
         113   [ #  #  #  # ]:          0 :     if (0 <= max_length && max_length < BUFFER_BLOCK_SIZE[0]) {
         114                 :          0 :         block_size = max_length;
         115                 :            :     } else {
         116                 :          0 :         block_size = BUFFER_BLOCK_SIZE[0];
         117                 :            :     }
         118                 :            :
         119                 :            :     // the first block
         120                 :          0 :     b = PyBytes_FromStringAndSize(NULL, block_size);
         121         [ #  # ]:          0 :     if (b == NULL) {
         122                 :          0 :         return -1;
         123                 :            :     }
         124                 :            :
         125                 :            :     // create the list
         126                 :          0 :     buffer->list = PyList_New(1);
         127         [ #  # ]:          0 :     if (buffer->list == NULL) {
         128                 :          0 :         Py_DECREF(b);
         129                 :          0 :         return -1;
         130                 :            :     }
         131                 :          0 :     PyList_SET_ITEM(buffer->list, 0, b);
         132                 :            :
         133                 :            :     // set variables
         134                 :          0 :     buffer->allocated = block_size;
         135                 :          0 :     buffer->max_length = max_length;
         136                 :            :
         137                 :          0 :     *next_out = PyBytes_AS_STRING(b);
         138                 :          0 :     return block_size;
         139                 :            : }
         140                 :            :
         141                 :            : /* Initialize the buffer, with an initial size.
         142                 :            :
         143                 :            :    Check block size limit in the outer wrapper function. For example, some libs
         144                 :            :    accept UINT32_MAX as the maximum block size, then init_size should <= it.
         145                 :            :
         146                 :            :    On success, return allocated size (>=0)
         147                 :            :    On failure, return -1
         148                 :            : */
         149                 :            : static inline Py_ssize_t
         150                 :          0 : _BlocksOutputBuffer_InitWithSize(_BlocksOutputBuffer *buffer,
         151                 :            :                                  const Py_ssize_t init_size,
         152                 :            :                                  void **next_out)
         153                 :            : {
         154                 :            :     PyObject *b;
         155                 :            :
         156                 :            :     // ensure .list was set to NULL
         157                 :            :     assert(buffer->list == NULL);
         158                 :            :
         159                 :            :     // the first block
         160                 :          0 :     b = PyBytes_FromStringAndSize(NULL, init_size);
         161         [ #  # ]:          0 :     if (b == NULL) {
         162                 :          0 :         PyErr_SetString(PyExc_MemoryError, unable_allocate_msg);
         163                 :          0 :         return -1;
         164                 :            :     }
         165                 :            :
         166                 :            :     // create the list
         167                 :          0 :     buffer->list = PyList_New(1);
         168         [ #  # ]:          0 :     if (buffer->list == NULL) {
         169                 :          0 :         Py_DECREF(b);
         170                 :          0 :         return -1;
         171                 :            :     }
         172                 :          0 :     PyList_SET_ITEM(buffer->list, 0, b);
         173                 :            :
         174                 :            :     // set variables
         175                 :          0 :     buffer->allocated = init_size;
         176                 :          0 :     buffer->max_length = -1;
         177                 :            :
         178                 :          0 :     *next_out = PyBytes_AS_STRING(b);
         179                 :          0 :     return init_size;
         180                 :            : }
         181                 :            :
         182                 :            : /* Grow the buffer. The avail_out must be 0, please check it before calling.
         183                 :            :
         184                 :            :    On success, return allocated size (>=0)
         185                 :            :    On failure, return -1
         186                 :            : */
         187                 :            : static inline Py_ssize_t
         188                 :          0 : _BlocksOutputBuffer_Grow(_BlocksOutputBuffer *buffer,
         189                 :            :                          void **next_out,
         190                 :            :                          const Py_ssize_t avail_out)
         191                 :            : {
         192                 :            :     PyObject *b;
         193                 :          0 :     const Py_ssize_t list_len = Py_SIZE(buffer->list);
         194                 :            :     Py_ssize_t block_size;
         195                 :            :
         196                 :            :     // ensure no gaps in the data
         197         [ #  # ]:          0 :     if (avail_out != 0) {
         198                 :          0 :         PyErr_SetString(PyExc_SystemError,
         199                 :            :                         "avail_out is non-zero in _BlocksOutputBuffer_Grow().");
         200                 :          0 :         return -1;
         201                 :            :     }
         202                 :            :
         203                 :            :     // get block size
         204         [ #  # ]:          0 :     if (list_len < (Py_ssize_t) Py_ARRAY_LENGTH(BUFFER_BLOCK_SIZE)) {
         205                 :          0 :         block_size = BUFFER_BLOCK_SIZE[list_len];
         206                 :            :     } else {
         207                 :          0 :         block_size = BUFFER_BLOCK_SIZE[Py_ARRAY_LENGTH(BUFFER_BLOCK_SIZE) - 1];
         208                 :            :     }
         209                 :            :
         210                 :            :     // check max_length
         211         [ #  # ]:          0 :     if (buffer->max_length >= 0) {
         212                 :            :         // if (rest == 0), should not grow the buffer.
         213                 :          0 :         Py_ssize_t rest = buffer->max_length - buffer->allocated;
         214                 :            :         assert(rest > 0);
         215                 :            :
         216                 :            :         // block_size of the last block
         217         [ #  # ]:          0 :         if (block_size > rest) {
         218                 :          0 :             block_size = rest;
         219                 :            :         }
         220                 :            :     }
         221                 :            :
         222                 :            :     // check buffer->allocated overflow
         223         [ #  # ]:          0 :     if (block_size > PY_SSIZE_T_MAX - buffer->allocated) {
         224                 :          0 :         PyErr_SetString(PyExc_MemoryError, unable_allocate_msg);
         225                 :          0 :         return -1;
         226                 :            :     }
         227                 :            :
         228                 :            :     // create the block
         229                 :          0 :     b = PyBytes_FromStringAndSize(NULL, block_size);
         230         [ #  # ]:          0 :     if (b == NULL) {
         231                 :          0 :         PyErr_SetString(PyExc_MemoryError, unable_allocate_msg);
         232                 :          0 :         return -1;
         233                 :            :     }
         234         [ #  # ]:          0 :     if (PyList_Append(buffer->list, b) < 0) {
         235                 :          0 :         Py_DECREF(b);
         236                 :          0 :         return -1;
         237                 :            :     }
         238                 :          0 :     Py_DECREF(b);
         239                 :            :
         240                 :            :     // set variables
         241                 :          0 :     buffer->allocated += block_size;
         242                 :            :
         243                 :          0 :     *next_out = PyBytes_AS_STRING(b);
         244                 :          0 :     return block_size;
         245                 :            : }
         246                 :            :
         247                 :            : /* Return the current outputted data size. */
         248                 :            : static inline Py_ssize_t
         249                 :          0 : _BlocksOutputBuffer_GetDataSize(_BlocksOutputBuffer *buffer,
         250                 :            :                                 const Py_ssize_t avail_out)
         251                 :            : {
         252                 :          0 :     return buffer->allocated - avail_out;
         253                 :            : }
         254                 :            :
         255                 :            : /* Finish the buffer.
         256                 :            :
         257                 :            :    Return a bytes object on success
         258                 :            :    Return NULL on failure
         259                 :            : */
         260                 :            : static inline PyObject *
         261                 :          0 : _BlocksOutputBuffer_Finish(_BlocksOutputBuffer *buffer,
         262                 :            :                            const Py_ssize_t avail_out)
         263                 :            : {
         264                 :            :     PyObject *result, *block;
         265                 :          0 :     const Py_ssize_t list_len = Py_SIZE(buffer->list);
         266                 :            :
         267                 :            :     // fast path for single block
         268   [ #  #  #  #  :          0 :     if ((list_len == 1 && avail_out == 0) ||
                       #  # ]
         269         [ #  # ]:          0 :         (list_len == 2 && Py_SIZE(PyList_GET_ITEM(buffer->list, 1)) == avail_out))
         270                 :            :     {
         271                 :          0 :         block = PyList_GET_ITEM(buffer->list, 0);
         272                 :          0 :         Py_INCREF(block);
         273                 :            :
         274         [ #  # ]:          0 :         Py_CLEAR(buffer->list);
         275                 :          0 :         return block;
         276                 :            :     }
         277                 :            :
         278                 :            :     // final bytes object
         279                 :          0 :     result = PyBytes_FromStringAndSize(NULL, buffer->allocated - avail_out);
         280         [ #  # ]:          0 :     if (result == NULL) {
         281                 :          0 :         PyErr_SetString(PyExc_MemoryError, unable_allocate_msg);
         282                 :          0 :         return NULL;
         283                 :            :     }
         284                 :            :
         285                 :            :     // memory copy
         286         [ #  # ]:          0 :     if (list_len > 0) {
         287                 :          0 :         char *posi = PyBytes_AS_STRING(result);
         288                 :            :
         289                 :            :         // blocks except the last one
         290                 :          0 :         Py_ssize_t i = 0;
         291         [ #  # ]:          0 :         for (; i < list_len-1; i++) {
         292                 :          0 :             block = PyList_GET_ITEM(buffer->list, i);
         293                 :          0 :             memcpy(posi, PyBytes_AS_STRING(block), Py_SIZE(block));
         294                 :          0 :             posi += Py_SIZE(block);
         295                 :            :         }
         296                 :            :         // the last block
         297                 :          0 :         block = PyList_GET_ITEM(buffer->list, i);
         298                 :          0 :         memcpy(posi, PyBytes_AS_STRING(block), Py_SIZE(block) - avail_out);
         299                 :            :     } else {
         300                 :            :         assert(Py_SIZE(result) == 0);
         301                 :            :     }
         302                 :            :
         303         [ #  # ]:          0 :     Py_CLEAR(buffer->list);
         304                 :          0 :     return result;
         305                 :            : }
         306                 :            :
         307                 :            : /* Clean up the buffer when an error occurred. */
         308                 :            : static inline void
         309                 :          0 : _BlocksOutputBuffer_OnError(_BlocksOutputBuffer *buffer)
         310                 :            : {
         311         [ #  # ]:          0 :     Py_CLEAR(buffer->list);
         312                 :          0 : }
         313                 :            :
         314                 :            : #ifdef __cplusplus
         315                 :            : }
         316                 :            : #endif
         317                 :            : #endif /* Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H */

Generated by: LCOV version 1.14