Compare commits

..

6 commits

Author SHA1 Message Date
Inada Naoki
448d43f5dc
release v1.2.1 (#698)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-19 00:33:29 +09:00
Inada Naoki
2c56ddb5d0
Merge commit from fork
* fix Unpacker crash after unpack failure.

* fixup
2026-06-19 00:13:13 +09:00
dependabot[bot]
0f4f350b6f
Bump pypa/cibuildwheel from 4.0.0 to 4.1.0 in the all-dependencies group (#694) 2026-06-16 13:22:58 +09:00
Inada Naoki
11ed0a5110
release v1.2.0 (#692) 2026-06-11 12:29:41 +09:00
dependabot[bot]
c410a388c5
Bump pypa/cibuildwheel from 3.4.1 to 4.0.0 (#691) 2026-06-11 11:54:05 +09:00
Inada Naoki
97ba6ca0d2 skip ci: remove unneeded CIBW_SKIP option 2026-06-03 22:17:26 +09:00
8 changed files with 68 additions and 16 deletions

View file

@ -44,11 +44,11 @@ jobs:
platforms: ${{ matrix.cibw_archs }}
- name: Build
uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
uses: pypa/cibuildwheel@294735312765b09d24a2fbec22660ce817587d55 # v4.1.0
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

View file

@ -1,6 +1,15 @@
# 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: TBD
Release Date: 2026-06-11
- Support free threaded Python. #654, #686
- Dropped support for Python 3.9. #656

View file

@ -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.

View file

@ -4,8 +4,8 @@ import os
from .exceptions import * # noqa: F403
from .ext import ExtType, Timestamp
version = (1, 2, 0)
__version__ = "1.2.0rc1"
version = (1, 2, 1)
__version__ = "1.2.1"
if os.environ.get("MSGPACK_PUREPYTHON"):

View file

@ -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

View file

@ -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

View file

@ -1,3 +1,4 @@
Cython==3.2.1
cython==3.2.5
setuptools==78.1.1
pytest
build

View file

@ -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)