From 3eaf5fe5d3a92b5b1bec464ca3bc2ae1758fb567 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sun, 11 Feb 2024 02:34:20 +0100 Subject: [PATCH] fix(c): solve undefined behaviour caused by unaligned access Close #734 --- docs/news.rst | 5 +- psycopg_c/psycopg_c/_psycopg/copy.pyx | 3 +- psycopg_c/psycopg_c/types/array.pyx | 23 ++-- psycopg_c/psycopg_c/types/datetime.pyx | 102 +++++++++------ psycopg_c/psycopg_c/types/numeric.pyx | 168 ++++++++++++++----------- 5 files changed, 177 insertions(+), 124 deletions(-) diff --git a/docs/news.rst b/docs/news.rst index 494ebaa19..9a62899bb 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -46,9 +46,10 @@ Psycopg 3.2 (unreleased) .. __: https://numpy.org/doc/stable/reference/arrays.scalars.html#built-in-scalar-types -Psycopg 3.1.19 -^^^^^^^^^^^^^^ +Psycopg 3.1.19 (unreleased) +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +- Fix unaligned access undefined behaviour in C extension (:ticket:`#734`). - Fix excessive stripping of error message prefixes (:ticket:`#752`). - Allow to specify the ``connect_timeout`` connection parameter as float (:ticket:`#796`). diff --git a/psycopg_c/psycopg_c/_psycopg/copy.pyx b/psycopg_c/psycopg_c/_psycopg/copy.pyx index b943095d1..e1ca8191f 100644 --- a/psycopg_c/psycopg_c/_psycopg/copy.pyx +++ b/psycopg_c/psycopg_c/_psycopg/copy.pyx @@ -191,7 +191,8 @@ def parse_row_binary(data, tx: Transformer) -> Tuple[Any, ...]: _buffer_as_string_and_size(data, &ptr, &bufsize) cdef unsigned char *bufend = ptr + bufsize - cdef uint16_t benfields = (ptr)[0] + cdef uint16_t benfields + memcpy(&benfields, ptr, sizeof(benfields)) cdef int nfields = endian.be16toh(benfields) ptr += sizeof(benfields) cdef list row = PyList_New(nfields) diff --git a/psycopg_c/psycopg_c/types/array.pyx b/psycopg_c/psycopg_c/types/array.pyx index 280d33c28..91da57702 100644 --- a/psycopg_c/psycopg_c/types/array.pyx +++ b/psycopg_c/psycopg_c/types/array.pyx @@ -210,8 +210,9 @@ cdef object _array_load_binary( const char *buf, size_t length, Transformer tx, PyObject **row_loader_ptr ): # head is ndims, hasnull, elem oid - cdef uint32_t *buf32 = buf - cdef int ndims = endian.be32toh(buf32[0]) + cdef uint32_t buf32 + memcpy(&buf32, buf, sizeof(buf32)) + cdef int ndims = endian.be32toh(buf32) if ndims <= 0: return [] @@ -222,17 +223,23 @@ cdef object _array_load_binary( ) cdef object oid + cdef uint32_t beoid if row_loader_ptr[0] == NULL: - oid = endian.be32toh(buf32[2]) + memcpy(&beoid, buf + 2 * sizeof(uint32_t), sizeof(beoid)) + oid = endian.be32toh(beoid) row_loader_ptr[0] = tx._c_get_loader(oid, PQ_BINARY) cdef Py_ssize_t[MAXDIM] dims cdef int i + cdef uint32_t bedata + cdef char *dimptr = buf + 3 * sizeof(uint32_t) for i in range(ndims): # Every dimension is dim, lower bound - dims[i] = endian.be32toh(buf32[3 + 2 * i]) + memcpy(&bedata, dimptr, sizeof(bedata)) + dimptr += 2 * sizeof(uint32_t) + dims[i] = endian.be32toh(bedata) - buf += (3 + 2 * ndims) * sizeof(uint32_t) + buf += (3 + 2 * ndims) * sizeof(bedata) out = _array_load_binary_rec(ndims, dims, &buf, row_loader_ptr[0]) return out @@ -242,6 +249,7 @@ cdef object _array_load_binary_rec( ): cdef const char *buf cdef int i + cdef uint32_t besize cdef int32_t size cdef object val @@ -251,8 +259,9 @@ cdef object _array_load_binary_rec( if ndims == 1: buf = bufptr[0] for i in range(nelems): - size = endian.be32toh((buf)[0]) - buf += sizeof(uint32_t) + memcpy(&besize, buf, sizeof(besize)) + size = endian.be32toh(besize) + buf += sizeof(besize) if size == -1: val = None else: diff --git a/psycopg_c/psycopg_c/types/datetime.pyx b/psycopg_c/psycopg_c/types/datetime.pyx index 4b0784bde..c11e7b11c 100644 --- a/psycopg_c/psycopg_c/types/datetime.pyx +++ b/psycopg_c/psycopg_c/types/datetime.pyx @@ -95,10 +95,10 @@ cdef class DateBinaryDumper(CDumper): cdef int32_t days = PyObject_CallFunctionObjArgs( date_toordinal, obj, NULL) days -= PG_DATE_EPOCH_DAYS - cdef int32_t *buf = CDumper.ensure_size( - rv, offset, sizeof(int32_t)) - buf[0] = endian.htobe32(days) - return sizeof(int32_t) + cdef uint32_t bedays = endian.htobe32(days) + cdef uint32_t *buf = CDumper.ensure_size(rv, offset, sizeof(bedays)) + memcpy(buf, &bedays, sizeof(bedays)) + return sizeof(bedays) cdef class _BaseTimeDumper(CDumper): @@ -168,15 +168,15 @@ cdef class TimeBinaryDumper(_BaseTimeDumper): oid = oids.TIME_OID cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1: - cdef int64_t micros = cdt.time_microsecond(obj) + 1000000 * ( + cdef int64_t us = cdt.time_microsecond(obj) + 1000000 * ( cdt.time_second(obj) + 60 * (cdt.time_minute(obj) + 60 * cdt.time_hour(obj)) ) + cdef uint64_t beus = endian.htobe64(us) - cdef int64_t *buf = CDumper.ensure_size( - rv, offset, sizeof(int64_t)) - buf[0] = endian.htobe64(micros) - return sizeof(int64_t) + cdef char *buf = CDumper.ensure_size(rv, offset, sizeof(beus)) + memcpy(buf, &beus, sizeof(beus)) + return sizeof(beus) cpdef upgrade(self, obj, format): if not obj.tzinfo: @@ -192,21 +192,21 @@ cdef class TimeTzBinaryDumper(_BaseTimeDumper): oid = oids.TIMETZ_OID cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1: - cdef int64_t micros = cdt.time_microsecond(obj) + 1_000_000 * ( + cdef int64_t us = cdt.time_microsecond(obj) + 1_000_000 * ( cdt.time_second(obj) + 60 * (cdt.time_minute(obj) + 60 * cdt.time_hour(obj)) ) + cdef uint64_t beus = endian.htobe64(us) off = self._get_offset(obj) cdef int32_t offsec = int(PyObject_CallFunctionObjArgs( timedelta_total_seconds, off, NULL)) + cdef uint32_t beoff = endian.htobe32(-offsec) - cdef char *buf = CDumper.ensure_size( - rv, offset, sizeof(int64_t) + sizeof(int32_t)) - (buf)[0] = endian.htobe64(micros) - ((buf + sizeof(int64_t)))[0] = endian.htobe32(-offsec) - - return sizeof(int64_t) + sizeof(int32_t) + cdef char *buf = CDumper.ensure_size(rv, offset, sizeof(beus) + sizeof(beoff)) + memcpy(buf, &beus, sizeof(beus)) + memcpy(buf + sizeof(beus), &beoff, sizeof(beoff)) + return sizeof(beus) + sizeof(beoff) cdef class _BaseDatetimeDumper(CDumper): @@ -268,13 +268,14 @@ cdef class DatetimeBinaryDumper(_BaseDatetimeDumper): cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1: delta = obj - pg_datetimetz_epoch - cdef int64_t micros = cdt.timedelta_microseconds(delta) + 1_000_000 * ( + cdef int64_t us = cdt.timedelta_microseconds(delta) + 1_000_000 * ( 86_400 * cdt.timedelta_days(delta) + cdt.timedelta_seconds(delta)) + cdef uint64_t beus = endian.htobe64(us) - cdef char *buf = CDumper.ensure_size(rv, offset, sizeof(int64_t)) - (buf)[0] = endian.htobe64(micros) - return sizeof(int64_t) + cdef char *buf = CDumper.ensure_size(rv, offset, sizeof(beus)) + memcpy(buf, &beus, sizeof(beus)) + return sizeof(beus) cpdef upgrade(self, obj, format): if obj.tzinfo: @@ -292,13 +293,14 @@ cdef class DatetimeNoTzBinaryDumper(_BaseDatetimeDumper): cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1: delta = obj - pg_datetime_epoch - cdef int64_t micros = cdt.timedelta_microseconds(delta) + 1_000_000 * ( + cdef int64_t us = cdt.timedelta_microseconds(delta) + 1_000_000 * ( 86_400 * cdt.timedelta_days(delta) + cdt.timedelta_seconds(delta)) + cdef uint64_t beus = endian.htobe64(us) - cdef char *buf = CDumper.ensure_size(rv, offset, sizeof(int64_t)) - (buf)[0] = endian.htobe64(micros) - return sizeof(int64_t) + cdef char *buf = CDumper.ensure_size(rv, offset, sizeof(beus)) + memcpy(buf, &beus, sizeof(beus)) + return sizeof(beus) @cython.final @@ -345,18 +347,22 @@ cdef class TimedeltaBinaryDumper(CDumper): oid = oids.INTERVAL_OID cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1: - cdef int64_t micros = ( + cdef int64_t us = ( 1_000_000 * cdt.timedelta_seconds(obj) + cdt.timedelta_microseconds(obj)) + cdef uint64_t beus = endian.htobe64(us) + cdef int32_t days = cdt.timedelta_days(obj) + cdef uint32_t bedays = endian.htobe32(days) + # The third item is months cdef char *buf = CDumper.ensure_size( - rv, offset, sizeof(int64_t) + sizeof(int32_t) + sizeof(int32_t)) - (buf)[0] = endian.htobe64(micros) - ((buf + sizeof(int64_t)))[0] = endian.htobe32(days) - ((buf + sizeof(int64_t) + sizeof(int32_t)))[0] = 0 + rv, offset, sizeof(beus) + sizeof(bedays) + sizeof(int32_t)) + memcpy(buf, &beus, sizeof(beus)) + memcpy(buf + sizeof(beus), &bedays, sizeof(bedays)) + memset(buf + sizeof(beus) + sizeof(bedays), 0, sizeof(int32_t)) - return sizeof(int64_t) + sizeof(int32_t) + sizeof(int32_t) + return sizeof(beus) + sizeof(bedays) + sizeof(int32_t) @cython.final @@ -419,7 +425,9 @@ cdef class DateBinaryLoader(CLoader): format = PQ_BINARY cdef object cload(self, const char *data, size_t length): - cdef int days = endian.be32toh((data)[0]) + cdef uint32_t bedata + memcpy(&bedata, data, sizeof(bedata)) + cdef int days = endian.be32toh(bedata) cdef object pydays = days + PG_DATE_EPOCH_DAYS try: return PyObject_CallFunctionObjArgs( @@ -467,7 +475,9 @@ cdef class TimeBinaryLoader(CLoader): format = PQ_BINARY cdef object cload(self, const char *data, size_t length): - cdef int64_t val = endian.be64toh((data)[0]) + cdef uint64_t bedata + memcpy(&bedata, data, sizeof(bedata)) + cdef int64_t val = endian.be64toh(bedata) cdef int h, m, s, us with cython.cdivision(True): @@ -531,8 +541,14 @@ cdef class TimetzBinaryLoader(CLoader): format = PQ_BINARY cdef object cload(self, const char *data, size_t length): - cdef int64_t val = endian.be64toh((data)[0]) - cdef int32_t off = endian.be32toh(((data + sizeof(int64_t)))[0]) + cdef uint64_t beval + memcpy(&beval, data, sizeof(beval)) + cdef int64_t val = endian.be64toh(beval) + + cdef uint32_t beoff + memcpy(&beoff, data + sizeof(beval), sizeof(beoff)) + cdef int32_t off = endian.be32toh(beoff) + cdef int h, m, s, us with cython.cdivision(True): @@ -664,7 +680,9 @@ cdef class TimestampBinaryLoader(CLoader): format = PQ_BINARY cdef object cload(self, const char *data, size_t length): - cdef int64_t val = endian.be64toh((data)[0]) + cdef uint64_t beval + memcpy(&beval, data, sizeof(beval)) + cdef int64_t val = endian.be64toh(beval) cdef int64_t micros, secs, days # Work only with positive values as the cdivision behaves differently @@ -791,7 +809,9 @@ cdef class TimestamptzBinaryLoader(_BaseTimestamptzLoader): format = PQ_BINARY cdef object cload(self, const char *data, size_t length): - cdef int64_t val = endian.be64toh((data)[0]) + cdef uint64_t bedata + memcpy(&bedata, data, sizeof(bedata)) + cdef int64_t val = endian.be64toh(bedata) cdef int64_t micros, secs, days # Work only with positive values as the cdivision behaves differently @@ -951,11 +971,11 @@ cdef class IntervalBinaryLoader(CLoader): format = PQ_BINARY cdef object cload(self, const char *data, size_t length): - cdef int64_t val = endian.be64toh((data)[0]) - cdef int32_t days = endian.be32toh( - ((data + sizeof(int64_t)))[0]) - cdef int32_t months = endian.be32toh( - ((data + sizeof(int64_t) + sizeof(int32_t)))[0]) + cdef int32_t bedata[4] + memcpy(&bedata, data, sizeof(bedata)) + cdef int64_t val = endian.be64toh((bedata)[0]) + cdef int32_t days = endian.be32toh(bedata[2]) + cdef int32_t months = endian.be32toh(bedata[3]) cdef int years with cython.cdivision(True): diff --git a/psycopg_c/psycopg_c/types/numeric.pyx b/psycopg_c/psycopg_c/types/numeric.pyx index de76a236c..f9580fd84 100644 --- a/psycopg_c/psycopg_c/types/numeric.pyx +++ b/psycopg_c/psycopg_c/types/numeric.pyx @@ -7,7 +7,7 @@ Cython adapters for numeric types. cimport cython from libc.stdint cimport * -from libc.string cimport memcpy, strlen +from libc.string cimport memcpy, memset, strlen from cpython.mem cimport PyMem_Free from cpython.dict cimport PyDict_GetItem, PyDict_SetItem from cpython.long cimport ( @@ -259,7 +259,9 @@ cdef class Int2BinaryLoader(CLoader): format = PQ_BINARY cdef object cload(self, const char *data, size_t length): - return PyLong_FromLong(endian.be16toh((data)[0])) + cdef int16_t bedata + memcpy(&bedata, data, sizeof(bedata)) + return PyLong_FromLong(endian.be16toh(bedata)) @cython.final @@ -268,7 +270,9 @@ cdef class Int4BinaryLoader(CLoader): format = PQ_BINARY cdef object cload(self, const char *data, size_t length): - return PyLong_FromLong(endian.be32toh((data)[0])) + cdef int32_t bedata + memcpy(&bedata, data, sizeof(bedata)) + return PyLong_FromLong(endian.be32toh(bedata)) @cython.final @@ -277,7 +281,9 @@ cdef class Int8BinaryLoader(CLoader): format = PQ_BINARY cdef object cload(self, const char *data, size_t length): - return PyLong_FromLongLong(endian.be64toh((data)[0])) + cdef int64_t bedata + memcpy(&bedata, data, sizeof(bedata)) + return PyLong_FromLongLong(endian.be64toh(bedata)) @cython.final @@ -286,7 +292,9 @@ cdef class OidBinaryLoader(CLoader): format = PQ_BINARY cdef object cload(self, const char *data, size_t length): - return PyLong_FromUnsignedLong(endian.be32toh((data)[0])) + cdef uint32_t bedata + memcpy(&bedata, data, sizeof(bedata)) + return PyLong_FromUnsignedLong(endian.be32toh(bedata)) cdef class _FloatDumper(CDumper): @@ -338,11 +346,11 @@ cdef class FloatBinaryDumper(CDumper): cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1: cdef double d = PyFloat_AsDouble(obj) - cdef uint64_t *intptr = &d + cdef uint64_t beval = endian.htobe64((&d)[0]) cdef uint64_t *buf = CDumper.ensure_size( - rv, offset, sizeof(uint64_t)) - buf[0] = endian.htobe64(intptr[0]) - return sizeof(uint64_t) + rv, offset, sizeof(beval)) + memcpy(buf, &beval, sizeof(beval)) + return sizeof(beval) @cython.final @@ -353,11 +361,11 @@ cdef class Float4BinaryDumper(CDumper): cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1: cdef float f = PyFloat_AsDouble(obj) - cdef uint32_t *intptr = &f + cdef uint32_t beval = endian.htobe32((&f)[0]) cdef uint32_t *buf = CDumper.ensure_size( - rv, offset, sizeof(uint32_t)) - buf[0] = endian.htobe32(intptr[0]) - return sizeof(uint32_t) + rv, offset, sizeof(beval)) + memcpy(buf, &beval, sizeof(beval)) + return sizeof(beval) @cython.final @@ -378,7 +386,9 @@ cdef class Float4BinaryLoader(CLoader): format = PQ_BINARY cdef object cload(self, const char *data, size_t length): - cdef uint32_t asint = endian.be32toh((data)[0]) + cdef uint32_t bedata + memcpy(&bedata, data, sizeof(bedata)) + cdef uint32_t asint = endian.be32toh(bedata) # avoid warning: # dereferencing type-punned pointer will break strict-aliasing rules cdef char *swp = &asint @@ -391,7 +401,9 @@ cdef class Float8BinaryLoader(CLoader): format = PQ_BINARY cdef object cload(self, const char *data, size_t length): - cdef uint64_t asint = endian.be64toh((data)[0]) + cdef uint64_t bedata + memcpy(&bedata, data, sizeof(bedata)) + cdef uint64_t asint = endian.be64toh(bedata) cdef char *swp = &asint return PyFloat_FromDouble((swp)[0]) @@ -448,24 +460,31 @@ cdef class NumericBinaryLoader(CLoader): cdef object cload(self, const char *data, size_t length): - cdef uint16_t *data16 = data - cdef uint16_t ndigits = endian.be16toh(data16[0]) - cdef int16_t weight = endian.be16toh(data16[1]) - cdef uint16_t sign = endian.be16toh(data16[2]) - cdef uint16_t dscale = endian.be16toh(data16[3]) + cdef uint16_t behead[4] + memcpy(&behead, data, sizeof(behead)) + cdef uint16_t ndigits = endian.be16toh(behead[0]) + cdef int16_t weight = endian.be16toh(behead[1]) + cdef uint16_t sign = endian.be16toh(behead[2]) + cdef uint16_t dscale = endian.be16toh(behead[3]) + cdef int shift cdef int i cdef PyObject *pctx cdef object key + cdef char *digitptr + cdef uint16_t bedigit if sign == NUMERIC_POS or sign == NUMERIC_NEG: if length != (4 + ndigits) * sizeof(uint16_t): raise e.DataError("bad ndigits in numeric binary representation") val = 0 + digitptr = data + sizeof(behead) for i in range(ndigits): + memcpy(&bedigit, digitptr, sizeof(bedigit)) + digitptr += sizeof(bedigit) val *= 10_000 - val += endian.be16toh(data16[i + 4]) + val += endian.be16toh(bedigit) shift = dscale - (ndigits - weight - 1) * DEC_DIGITS @@ -595,28 +614,30 @@ cdef Py_ssize_t dump_decimal_to_numeric_binary( cdef int sign = t[0] cdef tuple digits = t[1] cdef uint16_t *buf + cdef uint16_t behead[4] cdef Py_ssize_t length cdef object pyexp = t[2] cdef const char *bexp + if not isinstance(pyexp, int): # Handle inf, nan - length = 4 * sizeof(uint16_t) - buf = CDumper.ensure_size(rv, offset, length) - buf[0] = 0 - buf[1] = 0 - buf[3] = 0 + buf = CDumper.ensure_size(rv, offset, sizeof(behead)) + behead[0] = 0 + behead[1] = 0 + behead[3] = 0 bexp = PyUnicode_AsUTF8(pyexp) if bexp[0] == b'n' or bexp[0] == b'N': - buf[2] = endian.htobe16(NUMERIC_NAN) + behead[2] = endian.htobe16(NUMERIC_NAN) elif bexp[0] == b'F': if sign: - buf[2] = endian.htobe16(NUMERIC_NINF) + behead[2] = endian.htobe16(NUMERIC_NINF) else: - buf[2] = endian.htobe16(NUMERIC_PINF) + behead[2] = endian.htobe16(NUMERIC_PINF) else: raise e.DataError(f"unexpected decimal exponent: {pyexp}") - return length + memcpy(buf, behead, sizeof(behead)) + return sizeof(behead) cdef int exp = pyexp cdef uint16_t ndigits = len(digits) @@ -635,13 +656,13 @@ cdef Py_ssize_t dump_decimal_to_numeric_binary( ndigits += exp % DEC_DIGITS if nzdigits == 0: - length = 4 * sizeof(uint16_t) - buf = CDumper.ensure_size(rv, offset, length) - buf[0] = 0 # ndigits - buf[1] = 0 # weight - buf[2] = endian.htobe16(NUMERIC_POS) # sign - buf[3] = endian.htobe16(dscale) - return length + buf = CDumper.ensure_size(rv, offset, sizeof(behead)) + behead[0] = 0 # ndigits + behead[1] = 0 # weight + behead[2] = endian.htobe16(NUMERIC_POS) # sign + behead[3] = endian.htobe16(dscale) + memcpy(buf, behead, sizeof(behead)) + return sizeof(behead) # Equivalent of 0-padding left to align the py digits to the pg digits # but without changing the digits tuple. @@ -656,25 +677,28 @@ cdef Py_ssize_t dump_decimal_to_numeric_binary( cdef int tmp = nzdigits + wi cdef int pgdigits = tmp // DEC_DIGITS + (tmp % DEC_DIGITS and 1) - length = (pgdigits + 4) * sizeof(uint16_t) + length = sizeof(behead) + pgdigits * sizeof(uint16_t) buf = CDumper.ensure_size(rv, offset, length) - buf[0] = endian.htobe16(pgdigits) - buf[1] = endian.htobe16(((ndigits + exp) // DEC_DIGITS - 1)) - buf[2] = endian.htobe16(NUMERIC_NEG) if sign else endian.htobe16(NUMERIC_POS) - buf[3] = endian.htobe16(dscale) - - cdef uint16_t pgdigit = 0 - cdef int bi = 4 + behead[0] = endian.htobe16(pgdigits) + behead[1] = endian.htobe16(((ndigits + exp) // DEC_DIGITS - 1)) + behead[2] = endian.htobe16(NUMERIC_NEG) if sign else endian.htobe16(NUMERIC_POS) + behead[3] = endian.htobe16(dscale) + memcpy(buf, behead, sizeof(behead)) + buf += 4 + + cdef uint16_t pgdigit = 0, bedigit for i in range(nzdigits): pgdigit += pydigit_weights[wi] * (digits[i]) wi += 1 if wi >= DEC_DIGITS: - buf[bi] = endian.htobe16(pgdigit) + bedigit = endian.htobe16(pgdigit) + memcpy(buf, &bedigit, sizeof(bedigit)) + buf += 1 pgdigit = wi = 0 - bi += 1 if pgdigit: - buf[bi] = endian.htobe16(pgdigit) + bedigit = endian.htobe16(pgdigit) + memcpy(buf, &bedigit, sizeof(bedigit)) return length @@ -729,37 +753,31 @@ cdef Py_ssize_t dump_int_or_sub_to_text( cdef Py_ssize_t dump_int_to_int2_binary( obj, bytearray rv, Py_ssize_t offset ) except -1: - cdef int16_t *buf = CDumper.ensure_size( - rv, offset, sizeof(int16_t)) cdef int16_t val = PyLong_AsLongLong(obj) - # swap bytes if needed - cdef uint16_t *ptvar = (&val) - buf[0] = endian.htobe16(ptvar[0]) - return sizeof(int16_t) + cdef int16_t *buf = CDumper.ensure_size(rv, offset, sizeof(obj)) + cdef uint16_t beval = endian.htobe16(val) # swap bytes if needed + memcpy(buf, &beval, sizeof(beval)) + return sizeof(val) cdef Py_ssize_t dump_int_to_int4_binary( obj, bytearray rv, Py_ssize_t offset ) except -1: - cdef int32_t *buf = CDumper.ensure_size( - rv, offset, sizeof(int32_t)) cdef int32_t val = PyLong_AsLongLong(obj) - # swap bytes if needed - cdef uint32_t *ptvar = (&val) - buf[0] = endian.htobe32(ptvar[0]) - return sizeof(int32_t) + cdef int32_t *buf = CDumper.ensure_size(rv, offset, sizeof(val)) + cdef uint32_t beval = endian.htobe32(val) # swap bytes if needed + memcpy(buf, &beval, sizeof(beval)) + return sizeof(val) cdef Py_ssize_t dump_int_to_int8_binary( obj, bytearray rv, Py_ssize_t offset ) except -1: - cdef int64_t *buf = CDumper.ensure_size( - rv, offset, sizeof(int64_t)) cdef int64_t val = PyLong_AsLongLong(obj) - # swap bytes if needed - cdef uint64_t *ptvar = (&val) - buf[0] = endian.htobe64(ptvar[0]) - return sizeof(int64_t) + cdef int64_t *buf = CDumper.ensure_size(rv, offset, sizeof(val)) + cdef uint64_t beval = endian.htobe64(val) # swap bytes if needed + memcpy(buf, &beval, sizeof(beval)) + return sizeof(val) cdef Py_ssize_t dump_int_to_numeric_binary(obj, bytearray rv, Py_ssize_t offset) except -1: @@ -775,20 +793,24 @@ cdef Py_ssize_t dump_int_to_numeric_binary(obj, bytearray rv, Py_ssize_t offset) cdef Py_ssize_t length = sizeof(uint16_t) * (ndigits + 4) cdef uint16_t *buf buf = CDumper.ensure_size(rv, offset, length) - buf[0] = endian.htobe16(ndigits) - buf[1] = endian.htobe16(ndigits - 1) # weight - buf[2] = endian.htobe16(sign) - buf[3] = 0 # dscale + + cdef uint16_t behead[4] + behead[0] = endian.htobe16(ndigits) + behead[1] = endian.htobe16(ndigits - 1) # weight + behead[2] = endian.htobe16(sign) + behead[3] = 0 # dscale + memcpy(buf, behead, sizeof(behead)) cdef int i = 4 + ndigits - 1 - cdef uint16_t rem + cdef uint16_t rem, berem while obj: rem = obj % 10000 obj //= 10000 - buf[i] = endian.htobe16(rem) + berem = endian.htobe16(rem) + memcpy(buf + i, &berem, sizeof(berem)) i -= 1 while i > 3: - buf[i] = 0 + memset(buf + i, 0, sizeof(buf[0])) i -= 1 return length -- 2.47.2