Skip to content

Commit 620b0eb

Browse files
authored
Merge commit from fork
1 parent 475c1b7 commit 620b0eb

4 files changed

Lines changed: 101 additions & 62 deletions

File tree

‎msgpack/_unpacker.pyx‎

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,22 @@ cdef extern from "unpack.h":
4040
Py_ssize_t max_map_len
4141
Py_ssize_t max_ext_len
4242

43+
ctypedef struct unpack_context
44+
ctypedef int (*execute_fn)(unpack_context* ctx, const char* data,
45+
Py_ssize_t len, Py_ssize_t* off) except -1
46+
4347
ctypedef struct unpack_context:
4448
msgpack_user user
45-
PyObject* obj
46-
Py_ssize_t count
49+
execute_fn execute
4750

48-
ctypedef int (*execute_fn)(unpack_context* ctx, const char* data,
49-
Py_ssize_t len, Py_ssize_t* off) except -1
5051
execute_fn unpack_construct
5152
execute_fn unpack_skip
5253
execute_fn read_array_header
5354
execute_fn read_map_header
5455

5556
void unpack_init(unpack_context* ctx)
56-
object unpack_data(unpack_context* ctx)
57+
# Transfers the result's owned reference to Cython and clears its slot.
58+
object unpack_take_result(unpack_context* ctx)
5759
void unpack_clear(unpack_context* ctx)
5860

