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-python-0.4.6/msgpack/_unpacker.pyx
===================================================================
--- msgpack-python-0.4.6.orig/msgpack/_unpacker.pyx
+++ msgpack-python-0.4.6/msgpack/_unpacker.pyx
@@ -45,6 +45,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)
 
@@ -395,7 +396,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
@@ -405,6 +406,7 @@ cdef class Unpacker(object):
                     raise OutOfData("No more data to unpack.")
             else:
                 raise ValueError("Unpack failed: error = %d" % (ret,))
+            unpack_clear(&self.ctx)
 
     def read_bytes(self, Py_ssize_t nbytes):
         """Read a specified number of raw bytes from the stream"""
Index: msgpack-python-0.4.6/msgpack/unpack_template.h
===================================================================
--- msgpack-python-0.4.6.orig/msgpack/unpack_template.h
+++ msgpack-python-0.4.6/msgpack/unpack_template.h
@@ -56,6 +56,12 @@ static inline void unpack_init(unpack_co
     ctx->stack[0].obj = unpack_callback_root(&ctx->user);
 }
 
+static inline void unpack_clear(unpack_context *ctx)
+{
+    Py_CLEAR(ctx->stack[0].obj);
+    unpack_init(ctx);
+}
+
 /*
 static inline void unpack_destroy(unpack_context* ctx)
 {
@@ -209,7 +215,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
@@ -347,6 +353,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; }
@@ -409,10 +416,18 @@ _end:
 #undef start_container
 
 static int unpack_construct(unpack_context *ctx, const char *data, size_t len, size_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, size_t len, size_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-python-0.4.6/test/test_except.py
===================================================================
--- msgpack-python-0.4.6.orig/test/test_except.py
+++ msgpack-python-0.4.6/test/test_except.py
@@ -2,7 +2,7 @@
 # coding: utf-8
 
 from pytest import raises
-from msgpack import packb, unpackb
+from msgpack import packb, unpackb, Unpacker
 
 import datetime
 
@@ -25,6 +25,22 @@ def test_raise_from_object_hook():
     raises(DummyException, unpackb, packb({'fizz': {'buzz': 'spam'}}), object_hook=hook)
     raises(DummyException, unpackb, packb({'fizz': {'buzz': 'spam'}}), 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():
     with raises(ValueError):
