From 2c56ddb5d0025ed481d962c0f5d62d19dec7476d Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Fri, 19 Jun 2026 00:13:13 +0900
Subject: [PATCH] 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(-)

Index: msgpack-1.0.5/msgpack/_unpacker.pyx
===================================================================
--- msgpack-1.0.5.orig/msgpack/_unpacker.pyx
+++ msgpack-1.0.5/msgpack/_unpacker.pyx
@@ -53,6 +53,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)
@@ -200,6 +201,7 @@ def unpackb(object packed, *, object obj
         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")
@@ -469,7 +471,7 @@ cdef class Unpacker(object):
                 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
@@ -477,7 +479,9 @@ cdef class Unpacker(object):
                     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
Index: msgpack-1.0.5/msgpack/unpack_template.h
===================================================================
--- msgpack-1.0.5.orig/msgpack/unpack_template.h
+++ msgpack-1.0.5/msgpack/unpack_template.h
@@ -73,6 +73,7 @@ static inline PyObject* unpack_data(unpa
 static inline void unpack_clear(unpack_context *ctx)
 {
     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)
@@ -192,7 +193,7 @@ static inline int unpack_execute(bool co
                 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
@@ -336,6 +337,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; }
@@ -398,10 +400,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
Index: msgpack-1.0.5/test/test_except.py
===================================================================
--- msgpack-1.0.5.orig/test/test_except.py
+++ msgpack-1.0.5/test/test_except.py
@@ -31,6 +31,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_invalidvalue():
     incomplete = b"\xd9\x97#DL_"  # raw8 - length=0x97
@@ -61,3 +77,13 @@ def test_strict_map_key():
     packed = packb(invalid, use_bin_type=True)
     with raises(ValueError):
         unpackb(packed, raw=False, strict_map_key=True)
+
+
+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)