5961
cdef inline init_ctx(unpack_context *ctx,
@@ -191,16 +193,16 @@ def unpackb(object packed, *, object object_hook=None, object list_hook=None,
191193
max_str_len, max_bin_len, max_array_len, max_map_len, max_ext_len)
192194
ret = unpack_construct(&ctx, buf, buf_len, &off)
193195
if ret == 1:
194-
obj = unpack_data(&ctx)
196+
obj = unpack_take_result(&ctx)
195197
if off < buf_len:
196198
# buf may point into a temporary contiguous copy owned by view,
197199
# so the extra data must be copied out before releasing view.
198200
raise ExtraData(obj, PyBytes_FromStringAndSize(buf+off, buf_len-off))
199201
return obj
200202
finally:
203+
unpack_clear(&ctx)
201204
PyBuffer_Release(&view);
202205

203-
unpack_clear(&ctx)
204206
if ret == 0:
205207
raise ValueError("Unpack failed: incomplete input")
206208
elif ret == -2:
@@ -214,6 +216,11 @@ def unpackb(object packed, *, object object_hook=None, object list_hook=None,
214216
cdef class Unpacker:
215217
"""Streaming unpacker.
216218
219+
If an operation needs more data, resume it with the same method after
220+
feeding more bytes. ``unpack()`` and iteration may be used interchangeably.
221+
Switching between unpacking, skipping, header readers, or ``read_bytes()``
222+
while an object is incomplete raises ``ValueError`` in the C extension.
223+
217224
Arguments:
218225
219226
:param file_like:
@@ -339,6 +346,9 @@ cdef class Unpacker:
339346
Py_ssize_t max_ext_len=-1):
340347
cdef const char *cerr=NULL
341348

349+
if self._unpacking:
350+
raise RuntimeError("Unpacker.__init__() cannot be called while unpacking is in progress")
351+
342352
unpack_clear(&self.ctx)
343353
unpack_init(&self.ctx)
344354
if self.buf != NULL:
@@ -473,18 +483,29 @@ cdef class Unpacker:
473483
cdef object obj
474484
cdef Py_ssize_t prev_head
475485

486+
if self._unpacking:
487+
raise RuntimeError("Unpacker cannot be called recursively while unpacking is in progress")
488+
if self.ctx.execute != NULL and self.ctx.execute != execute:
489+
raise ValueError("Cannot switch unpacking methods while an object is incomplete")
490+
476491
self._unpacking = True
477492
try:
478493
while 1:
479494
prev_head = self.buf_head
480495
if prev_head < self.buf_tail:
481-
ret = execute(&self.ctx, self.buf, self.buf_tail, &self.buf_head)
482-
self.stream_offset += self.buf_head - prev_head
496+
self.ctx.execute = execute
497+
try:
498+
ret = execute(&self.ctx, self.buf, self.buf_tail, &self.buf_head)
499+
except:
500+
unpack_clear(&self.ctx)
501+
raise
502+
finally:
503+
self.stream_offset += self.buf_head - prev_head
483504
else:
484505
ret = 0
485506

486507
if ret == 1:
487-
obj = unpack_data(&self.ctx)
508+
obj = unpack_take_result(&self.ctx)
488509
unpack_init(&self.ctx)
489510
return obj
490511
if ret == 0:
@@ -510,6 +531,10 @@ cdef class Unpacker:
510531
def read_bytes(self, Py_ssize_t nbytes):
511532
"""Read a specified number of raw bytes from the stream"""
512533
cdef Py_ssize_t nread
534+
if self._unpacking:
535+
raise RuntimeError("Unpacker.read_bytes() cannot be called while unpacking is in progress")
536+
if self.ctx.execute != NULL:
537+
raise ValueError("Cannot switch unpacking methods while an object is incomplete")
513538
nread = min(self.buf_tail - self.buf_head, nbytes)
514539
ret = PyBytes_FromStringAndSize(self.buf + self.buf_head, nread)
515540
self.buf_head += nread

‎msgpack/unpack.h‎

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@ typedef struct unpack_user {
3535
Py_ssize_t max_str_len, max_bin_len, max_array_len, max_map_len, max_ext_len;
3636
} unpack_user;
3737

38-
typedef PyObject* msgpack_unpack_object;
38+
typedef PyObject* msgpack_unpack_object; //TODO: remove typedef and use PyObject* directly
3939
struct unpack_context;
4040
typedef struct unpack_context unpack_context;
4141
typedef int (*execute_fn)(unpack_context *ctx, const char* data, Py_ssize_t len, Py_ssize_t* off);
4242

43+
/* Constructors receive a NULL output slot and store an owned reference on
44+
* success. On failure the slot remains NULL. */
4345
static inline int unpack_callback_uint16(unpack_user* u, uint16_t d, msgpack_unpack_object* o)
4446
{
4547
PyObject *p = PyLong_FromLong((long)d);
@@ -137,6 +139,7 @@ static inline int unpack_callback_array(unpack_user* u, unsigned int n, msgpack_
137139
return 0;
138140
}
139141

142+
/* Consumes o on both success and failure; borrows the container. */
140143
static inline int unpack_callback_array_item(unpack_user* u, unsigned int current, msgpack_unpack_object* c, msgpack_unpack_object o)
141144
{
142145
if (u->use_list)
@@ -146,14 +149,16 @@ static inline int unpack_callback_array_item(unpack_user* u, unsigned int curren
146149
return 0;
147150
}
148151

152+
/* Replaces the owned container reference with the hook result, or NULL on
153+
* failure. The original reference is consumed in either case. */
149154
static inline int unpack_callback_array_end(unpack_user* u, msgpack_unpack_object* c)
150155
{
151156
if (u->list_hook) {
152157
PyObject *new_c = PyObject_CallFunctionObjArgs(u->list_hook, *c, NULL);
153-
if (!new_c)
154-
return -1;
155158
Py_DECREF(*c);
156159
*c = new_c;
160+
if (!new_c)
161+
return -1;
157162
}
158163
return 0;
159164
}
@@ -177,41 +182,42 @@ static inline int unpack_callback_map(unpack_user* u, unsigned int n, msgpack_un
177182
return 0;
178183
}
179184

185+
/* Consumes k and v on both success and failure; borrows the container. */
180186
static inline int unpack_callback_map_item(unpack_user* u, unsigned int current, msgpack_unpack_object* c, msgpack_unpack_object k, msgpack_unpack_object v)
181187
{
182188
if (u->strict_map_key && !PyUnicode_CheckExact(k) && !PyBytes_CheckExact(k)) {
183189
PyErr_Format(PyExc_ValueError, "%.100s is not allowed for map key when strict_map_key=True", Py_TYPE(k)->tp_name);
190+
Py_DECREF(k);
191+
Py_DECREF(v);
184192
return -1;
185193
}
186194
if (PyUnicode_CheckExact(k)) {
187195
PyUnicode_InternInPlace(&k);
188196
}
189197
if (u->has_pairs_hook) {
190198
msgpack_unpack_object item = PyTuple_Pack(2, k, v);
191-
if (!item)
192-
return -1;
193199
Py_DECREF(k);
194200
Py_DECREF(v);
201+
if (!item)
202+
return -1;
195203
PyList_SET_ITEM(*c, current, item);
196204
return 0;
197205
}
198-
else if (PyDict_SetItem(*c, k, v) == 0) {
199-
Py_DECREF(k);
200-
Py_DECREF(v);
201-
return 0;
202-
}
203-
return -1;
206+
int ret = PyDict_SetItem(*c, k, v);
207+
Py_DECREF(k);
208+
Py_DECREF(v);
209+
return ret;
204210
}
205211

212+
/* Same ownership contract as unpack_callback_array_end. */
206213
static inline int unpack_callback_map_end(unpack_user* u, msgpack_unpack_object* c)
207214
{
208215
if (u->object_hook) {
209216
PyObject *new_c = PyObject_CallFunctionObjArgs(u->object_hook, *c, NULL);
210-
if (!new_c)
211-
return -1;
212-
213217
Py_DECREF(*c);
214218
*c = new_c;
219+
if (!new_c)
220+
return -1;
215221
}
216222
return 0;
217223
}

‎msgpack/unpack_container_header.h‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,8 @@ static inline int unpack_container_header(unpack_context* ctx, const char* data,
4646
return -1;
4747
}
4848

49-
if (unpack_callback_uint32(&ctx->user, size, &ctx->stack[0].obj) < 0)
49+
if (unpack_callback_uint32(&ctx->user, size, &ctx->result) < 0)
5050
return -1;
5151

5252
return 1;
5353
}
54-

0 commit comments

Comments
 (0)