From 97ba6ca0d29c1e8a5b30112f13f37a4d6b3c4492 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 3 Jun 2026 22:17:26 +0900 Subject: [PATCH 01/13] skip ci: remove unneeded CIBW_SKIP option --- .github/workflows/wheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index fb78688..56b31f0 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -48,7 +48,7 @@ jobs: env: CIBW_TEST_REQUIRES: "pytest" CIBW_TEST_COMMAND: "pytest {package}/test" - CIBW_SKIP: "pp* cp38-* cp39-* cp310-win_arm64" + CIBW_SKIP: "cp38-* cp39-* cp310-win_arm64" CIBW_ARCHS: ${{ matrix.cibw_archs || 'auto' }} - name: Build sdist From c410a388c58d55091736e510cad064977a12cb8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 11:54:05 +0900 Subject: [PATCH 02/13] Bump pypa/cibuildwheel from 3.4.1 to 4.0.0 (#691) --- .github/workflows/wheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index 56b31f0..a3d80b5 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -44,7 +44,7 @@ jobs: platforms: ${{ matrix.cibw_archs }} - name: Build - uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1 + uses: pypa/cibuildwheel@f03ac7617d6cff873ccf24cc0d567ef5ba5a9e6d # v4.0.0 env: CIBW_TEST_REQUIRES: "pytest" CIBW_TEST_COMMAND: "pytest {package}/test" From 11ed0a5110c1920b85b7d24d9414f480983e0f16 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 11 Jun 2026 12:29:41 +0900 Subject: [PATCH 03/13] release v1.2.0 (#692) --- CHANGELOG.md | 2 +- msgpack/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63cfcce..a29b4b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # 1.2.0 -Release Date: TBD +Release Date: 2026-06-11 - Support free threaded Python. #654, #686 - Dropped support for Python 3.9. #656 diff --git a/msgpack/__init__.py b/msgpack/__init__.py index f7346d0..f053abd 100644 --- a/msgpack/__init__.py +++ b/msgpack/__init__.py @@ -5,7 +5,7 @@ from .exceptions import * # noqa: F403 from .ext import ExtType, Timestamp version = (1, 2, 0) -__version__ = "1.2.0rc1" +__version__ = "1.2.0" if os.environ.get("MSGPACK_PUREPYTHON"): From 0f4f350b6f3e80f24750422673424cce5d96e15d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:22:58 +0900 Subject: [PATCH 04/13] Bump pypa/cibuildwheel from 4.0.0 to 4.1.0 in the all-dependencies group (#694) --- .github/workflows/wheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index a3d80b5..92fce01 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -44,7 +44,7 @@ jobs: platforms: ${{ matrix.cibw_archs }} - name: Build - uses: pypa/cibuildwheel@f03ac7617d6cff873ccf24cc0d567ef5ba5a9e6d # v4.0.0 + uses: pypa/cibuildwheel@294735312765b09d24a2fbec22660ce817587d55 # v4.1.0 env: CIBW_TEST_REQUIRES: "pytest" CIBW_TEST_COMMAND: "pytest {package}/test" From 2c56ddb5d0025ed481d962c0f5d62d19dec7476d Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 19 Jun 2026 00:13:13 +0900 Subject: [PATCH 05/13] Merge commit from fork * fix Unpacker crash after unpack failure. * fixup --- msgpack/_unpacker.pyx | 8 ++++++-- msgpack/unpack_template.h | 22 +++++++++++++++------- requirements.txt | 3 ++- test/test_except.py | 26 ++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/msgpack/_unpacker.pyx b/msgpack/_unpacker.pyx index 4bfbe06..e0cdde4 100644 --- a/msgpack/_unpacker.pyx +++ b/msgpack/_unpacker.pyx @@ -51,6 +51,7 @@ cdef extern from "unpack.h": execute_fn unpack_skip execute_fn read_array_header execute_fn read_map_header + void unpack_init(unpack_context* ctx) object unpack_data(unpack_context* ctx) void unpack_clear(unpack_context* ctx) @@ -197,6 +198,7 @@ def unpackb(object packed, *, object object_hook=None, object list_hook=None, if off < buf_len: raise ExtraData(obj, PyBytes_FromStringAndSize(buf+off, buf_len-off)) return obj + unpack_clear(&ctx) if ret == 0: raise ValueError("Unpack failed: incomplete input") @@ -475,7 +477,7 @@ cdef class Unpacker: obj = unpack_data(&self.ctx) unpack_init(&self.ctx) return obj - elif ret == 0: + if ret == 0: if self.file_like is not None: self.read_from_file() continue @@ -483,7 +485,9 @@ cdef class Unpacker: raise StopIteration("No more data to unpack.") else: raise OutOfData("No more data to unpack.") - elif ret == -2: + + unpack_clear(&self.ctx) + if ret == -2: raise FormatError elif ret == -3: raise StackError diff --git a/msgpack/unpack_template.h b/msgpack/unpack_template.h index 4230661..ab5887a 100644 --- a/msgpack/unpack_template.h +++ b/msgpack/unpack_template.h @@ -72,15 +72,14 @@ static inline PyObject* unpack_data(unpack_context* ctx) static inline void unpack_clear(unpack_context *ctx) { - unsigned int i; - for (i = 1; i < ctx->top; i++) { - Py_CLEAR(ctx->stack[i].obj); + for (unsigned int i = 0; i < ctx->top; i++) { /* map_key holds a live reference only while waiting for the value */ if (ctx->stack[i].ct == CT_MAP_VALUE) { Py_CLEAR(ctx->stack[i].map_key); } + Py_CLEAR(ctx->stack[i].obj); } - Py_CLEAR(ctx->stack[0].obj); + unpack_init(ctx); } static inline int unpack_execute(bool construct, unpack_context* ctx, const char* data, Py_ssize_t len, Py_ssize_t* off) @@ -200,7 +199,7 @@ static inline int unpack_execute(bool construct, unpack_context* ctx, const char case 0xd5: // fixext 2 case 0xd6: // fixext 4 case 0xd7: // fixext 8 - again_fixed_trail_if_zero(ACS_EXT_VALUE, + again_fixed_trail_if_zero(ACS_EXT_VALUE, (1 << (((unsigned int)*p) & 0x03))+1, _ext_zero); case 0xd8: // fixext 16 @@ -344,6 +343,7 @@ _push: goto _header_again; case CT_MAP_VALUE: if(construct_cb(_map_item)(user, c->count, &c->obj, c->map_key, obj) < 0) { goto _failed; } + c->map_key = NULL; if(++c->count == c->size) { obj = c->obj; if (construct_cb(_map_end)(user, &obj) < 0) { goto _failed; } @@ -406,10 +406,18 @@ _end: #undef start_container static int unpack_construct(unpack_context *ctx, const char *data, Py_ssize_t len, Py_ssize_t *off) { - return unpack_execute(1, ctx, data, len, off); + int ret = unpack_execute(1, ctx, data, len, off); + if (ret == -1) { + unpack_clear(ctx); + } + return ret; } static int unpack_skip(unpack_context *ctx, const char *data, Py_ssize_t len, Py_ssize_t *off) { - return unpack_execute(0, ctx, data, len, off); + int ret = unpack_execute(0, ctx, data, len, off); + if (ret == -1) { + unpack_clear(ctx); + } + return ret; } #define unpack_container_header read_array_header diff --git a/requirements.txt b/requirements.txt index 9e4643b..7991e3f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ -Cython==3.2.1 +cython==3.2.5 setuptools==78.1.1 +pytest build diff --git a/test/test_except.py b/test/test_except.py index c04b110..a3bf467 100644 --- a/test/test_except.py +++ b/test/test_except.py @@ -33,6 +33,22 @@ def test_raise_from_object_hook(): object_pairs_hook=hook, ) + up = Unpacker(object_hook=hook) + + def up_unpack(x): + up.feed(x) + return up.unpack() + + raises(DummyException, up_unpack, packb({})) + raises(DummyException, up_unpack, packb({"fizz": "buzz"})) + raises(DummyException, up_unpack, packb({"fizz": "buzz"})) + raises(DummyException, up_unpack, packb({"fizz": {"buzz": "spam"}})) + raises( + DummyException, + up_unpack, + packb({"fizz": {"buzz": "spam"}}), + ) + def test_raise_from_list_hook(): def hook(lst: list) -> list: @@ -138,3 +154,13 @@ def test_strict_map_key_with_object_pairs_hook(): packed = packb(valid, use_bin_type=True) result = unpackb(packed, raw=False, strict_map_key=True, object_pairs_hook=list) assert result == [("key", "value")] + + +def test_unpacker_should_not_crash_after_exception(): + up = Unpacker() # default: strict_map_key=True + up.feed(b"\x83\x73\xc4\x00") # fixmap(3): int key (rejected) + empty bin8 + try: + up.unpack() # ValueError: int is not allowed for map key ... + except Exception: + pass + up.skip() # SIGSEGV (resumes from a corrupt parser context) From 448d43f5dcca7b3a2b0810e161af0a6431d90071 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 19 Jun 2026 00:33:29 +0900 Subject: [PATCH 06/13] release v1.2.1 (#698) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- CHANGELOG.md | 9 +++++++++ README.md | 6 +++++- msgpack/__init__.py | 4 ++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a29b4b2..b8fbd8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 1.2.1 + +Release Date: 2026-06-19 + +Fix a segfault when calling `Unpacker.unpack()` or `Unpacker.skip()` after an unpacking failure. +But note that reusing the same `Unpacker` instance after an unpacking failure is not supported. +Please create a new `Unpacker` instance instead. GHSA-6v7p-g79w-8964 + + # 1.2.0 Release Date: 2026-06-11 diff --git a/README.md b/README.md index 223742d..e66d454 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,10 @@ for unpacked in unpacker: print(unpacked) ``` +> [!IMPORTANT] +> If `Unpacker.unpack()` stops with an exception other than `OutOfData`, that `Unpacker` cannot be reused. +> Create a new `Unpacker` when reading another stream. + ### Packing/unpacking of custom data types @@ -220,7 +224,7 @@ When upgrading from msgpack-0.4 or earlier, do `pip uninstall msgpack-python` be * The extension module no longer supports Python 2. The pure Python implementation (`msgpack.fallback`) is used for Python 2. - + * msgpack 1.0.6 drops official support of Python 2.7, as pip and GitHub Action "setup-python" no longer supports Python 2.7. diff --git a/msgpack/__init__.py b/msgpack/__init__.py index f053abd..eeb85af 100644 --- a/msgpack/__init__.py +++ b/msgpack/__init__.py @@ -4,8 +4,8 @@ import os from .exceptions import * # noqa: F403 from .ext import ExtType, Timestamp -version = (1, 2, 0) -__version__ = "1.2.0" +version = (1, 2, 1) +__version__ = "1.2.1" if os.environ.get("MSGPACK_PUREPYTHON"): From f1a170234a95dd64d4c0dee398d16d4a011e90ea Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 19 Jun 2026 18:49:02 +0900 Subject: [PATCH 07/13] fix docstring for read_size (#700) fix #697 --- .gitignore | 1 + msgpack/_unpacker.pyx | 2 +- msgpack/fallback.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 341be63..31d7d6e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ msgpack/*.cpp /tags /docs/_build .cache +uv.lock diff --git a/msgpack/_unpacker.pyx b/msgpack/_unpacker.pyx index e0cdde4..26fb377 100644 --- a/msgpack/_unpacker.pyx +++ b/msgpack/_unpacker.pyx @@ -222,7 +222,7 @@ cdef class Unpacker: If specified, unpacker reads serialized data from it and `.feed()` is not usable. :param int read_size: - Used as `file_like.read(read_size)`. (default: `min(16*1024, max_buffer_size)`) + Used as `file_like.read(read_size)`. Must be equal to or smaller than *max_buffer_size*. :param bool use_list: If true, unpack msgpack array to Python list. diff --git a/msgpack/fallback.py b/msgpack/fallback.py index 860d94a..824f59d 100644 --- a/msgpack/fallback.py +++ b/msgpack/fallback.py @@ -137,7 +137,7 @@ class Unpacker: If specified, unpacker reads serialized data from it and `.feed()` is not usable. :param int read_size: - Used as `file_like.read(read_size)`. (default: `min(16*1024, max_buffer_size)`) + Used as `file_like.read(read_size)`. Must be equal to or smaller than *max_buffer_size*. :param bool use_list: If true, unpack msgpack array to Python list. From d13418061d00e10964394bda555247727f85ce28 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 19 Jun 2026 20:48:27 +0900 Subject: [PATCH 08/13] unpacker: fix silent datetime truncation with datetime=3 (#701) --- msgpack/unpack.h | 9 ++++++++- test/test_timestamp.py | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/msgpack/unpack.h b/msgpack/unpack.h index 0f9ffc0..55cfdab 100644 --- a/msgpack/unpack.h +++ b/msgpack/unpack.h @@ -283,6 +283,7 @@ static int unpack_timestamp(const char* buf, unsigned int buflen, msgpack_timest ts->tv_sec = _msgpack_load64(int64_t, buf + 4); return 0; default: + PyErr_Format(PyExc_ValueError, "invalid timestamp data (length %u)", buflen); return -1; } } @@ -336,12 +337,18 @@ static int unpack_callback_ext(unpack_user* u, const char* base, const char* pos else if (u->timestamp == 3) { // datetime // Calculate datetime using epoch + delta // due to limitations PyDateTime_FromTimestamp on Windows with negative timestamps + int64_t days = ts.tv_sec / (24*3600); + if (days < INT_MIN || days > INT_MAX) { + PyErr_Format(PyExc_OverflowError, + "days=%lld; too large to convert to C int", days); + return -1; + } PyObject *epoch = PyDateTimeAPI->DateTime_FromDateAndTime(1970, 1, 1, 0, 0, 0, 0, u->utc, PyDateTimeAPI->DateTimeType); if (epoch == NULL) { return -1; } - PyObject* d = PyDelta_FromDSU(ts.tv_sec/(24*3600), ts.tv_sec%(24*3600), ts.tv_nsec / 1000); + PyObject* d = PyDelta_FromDSU((int)days, ts.tv_sec%(24*3600), ts.tv_nsec / 1000); if (d == NULL) { Py_DECREF(epoch); return -1; diff --git a/test/test_timestamp.py b/test/test_timestamp.py index 6dfe368..7c8e3e8 100644 --- a/test/test_timestamp.py +++ b/test/test_timestamp.py @@ -176,3 +176,9 @@ def test_pack_datetime_without_tzinfo(): packed = msgpack.packb(dt, datetime=True) unpacked = msgpack.unpackb(packed, timestamp=3) assert unpacked == dt + + +def test_too_large_timestamp(): + # When timestamp64 is too large, conversion to datetime fails due to int64 -> int32 conversion. + # https://github.com/msgpack/msgpack-python/issues/696 + print(msgpack.unpackb(b"\xd7\xff" + b"\x00" * 8, timestamp=3)) From 7082130739d640134e961a384d33ca9939e1f969 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 19 Jun 2026 21:45:06 +0900 Subject: [PATCH 09/13] run test with debug build (#702) --- .github/workflows/test_debug.yml | 62 ++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/test_debug.yml diff --git a/.github/workflows/test_debug.yml b/.github/workflows/test_debug.yml new file mode 100644 index 0000000..8aeec64 --- /dev/null +++ b/.github/workflows/test_debug.yml @@ -0,0 +1,62 @@ +name: Run tests with debug Python +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + env: + PYTHON_VERSION: 3.14.6 + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: /opt/python-debug + key: python-debug-${{ runner.os }}-${{ env.PYTHON_VERSION }} + + - name: Install build dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: | + sudo apt-get update + sudo apt-get install -y build-essential libssl-dev zlib1g-dev \ + libbz2-dev libreadline-dev libsqlite3-dev curl libncursesw5-dev \ + xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev + + - name: Build Python with Py_DEBUG + if: steps.cache.outputs.cache-hit != 'true' + run: | + wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz + tar -xzf Python-${PYTHON_VERSION}.tgz + cd Python-${PYTHON_VERSION} + + ./configure --with-pydebug --prefix=/opt/python-debug + make -j4 + sudo make install + + - name: Create venv from debug Python + run: | + PYDBG_BIN="/opt/python-debug/bin/python3" + "$PYDBG_BIN" -m venv .venv + echo "$PWD/.venv/bin" >> "$GITHUB_PATH" + echo "VIRTUAL_ENV=$PWD/.venv" >> "$GITHUB_ENV" + + - name: Prepare + run: | + python -m pip install -r requirements.txt pytest + + - name: Build + run: | + make cython + pip install . + + - name: Test (C extension) + run: | + pytest -v test + + - name: Test (pure Python fallback) + run: | + MSGPACK_PUREPYTHON=1 pytest -v test From cf3fd2b0617d50f60d331490f91110721cebaef6 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 24 Jun 2026 15:13:45 +0900 Subject: [PATCH 10/13] fix: add reentrant guard to Unpacker.feed() (#704) fix ##695 --- msgpack/_unpacker.pyx | 69 +++++++++++++++++++++++++------------------ test/test_unpack.py | 19 ++++++++++++ 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/msgpack/_unpacker.pyx b/msgpack/_unpacker.pyx index 26fb377..f758c0c 100644 --- a/msgpack/_unpacker.pyx +++ b/msgpack/_unpacker.pyx @@ -305,6 +305,8 @@ cdef class Unpacker: Raises ``OutOfData`` when *packed* is incomplete. Raises ``FormatError`` when *packed* is not valid msgpack. Raises ``StackError`` when *packed* contains too nested. + Raises ``RuntimeError`` when ``feed()`` is called while unpacking + is in progress (e.g. from a hook). Other exceptions can be raised during unpacking. """ cdef unpack_context ctx @@ -318,6 +320,7 @@ cdef class Unpacker: cdef object unicode_errors cdef Py_ssize_t max_buffer_size cdef uint64_t stream_offset + cdef bint _unpacking def __dealloc__(self): unpack_clear(&self.ctx) @@ -381,6 +384,7 @@ cdef class Unpacker: self.buf_head = 0 self.buf_tail = 0 self.stream_offset = 0 + self._unpacking = False if unicode_errors is not None: self.unicode_errors = unicode_errors @@ -398,6 +402,11 @@ cdef class Unpacker: cdef char* buf cdef Py_ssize_t buf_len + if self._unpacking: + raise RuntimeError( + "Unpacker.feed() cannot be called while unpacking is in progress" + ) + if self.file_like is not None: raise AssertionError( "unpacker.feed() is not be able to use with `file_like`.") @@ -465,36 +474,40 @@ cdef class Unpacker: cdef object obj cdef Py_ssize_t prev_head - while 1: - prev_head = self.buf_head - if prev_head < self.buf_tail: - ret = execute(&self.ctx, self.buf, self.buf_tail, &self.buf_head) - self.stream_offset += self.buf_head - prev_head - else: - ret = 0 - - if ret == 1: - obj = unpack_data(&self.ctx) - unpack_init(&self.ctx) - return obj - if ret == 0: - if self.file_like is not None: - self.read_from_file() - continue - if iter: - raise StopIteration("No more data to unpack.") + self._unpacking = True + try: + while 1: + prev_head = self.buf_head + if prev_head < self.buf_tail: + ret = execute(&self.ctx, self.buf, self.buf_tail, &self.buf_head) + self.stream_offset += self.buf_head - prev_head else: - raise OutOfData("No more data to unpack.") + ret = 0 - unpack_clear(&self.ctx) - if ret == -2: - raise FormatError - elif ret == -3: - raise StackError - elif PyErr_Occurred(): - raise - else: - raise ValueError("Unpack failed: error = %d" % (ret,)) + if ret == 1: + obj = unpack_data(&self.ctx) + unpack_init(&self.ctx) + return obj + if ret == 0: + if self.file_like is not None: + self.read_from_file() + continue + if iter: + raise StopIteration("No more data to unpack.") + else: + raise OutOfData("No more data to unpack.") + + unpack_clear(&self.ctx) + if ret == -2: + raise FormatError + elif ret == -3: + raise StackError + elif PyErr_Occurred(): + raise + else: + raise ValueError("Unpack failed: error = %d" % (ret,)) + finally: + self._unpacking = False @cython.critical_section def read_bytes(self, Py_ssize_t nbytes): diff --git a/test/test_unpack.py b/test/test_unpack.py index 705c16a..81bc976 100644 --- a/test/test_unpack.py +++ b/test/test_unpack.py @@ -123,3 +123,22 @@ def test_unpacker_reinit_clears_partial_state(): unpacker.feed(packb({"a": 1})) assert unpacker.unpack() == {"a": 1} + + +@mark.skipif( + Unpacker.__module__ == "msgpack.fallback", + reason="reentrant guard is implemented in C extension only", +) +def test_unpacker_reentrant_feed(): + import struct + + def ext_hook(code, data): + # re-entrant feed on the SAME unpacker, large enough to force a buffer realloc + up.feed(b"\xc0" * 100) + return 0 + + up = Unpacker(ext_hook=ext_hook, max_buffer_size=64 * 1024 * 1024) + # array(11): [ ExtType(code=5, data=b'A') (fires the re-entrant hook), then 10 more elements ] + up.feed(b"\xdc" + struct.pack(">H", 11) + b"\xd4\x05A" + b"\x2a" * 10) + with raises(RuntimeError): + up.unpack() From 33fec11eb4b72133f10d0640cc7209b5cc8ed660 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 15:14:03 +0900 Subject: [PATCH 11/13] Bump actions/checkout from 6.0.3 to 7.0.0 in the all-dependencies group (#705) --- .github/workflows/docs.yaml | 2 +- .github/workflows/lint.yaml | 2 +- .github/workflows/test.yml | 2 +- .github/workflows/test_debug.yml | 2 +- .github/workflows/wheel.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 221f5d4..7ae7943 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 6ed5a1c..1ad0a1e 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-slim steps: - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: ruff check run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bc101aa..67c5e52 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/test_debug.yml b/.github/workflows/test_debug.yml index 8aeec64..0cfb200 100644 --- a/.github/workflows/test_debug.yml +++ b/.github/workflows/test_debug.yml @@ -11,7 +11,7 @@ jobs: PYTHON_VERSION: 3.14.6 steps: - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index 92fce01..8234ab4 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -26,7 +26,7 @@ jobs: name: Build wheels on ${{ matrix.os }}${{ matrix.name_suffix || '' }} steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.x" From 951995ecca1f4428967fbbab36f147412d0a5214 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 24 Jun 2026 17:14:55 +0900 Subject: [PATCH 12/13] Improve error handling and reporting in unpacking functions (#707) --- msgpack/_unpacker.pyx | 6 +----- msgpack/unpack.h | 5 ----- msgpack/unpack_template.h | 25 +++---------------------- 3 files changed, 4 insertions(+), 32 deletions(-) diff --git a/msgpack/_unpacker.pyx b/msgpack/_unpacker.pyx index f758c0c..e046361 100644 --- a/msgpack/_unpacker.pyx +++ b/msgpack/_unpacker.pyx @@ -46,7 +46,7 @@ cdef extern from "unpack.h": Py_ssize_t count ctypedef int (*execute_fn)(unpack_context* ctx, const char* data, - Py_ssize_t len, Py_ssize_t* off) except? -1 + Py_ssize_t len, Py_ssize_t* off) except -1 execute_fn unpack_construct execute_fn unpack_skip execute_fn read_array_header @@ -206,8 +206,6 @@ def unpackb(object packed, *, object object_hook=None, object list_hook=None, raise FormatError elif ret == -3: raise StackError - elif PyErr_Occurred(): - raise else: raise ValueError("Unpack failed: error = %d" % (ret,)) @@ -502,8 +500,6 @@ cdef class Unpacker: raise FormatError elif ret == -3: raise StackError - elif PyErr_Occurred(): - raise else: raise ValueError("Unpack failed: error = %d" % (ret,)) finally: diff --git a/msgpack/unpack.h b/msgpack/unpack.h index 55cfdab..eb4330a 100644 --- a/msgpack/unpack.h +++ b/msgpack/unpack.h @@ -40,11 +40,6 @@ struct unpack_context; typedef struct unpack_context unpack_context; typedef int (*execute_fn)(unpack_context *ctx, const char* data, Py_ssize_t len, Py_ssize_t* off); -static inline msgpack_unpack_object unpack_callback_root(unpack_user* u) -{ - return NULL; -} - static inline int unpack_callback_uint16(unpack_user* u, uint16_t d, msgpack_unpack_object* o) { PyObject *p = PyLong_FromLong((long)d); diff --git a/msgpack/unpack_template.h b/msgpack/unpack_template.h index ab5887a..797a2f9 100644 --- a/msgpack/unpack_template.h +++ b/msgpack/unpack_template.h @@ -35,11 +35,6 @@ struct unpack_context { unsigned int cs; unsigned int trail; unsigned int top; - /* - unpack_stack* stack; - unsigned int stack_size; - unpack_stack embed_stack[MSGPACK_EMBED_STACK_SIZE]; - */ unpack_stack stack[MSGPACK_EMBED_STACK_SIZE]; }; @@ -49,22 +44,9 @@ static inline void unpack_init(unpack_context* ctx) ctx->cs = CS_HEADER; ctx->trail = 0; ctx->top = 0; - /* - ctx->stack = ctx->embed_stack; - ctx->stack_size = MSGPACK_EMBED_STACK_SIZE; - */ - ctx->stack[0].obj = unpack_callback_root(&ctx->user); + ctx->stack[0].obj = NULL; } -/* -static inline void unpack_destroy(unpack_context* ctx) -{ - if(ctx->stack_size != MSGPACK_EMBED_STACK_SIZE) { - free(ctx->stack); - } -} -*/ - static inline PyObject* unpack_data(unpack_context* ctx) { return (ctx)->stack[0].obj; @@ -94,9 +76,6 @@ static inline int unpack_execute(bool construct, unpack_context* ctx, const char unsigned int cs = ctx->cs; unsigned int top = ctx->top; unpack_stack* stack = ctx->stack; - /* - unsigned int stack_size = ctx->stack_size; - */ unpack_user* user = &ctx->user; PyObject* obj = NULL; @@ -319,6 +298,7 @@ static inline int unpack_execute(bool construct, unpack_context* ctx, const char start_container(_map, _msgpack_load32(uint32_t,n), CT_MAP_KEY); default: + PyErr_Format(PyExc_RuntimeError, "Invalid state: %d", cs); goto _failed; } } @@ -355,6 +335,7 @@ _push: goto _header_again; default: + PyErr_Format(PyExc_RuntimeError, "Invalid container type: %u", c->ct); goto _failed; } From 2de627311fb17b1a942d052447c3777cd58b162d Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 24 Jun 2026 18:17:22 +0900 Subject: [PATCH 13/13] test_unpack.py: add tests for ExtraData (#708) --- test/test_unpack.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/test/test_unpack.py b/test/test_unpack.py index 81bc976..ae6ea10 100644 --- a/test/test_unpack.py +++ b/test/test_unpack.py @@ -5,7 +5,15 @@ from io import BytesIO from pytest import mark, raises -from msgpack import ExtType, OutOfData, Unpacker, packb +from msgpack import ( + ExtraData, + ExtType, + OutOfData, + Unpacker, + packb, + unpack, + unpackb, +) def test_unpack_array_header_from_file(): @@ -142,3 +150,20 @@ def test_unpacker_reentrant_feed(): up.feed(b"\xdc" + struct.pack(">H", 11) + b"\xd4\x05A" + b"\x2a" * 10) with raises(RuntimeError): up.unpack() + + +def test_unpackb_raises_extra_data_with_trailing_bytes(): + packed = packb(42) + packb("trailing") + with raises(ExtraData) as exc_info: + unpackb(packed) + err = exc_info.value + assert err.unpacked == 42 + assert err.extra == packb("trailing") + + +def test_unpack_raises_extra_data_on_stream_with_trailing_bytes(): + stream = BytesIO(packb(100) + packb(200)) + with raises(ExtraData) as exc_info: + unpack(stream) + assert exc_info.value.unpacked == 100 + assert exc_info.value.extra == packb(200)