From 4444065465bd4840150d38918369bbda6d0de43a Mon Sep 17 00:00:00 2001 From: Shaisolaris <66793913+Shaisolaris@users.noreply.github.com> Date: Sun, 11 Oct 2026 23:58:52 -0700 Subject: [PATCH] fallback: leave the object unread when a header type does not match read_array_header() and read_map_header() in the pure Python Unpacker consumed the object (header and, for str/bin/ext, its payload) before raising ValueError, so the next call started past it or in the middle of a container. The C extension leaves the object in place. Roll back to the start of the object, as the C extension does. --- msgpack/fallback.py | 3 +++ test/test_read_size.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/msgpack/fallback.py b/msgpack/fallback.py index cee48e9a..2b2aaa17 100644 --- a/msgpack/fallback.py +++ b/msgpack/fallback.py @@ -501,10 +501,13 @@ def _unpack(self, execute=EX_CONSTRUCT): if execute == EX_READ_ARRAY_HEADER: if typ != TYPE_ARRAY: + # Leave the object unread, as the C extension does. + self._buff_i = self._buf_checkpoint raise ValueError("Expected array") return n if execute == EX_READ_MAP_HEADER: if typ != TYPE_MAP: + self._buff_i = self._buf_checkpoint raise ValueError("Expected map") return n # TODO should we eliminate the recursion? diff --git a/test/test_read_size.py b/test/test_read_size.py index 0f6c1b50..d696ad6a 100644 --- a/test/test_read_size.py +++ b/test/test_read_size.py @@ -1,5 +1,9 @@ """Test Unpacker's read_array_header and read_map_header methods""" +from io import BytesIO + +from pytest import raises + from msgpack import OutOfData, Unpacker, packb UnexpectedTypeException = ValueError @@ -70,3 +74,31 @@ def test_incorrect_type_nested_map(): assert 0, "should raise exception" except UnexpectedTypeException: assert 1, "okay" + + +def test_incorrect_type_does_not_consume_object(): + packed = packb(1) + packb("abc") + packb({"a": 1}) + packb([2]) + unpackers = [Unpacker(), Unpacker(BytesIO(packed))] + unpackers[0].feed(packed) + for unpacker in unpackers: + with raises(UnexpectedTypeException): + unpacker.read_array_header() + assert unpacker.tell() == 0 + assert unpacker.unpack() == 1 + + with raises(UnexpectedTypeException): + unpacker.read_map_header() + assert unpacker.unpack() == "abc" + + # A map is not an array: the stream must stay in sync. + with raises(UnexpectedTypeException): + unpacker.read_array_header() + assert unpacker.read_map_header() == 1 + assert unpacker.unpack() == "a" + assert unpacker.unpack() == 1 + + with raises(UnexpectedTypeException): + unpacker.read_map_header() + assert unpacker.read_array_header() == 1 + assert unpacker.unpack() == 2 + assert unpacker.tell() == len(packed)