@@ -241,10 +241,40 @@ pack_tuple_data(PyObject *self, PyObject *tup)
241241 return (_pack_tuple_data (tup ));
242242}
243243
244+ /*
245+ * Check for overflow before incrementing the buffer size for cat_messages.
246+ */
247+ #define INCSIZET (XVAR , AMT ) do { \
248+ size_t _amt_ = AMT; \
249+ size_t _newsize_ = XVAR + _amt_; \
250+ if (_newsize_ >= XVAR) XVAR = _newsize_; else { \
251+ PyErr_Format(PyExc_OverflowError, \
252+ "buffer size overflowed, was %zd bytes, but could not add %d more", XVAR, _amt_); \
253+ goto fail; } \
254+ } while(0)
255+
256+ #define INCMSGSIZE (XVAR , AMT ) do { \
257+ uint32_t _amt_ = AMT; \
258+ uint32_t _newsize_ = XVAR + _amt_; \
259+ if (_newsize_ >= XVAR) XVAR = _newsize_; else { \
260+ PyErr_Format(PyExc_OverflowError, \
261+ "message size too large, was %u bytes, but could not add %u more", XVAR, _amt_); \
262+ goto fail; } \
263+ } while(0)
264+
265+ /*
266+ * cat_messages - cat the serialized form of the messages in the given list
267+ *
268+ * This offers a fast way to construct the final bytes() object to be sent to
269+ * the wire. It avoids re-creating bytes() objects by calculating the serialized
270+ * size of contiguous, homogenous messages, allocating or extending the buffer
271+ * to accommodate for the needed size, and finally, copying the data into the
272+ * newly available space.
273+ */
244274static PyObject *
245275cat_messages (PyObject * self , PyObject * messages_in )
246276{
247- const static uint32_t null_attribute = 0xFFFFFFFFL ;
277+ const static char null_attribute [ 4 ] = { 0xff , 0xff , 0xff , 0xff } ;
248278 PyObject * msgs = NULL ;
249279 Py_ssize_t nmsgs = 0 ;
250280 Py_ssize_t cmsg = 0 ;
@@ -254,15 +284,16 @@ cat_messages(PyObject *self, PyObject *messages_in)
254284 */
255285 char * buf = NULL ;
256286 char * nbuf = NULL ;
257- Py_ssize_t bufsize = 0 ;
258- Py_ssize_t bufpos = 0 ;
287+ size_t bufsize = 0 ;
288+ size_t bufpos = 0 ;
259289
260290 /*
261291 * Get a List object for faster rescanning when dealing with copy data.
262292 */
263293 msgs = PyObject_CallFunctionObjArgs ((PyObject * ) & PyList_Type , messages_in , NULL );
264294 if (msgs == NULL )
265295 return (NULL );
296+
266297 nmsgs = PyList_GET_SIZE (msgs );
267298
268299 while (cmsg < nmsgs )
@@ -276,13 +307,13 @@ cat_messages(PyObject *self, PyObject *messages_in)
276307 if (PyBytes_CheckExact (ob ))
277308 {
278309 Py_ssize_t eofc = cmsg ;
279- Py_ssize_t xsize = 0 ;
310+ size_t xsize = 0 ;
280311 /* find the last of the copy data (eofc) */
281312 do
282313 {
283314 ++ eofc ;
284315 /* increase in size to allocate for the adjacent copy messages */
285- xsize += PyBytes_GET_SIZE (ob );
316+ INCSIZET ( xsize , PyBytes_GET_SIZE (ob ) );
286317 if (eofc >= nmsgs )
287318 break ; /* end of messages in the list? */
288319
@@ -296,8 +327,8 @@ cat_messages(PyObject *self, PyObject *messages_in)
296327 */
297328
298329 /* realloc the buf for the new copy data */
299- xsize = xsize + (5 * (eofc - cmsg ));
300- bufsize = bufsize + xsize ;
330+ INCSIZET ( xsize , (5 * (eofc - cmsg ) ));
331+ INCSIZET ( bufsize , xsize ) ;
301332 nbuf = realloc (buf , bufsize );
302333 if (nbuf == NULL )
303334 {
@@ -320,17 +351,17 @@ cat_messages(PyObject *self, PyObject *messages_in)
320351 */
321352 while (cmsg < eofc )
322353 {
323- uint32_t msg_length ;
354+ uint32_t msg_length = 0 ;
324355 char * localbuf = buf + bufpos + 1 ;
325356 buf [bufpos ] = 'd' ; /* COPY data message type */
326357
327358 ob = PyList_GET_ITEM (msgs , cmsg );
328- msg_length = PyBytes_GET_SIZE (ob ) + 4 ;
359+ INCMSGSIZE ( msg_length , ( uint32_t ) PyBytes_GET_SIZE (ob ) + 4 ) ;
329360
330- bufpos = bufpos + 1 + msg_length ;
361+ INCSIZET ( bufpos , 1 + msg_length ) ;
331362 msg_length = local_ntohl (msg_length );
332- memcpy (localbuf , & msg_length , 4 );
333- memcpy (localbuf + 4 , PyBytes_AS_STRING (ob ), PyBytes_GET_SIZE (ob ));
363+ Py_MEMCPY (localbuf , & msg_length , 4 );
364+ Py_MEMCPY (localbuf + 4 , PyBytes_AS_STRING (ob ), PyBytes_GET_SIZE (ob ));
334365 ++ cmsg ;
335366 }
336367 }
@@ -340,7 +371,7 @@ cat_messages(PyObject *self, PyObject *messages_in)
340371 * Handle 'D' tuple data from a raw Python tuple.
341372 */
342373 Py_ssize_t eofc = cmsg ;
343- Py_ssize_t xsize = 0 ;
374+ size_t xsize = 0 ;
344375
345376 /* find the last of the tuple data (eofc) */
346377 do
@@ -359,17 +390,17 @@ cat_messages(PyObject *self, PyObject *messages_in)
359390 * The items take *at least* 4 bytes each.
360391 * (The attribute count is considered later)
361392 */
362- xsize = xsize + (nitems * 4 );
393+ INCSIZET ( xsize , (nitems * 4 ) );
363394
364395 for (current_item = 0 ; current_item < nitems ; ++ current_item )
365396 {
366397 PyObject * att = PyTuple_GET_ITEM (ob , current_item );
367398
368399 /*
369- * Attributes are expected to be bytes() or None.
400+ * Attributes *must* be bytes() or None.
370401 */
371402 if (PyBytes_CheckExact (att ))
372- xsize = xsize + PyBytes_GET_SIZE (att );
403+ INCSIZET ( xsize , PyBytes_GET_SIZE (att ) );
373404 else if (att != Py_None )
374405 {
375406 PyErr_Format (PyExc_TypeError ,
@@ -378,7 +409,7 @@ cat_messages(PyObject *self, PyObject *messages_in)
378409 goto fail ;
379410 }
380411 /*
381- * else it's Py_None and the size has been specified .
412+ * else it's Py_None and the size will be included later .
382413 */
383414 }
384415
@@ -403,15 +434,15 @@ cat_messages(PyObject *self, PyObject *messages_in)
403434 * 4 for the message size
404435 * 2 for the attribute count
405436 */
406- xsize = xsize + (7 * (eofc - cmsg ));
407- bufsize = bufsize + xsize ;
437+ INCSIZET ( xsize , (7 * (eofc - cmsg ) ));
438+ INCSIZET ( bufsize , xsize ) ;
408439 nbuf = realloc (buf , bufsize );
409440 if (nbuf == NULL )
410441 {
411442 PyErr_Format (
412443 PyExc_MemoryError ,
413- "failed to allocate %lu bytes of memory for out-going messages" ,
414- ( unsigned long ) bufsize
444+ "failed to allocate %zd bytes of memory for out-going messages" ,
445+ bufsize
415446 );
416447 goto fail ;
417448 }
@@ -433,7 +464,7 @@ cat_messages(PyObject *self, PyObject *messages_in)
433464 Py_ssize_t current_item , nitems ;
434465 uint32_t msg_length , out_msg_len ;
435466 uint16_t natts ;
436- char * localbuf = buf + bufpos + 5 ; /* skipping the header for now */
467+ char * localbuf = ( buf + bufpos ) + 5 ; /* skipping the header for now */
437468 buf [bufpos ] = 'D' ; /* Tuple data message type */
438469
439470 ob = PyList_GET_ITEM (msgs , cmsg );
@@ -474,7 +505,7 @@ cat_messages(PyObject *self, PyObject *messages_in)
474505 Py_MEMCPY (localbuf , PyBytes_AS_STRING (att ), attsize );
475506 localbuf = localbuf + attsize ;
476507
477- msg_length = msg_length + attsize ;
508+ INCSIZET ( msg_length , attsize ) ;
478509 }
479510 }
480511
@@ -488,7 +519,7 @@ cat_messages(PyObject *self, PyObject *messages_in)
488519 * Filled in the data while summing the message size, so
489520 * adjust the buffer position for the next message.
490521 */
491- bufpos = bufpos + 1 + msg_length ;
522+ INCSIZET ( bufpos , 1 + msg_length ) ;
492523 ++ cmsg ;
493524 }
494525 }
@@ -542,7 +573,8 @@ cat_messages(PyObject *self, PyObject *messages_in)
542573 msg_type_size = PyBytes_GET_SIZE (msg_type );
543574
544575 /* realloc the buf for the new copy data */
545- bufsize = bufsize + 4 + msg_type_size + PyBytes_GET_SIZE (serialized );
576+ INCSIZET (bufsize , 4 + msg_type_size );
577+ INCSIZET (bufsize , PyBytes_GET_SIZE (serialized ));
546578 nbuf = realloc (buf , bufsize );
547579 if (nbuf == NULL )
548580 {
@@ -565,10 +597,11 @@ cat_messages(PyObject *self, PyObject *messages_in)
565597 * All necessary information acquired, so fill in the message's data.
566598 */
567599 buf [bufpos ] = * (PyBytes_AS_STRING (msg_type ));
568- msg_length = PyBytes_GET_SIZE (serialized ) + 4 ;
600+ msg_length = PyBytes_GET_SIZE (serialized );
601+ INCMSGSIZE (msg_length , 4 );
569602 msg_length = local_ntohl (msg_length );
570- memcpy (buf + bufpos + msg_type_size , & msg_length , 4 );
571- memcpy (
603+ Py_MEMCPY (buf + bufpos + msg_type_size , & msg_length , 4 );
604+ Py_MEMCPY (
572605 buf + bufpos + 4 + msg_type_size ,
573606 PyBytes_AS_STRING (serialized ),
574607 PyBytes_GET_SIZE (serialized )
0 commit comments