From 8ea185273df2d713481d8fad3deec68a4a41fecd Mon Sep 17 00:00:00 2001 From: Davis C Date: Sat, 22 Mar 2025 13:11:14 -0500 Subject: [PATCH 1/7] Disable recursion test for now --- test/test.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/test/test.py b/test/test.py index c3920c4..829efce 100644 --- a/test/test.py +++ b/test/test.py @@ -463,24 +463,24 @@ def assert_raises_regex(self, *args, **kwargs): # pylint: disable=deprecated-method,no-member return (self.assertRaisesRegexp if PY2 else self.assertRaisesRegex)(*args, **kwargs) - def test_recursion(self): - old_limit = getrecursionlimit() - setrecursionlimit(200) - try: - obj = current = [] - for _ in range(getrecursionlimit() * 2): - new_list = [] - current.append(new_list) - current = new_list - - with self.assert_raises_regex(RuntimeError, 'recursion'): - self.ubjdumpb(obj) - - raw = ARRAY_START * (getrecursionlimit() * 2) - with self.assert_raises_regex(RuntimeError, 'recursion'): - self.ubjloadb(raw) - finally: - setrecursionlimit(old_limit) + #def test_recursion(self): + # old_limit = getrecursionlimit() + # setrecursionlimit(200) + # try: + # obj = current = [] + # for _ in range(getrecursionlimit() * 2): + # new_list = [] + # current.append(new_list) + # current = new_list + + # with self.assert_raises_regex(RuntimeError, 'recursion'): + # self.ubjdumpb(obj) + + # raw = ARRAY_START * (getrecursionlimit() * 2) + # with self.assert_raises_regex(RuntimeError, 'recursion'): + # self.ubjloadb(raw) + # finally: + # setrecursionlimit(old_limit) def test_encode_default(self): def default(obj): From 5eedfa6362fa76e3d19b5da3612d3b331b8a2b75 Mon Sep 17 00:00:00 2001 From: Davis C Date: Sat, 22 Mar 2025 13:51:25 -0500 Subject: [PATCH 2/7] errors=skip test case --- test/test.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/test.py b/test/test.py index 829efce..cf3e3a5 100644 --- a/test/test.py +++ b/test/test.py @@ -134,6 +134,10 @@ def test_char(self): for suffix in (b'', b'\xfe'): with self.assertRaises(DecoderException): self.ubjloadb(TYPE_CHAR + suffix) + # char invalid utf-8 + for suffix in (b'\xfe',): + encoded = self.ubjloadb(TYPE_CHAR + suffix, errors='replace') + self.assertEqual(encoded, suffix.decode('utf-8', errors='replace')) for char in (u('a'), u('\0'), u('~')): self.check_enc_dec(char, 2) @@ -144,6 +148,11 @@ def test_string(self): for suffix in (b'\x81', b'\x01', b'\x01' + b'\xfe'): with self.assertRaises(DecoderException): self.ubjloadb(TYPE_STRING + TYPE_INT8 + suffix) + # string invalid utf-8 + for length, suffix in ((b'\x01', b'\xfe'),): + suffix_d = suffix.decode('utf-8', errors='replace') + encoded = self.ubjloadb(TYPE_STRING + TYPE_INT8 + length + suffix, errors='replace') + self.assertEqual(encoded, suffix_d) # Note: In Python 2 plain str type is encoded as byte array for string in ('some ascii', u(r'\u00a9 with extended\u2122'), u('long string') * 100): self.check_enc_dec(string, 4, length_greater_or_equal=True) From 7b439c6a59f9ce9b545c4373d9bdcebf595daa77 Mon Sep 17 00:00:00 2001 From: Davis C Date: Sat, 22 Mar 2025 13:29:25 -0500 Subject: [PATCH 3/7] Added `errors` flag to Python --- ubjson/decoder.py | 96 ++++++++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 46 deletions(-) diff --git a/ubjson/decoder.py b/ubjson/decoder.py index 19e4cf5..d37e9ec 100644 --- a/ubjson/decoder.py +++ b/ubjson/decoder.py @@ -56,115 +56,119 @@ def position(self): return self.args[1] # pylint: disable=unsubscriptable-object +def __parse_utf8(raw, errors): + return raw.decode('utf-8', errors=errors) + + # pylint: disable=unused-argument -def __decode_high_prec(fp_read, marker): - length = __decode_int_non_negative(fp_read, fp_read(1)) +def __decode_high_prec(fp_read, marker, errors): + length = __decode_int_non_negative(fp_read, fp_read(1), errors) raw = fp_read(length) if len(raw) < length: raise DecoderException('High prec. too short') try: - return Decimal(raw.decode('utf-8')) + return Decimal(__parse_utf8(raw, errors)) except UnicodeError as ex: raise_from(DecoderException('Failed to decode decimal string'), ex) except DecimalException as ex: raise_from(DecoderException('Failed to decode decimal'), ex) -def __decode_int_non_negative(fp_read, marker): +def __decode_int_non_negative(fp_read, marker, errors): if marker not in __TYPES_INT: raise DecoderException('Integer marker expected') - value = __METHOD_MAP[marker](fp_read, marker) + value = __METHOD_MAP[marker](fp_read, marker, errors) if value < 0: raise DecoderException('Negative count/length unexpected') return value -def __decode_int8(fp_read, marker): +def __decode_int8(fp_read, marker, _errors): try: return __SMALL_INTS_DECODED[fp_read(1)] except KeyError as ex: raise_from(DecoderException('Failed to unpack int8'), ex) -def __decode_uint8(fp_read, marker): +def __decode_uint8(fp_read, marker, _errors): try: return __SMALL_UINTS_DECODED[fp_read(1)] except KeyError as ex: raise_from(DecoderException('Failed to unpack uint8'), ex) -def __decode_int16(fp_read, marker): +def __decode_int16(fp_read, marker, _errors): try: return __UNPACK_INT16(fp_read(2))[0] except StructError as ex: raise_from(DecoderException('Failed to unpack int16'), ex) -def __decode_int32(fp_read, marker): +def __decode_int32(fp_read, marker, _errors): try: return __UNPACK_INT32(fp_read(4))[0] except StructError as ex: raise_from(DecoderException('Failed to unpack int32'), ex) -def __decode_int64(fp_read, marker): +def __decode_int64(fp_read, marker, _errors): try: return __UNPACK_INT64(fp_read(8))[0] except StructError as ex: raise_from(DecoderException('Failed to unpack int64'), ex) -def __decode_float32(fp_read, marker): +def __decode_float32(fp_read, marker, _errors): try: return __UNPACK_FLOAT32(fp_read(4))[0] except StructError as ex: raise_from(DecoderException('Failed to unpack float32'), ex) -def __decode_float64(fp_read, marker): +def __decode_float64(fp_read, marker, _errors): try: return __UNPACK_FLOAT64(fp_read(8))[0] except StructError as ex: raise_from(DecoderException('Failed to unpack float64'), ex) -def __decode_char(fp_read, marker): +def __decode_char(fp_read, marker, errors): raw = fp_read(1) if not raw: raise DecoderException('Char missing') try: - return raw.decode('utf-8') + return __parse_utf8(raw, errors) except UnicodeError as ex: raise_from(DecoderException('Failed to decode char'), ex) -def __decode_string(fp_read, marker): +def __decode_string(fp_read, marker, errors): # current marker is string identifier, so read next byte which identifies integer type - length = __decode_int_non_negative(fp_read, fp_read(1)) + length = __decode_int_non_negative(fp_read, fp_read(1), errors) raw = fp_read(length) if len(raw) < length: raise DecoderException('String too short') try: - return raw.decode('utf-8') + return __parse_utf8(raw, errors) except UnicodeError as ex: raise_from(DecoderException('Failed to decode string'), ex) # same as string, except there is no 'S' marker -def __decode_object_key(fp_read, marker, intern_object_keys): - length = __decode_int_non_negative(fp_read, marker) +def __decode_object_key(fp_read, marker, intern_object_keys, errors): + length = __decode_int_non_negative(fp_read, marker, errors) raw = fp_read(length) if len(raw) < length: raise DecoderException('String too short') try: - return intern_unicode(raw.decode('utf-8')) if intern_object_keys else raw.decode('utf-8') + return intern_unicode(__parse_utf8(raw, errors)) if intern_object_keys else __parse_utf8(raw, errors) except UnicodeError as ex: raise_from(DecoderException('Failed to decode object key'), ex) -__METHOD_MAP = {TYPE_NULL: (lambda _, __: None), - TYPE_BOOL_TRUE: (lambda _, __: True), - TYPE_BOOL_FALSE: (lambda _, __: False), +__METHOD_MAP = {TYPE_NULL: (lambda _, __, ___: None), + TYPE_BOOL_TRUE: (lambda _, __, ___: True), + TYPE_BOOL_FALSE: (lambda _, __, ___: False), TYPE_INT8: __decode_int8, TYPE_UINT8: __decode_uint8, TYPE_INT16: __decode_int16, @@ -177,7 +181,7 @@ def __decode_object_key(fp_read, marker, intern_object_keys): TYPE_STRING: __decode_string} -def __get_container_params(fp_read, in_mapping, no_bytes): +def __get_container_params(fp_read, in_mapping, no_bytes, errors): marker = fp_read(1) if marker == CONTAINER_TYPE: marker = fp_read(1) @@ -188,7 +192,7 @@ def __get_container_params(fp_read, in_mapping, no_bytes): else: type_ = TYPE_NONE if marker == CONTAINER_COUNT: - count = __decode_int_non_negative(fp_read, fp_read(1)) + count = __decode_int_non_negative(fp_read, fp_read(1), errors) counting = True # special cases (no data (None or bool) / bytes array) will be handled in calling functions @@ -207,21 +211,21 @@ def __get_container_params(fp_read, in_mapping, no_bytes): def __decode_object(fp_read, no_bytes, object_hook, object_pairs_hook, # pylint: disable=too-many-branches - intern_object_keys): - marker, counting, count, type_ = __get_container_params(fp_read, True, no_bytes) + intern_object_keys, errors): + marker, counting, count, type_ = __get_container_params(fp_read, True, no_bytes, errors) has_pairs_hook = object_pairs_hook is not None obj = [] if has_pairs_hook else {} # special case - no data (None or bool) if type_ in __TYPES_NO_DATA: - value = __METHOD_MAP[type_](fp_read, type_) + value = __METHOD_MAP[type_](fp_read, type_, errors) if has_pairs_hook: for _ in range(count): - obj.append((__decode_object_key(fp_read, fp_read(1), intern_object_keys), value)) + obj.append((__decode_object_key(fp_read, fp_read(1), intern_object_keys, errors), value)) return object_pairs_hook(obj) for _ in range(count): - obj[__decode_object_key(fp_read, fp_read(1), intern_object_keys)] = value + obj[__decode_object_key(fp_read, fp_read(1), intern_object_keys, errors)] = value return object_hook(obj) while count > 0 and (counting or marker != OBJECT_END): @@ -230,12 +234,12 @@ def __decode_object(fp_read, no_bytes, object_hook, object_pairs_hook, # pylint continue # decode key for object - key = __decode_object_key(fp_read, marker, intern_object_keys) + key = __decode_object_key(fp_read, marker, intern_object_keys, errors) marker = fp_read(1) if type_ == TYPE_NONE else type_ # decode value try: - value = __METHOD_MAP[marker](fp_read, marker) + value = __METHOD_MAP[marker](fp_read, marker, errors) except KeyError: handled = False else: @@ -244,9 +248,9 @@ def __decode_object(fp_read, no_bytes, object_hook, object_pairs_hook, # pylint # handle outside above except (on KeyError) so do not have unfriendly "exception within except" backtrace if not handled: if marker == ARRAY_START: - value = __decode_array(fp_read, no_bytes, object_hook, object_pairs_hook, intern_object_keys) + value = __decode_array(fp_read, no_bytes, object_hook, object_pairs_hook, intern_object_keys, errors) elif marker == OBJECT_START: - value = __decode_object(fp_read, no_bytes, object_hook, object_pairs_hook, intern_object_keys) + value = __decode_object(fp_read, no_bytes, object_hook, object_pairs_hook, intern_object_keys, errors) else: raise DecoderException('Invalid marker within object') @@ -262,12 +266,12 @@ def __decode_object(fp_read, no_bytes, object_hook, object_pairs_hook, # pylint return object_pairs_hook(obj) if has_pairs_hook else object_hook(obj) -def __decode_array(fp_read, no_bytes, object_hook, object_pairs_hook, intern_object_keys): - marker, counting, count, type_ = __get_container_params(fp_read, False, no_bytes) +def __decode_array(fp_read, no_bytes, object_hook, object_pairs_hook, intern_object_keys, errors): + marker, counting, count, type_ = __get_container_params(fp_read, False, no_bytes, errors) # special case - no data (None or bool) if type_ in __TYPES_NO_DATA: - return [__METHOD_MAP[type_](fp_read, type_)] * count + return [__METHOD_MAP[type_](fp_read, type_, errors)] * count # special case - bytes array if type_ == TYPE_UINT8 and not no_bytes: @@ -284,7 +288,7 @@ def __decode_array(fp_read, no_bytes, object_hook, object_pairs_hook, intern_obj # decode value try: - value = __METHOD_MAP[marker](fp_read, marker) + value = __METHOD_MAP[marker](fp_read, marker, errors) except KeyError: handled = False else: @@ -293,9 +297,9 @@ def __decode_array(fp_read, no_bytes, object_hook, object_pairs_hook, intern_obj # handle outside above except (on KeyError) so do not have unfriendly "exception within except" backtrace if not handled: if marker == ARRAY_START: - value = __decode_array(fp_read, no_bytes, object_hook, object_pairs_hook, intern_object_keys) + value = __decode_array(fp_read, no_bytes, object_hook, object_pairs_hook, intern_object_keys, errors) elif marker == OBJECT_START: - value = __decode_object(fp_read, no_bytes, object_hook, object_pairs_hook, intern_object_keys) + value = __decode_object(fp_read, no_bytes, object_hook, object_pairs_hook, intern_object_keys, errors) else: raise DecoderException('Invalid marker within array') @@ -312,7 +316,7 @@ def __object_hook_noop(obj): return obj -def load(fp, no_bytes=False, object_hook=None, object_pairs_hook=None, intern_object_keys=False): +def load(fp, no_bytes=False, object_hook=None, object_pairs_hook=None, intern_object_keys=False, errors='strict'): """Decodes and returns UBJSON from the given file-like object Args: @@ -378,21 +382,21 @@ def load(fp, no_bytes=False, object_hook=None, object_pairs_hook=None, intern_ob marker = fp_read(1) try: try: - return __METHOD_MAP[marker](fp_read, marker) + return __METHOD_MAP[marker](fp_read, marker, errors) except KeyError: pass if marker == ARRAY_START: - return __decode_array(fp_read, bool(no_bytes), object_hook, object_pairs_hook, intern_object_keys) + return __decode_array(fp_read, bool(no_bytes), object_hook, object_pairs_hook, intern_object_keys, errors) if marker == OBJECT_START: - return __decode_object(fp_read, bool(no_bytes), object_hook, object_pairs_hook, intern_object_keys) + return __decode_object(fp_read, bool(no_bytes), object_hook, object_pairs_hook, intern_object_keys, errors) raise DecoderException('Invalid marker') except DecoderException as ex: raise_from(DecoderException(ex.args[0], position=(fp.tell() if hasattr(fp, 'tell') else None)), ex) -def loadb(chars, no_bytes=False, object_hook=None, object_pairs_hook=None, intern_object_keys=False): +def loadb(chars, no_bytes=False, object_hook=None, object_pairs_hook=None, intern_object_keys=False, errors='strict'): """Decodes and returns UBJSON from the given bytes or bytesarray object. See load() for available arguments.""" with BytesIO(chars) as fp: return load(fp, no_bytes=no_bytes, object_hook=object_hook, object_pairs_hook=object_pairs_hook, - intern_object_keys=intern_object_keys) + intern_object_keys=intern_object_keys, errors=errors) From 5f618693975ed3620dc4cce77ad420af9ceec557 Mon Sep 17 00:00:00 2001 From: Davis C Date: Sat, 22 Mar 2025 13:39:41 -0500 Subject: [PATCH 4/7] Added errors to C decode params --- src/_ubjson.c | 14 +++++++------- src/decoder.h | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/_ubjson.c b/src/_ubjson.c index cedbdd4..3cfb414 100644 --- a/src/_ubjson.c +++ b/src/_ubjson.c @@ -26,7 +26,7 @@ static _ubjson_encoder_prefs_t _ubjson_encoder_prefs_defaults = { NULL, 0, 0, 1 }; // no_bytes, object_pairs_hook -static _ubjson_decoder_prefs_t _ubjson_decoder_prefs_defaults = { NULL, NULL, 0, 0 }; +static _ubjson_decoder_prefs_t _ubjson_decoder_prefs_defaults = { NULL, NULL, 0, 0, NULL }; /******************************************************************************/ @@ -98,8 +98,8 @@ PyDoc_STRVAR(_ubjson_load__doc__, "See pure Python version (encoder.load) for do #define FUNC_DEF_LOAD {"load", (PyCFunction)_ubjson_load, METH_VARARGS | METH_KEYWORDS, _ubjson_load__doc__} static PyObject* _ubjson_load(PyObject *self, PyObject *args, PyObject *kwargs) { - static const char *format = "O|iOOi:load"; - static char *keywords[] = {"fp", "no_bytes", "object_hook", "object_pairs_hook", "intern_object_keys", NULL}; + static const char *format = "O|iOOiz:load"; + static char *keywords[] = {"fp", "no_bytes", "object_hook", "object_pairs_hook", "intern_object_keys", "errors", NULL}; _ubjson_decoder_buffer_t *buffer = NULL; _ubjson_decoder_prefs_t prefs = _ubjson_decoder_prefs_defaults; @@ -111,7 +111,7 @@ _ubjson_load(PyObject *self, PyObject *args, PyObject *kwargs) { UNUSED(self); if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords, &fp, &prefs.no_bytes, &prefs.object_hook, - &prefs.object_pairs_hook, &prefs.intern_object_keys)) { + &prefs.object_pairs_hook, &prefs.intern_object_keys, &prefs.errors)) { goto bail; } @@ -153,8 +153,8 @@ PyDoc_STRVAR(_ubjson_loadb__doc__, "See pure Python version (encoder.loadb) for #define FUNC_DEF_LOADB {"loadb", (PyCFunction)_ubjson_loadb, METH_VARARGS | METH_KEYWORDS, _ubjson_loadb__doc__} static PyObject* _ubjson_loadb(PyObject *self, PyObject *args, PyObject *kwargs) { - static const char *format = "O|iOOi:loadb"; - static char *keywords[] = {"chars", "no_bytes", "object_hook", "object_pairs_hook", "intern_object_keys", NULL}; + static const char *format = "O|iOOiz:loadb"; + static char *keywords[] = {"chars", "no_bytes", "object_hook", "object_pairs_hook", "intern_object_keys", "errors", NULL}; _ubjson_decoder_buffer_t *buffer = NULL; _ubjson_decoder_prefs_t prefs = _ubjson_decoder_prefs_defaults; @@ -163,7 +163,7 @@ _ubjson_loadb(PyObject *self, PyObject *args, PyObject *kwargs) { UNUSED(self); if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords, &chars, &prefs.no_bytes, &prefs.object_hook, - &prefs.object_pairs_hook, &prefs.intern_object_keys)) { + &prefs.object_pairs_hook, &prefs.intern_object_keys, &prefs.errors)) { goto bail; } if (PyUnicode_Check(chars)) { diff --git a/src/decoder.h b/src/decoder.h index 656c595..58f6a7a 100644 --- a/src/decoder.h +++ b/src/decoder.h @@ -30,6 +30,7 @@ typedef struct { // don't convert UINT8 arrays to bytes instances (and keep as an array of individual integers) int no_bytes; int intern_object_keys; + char *errors; } _ubjson_decoder_prefs_t; typedef struct _ubjson_decoder_buffer_t { From ac9713b720173cc78882086d2ddff31d2df10183 Mon Sep 17 00:00:00 2001 From: Davis C Date: Sat, 22 Mar 2025 14:07:29 -0500 Subject: [PATCH 5/7] Switch `DECODE_UNICODE_OR_BAIL` to use `DecodeUTF8` --- src/decoder.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/decoder.c b/src/decoder.c index 43d3a92..1af572a 100644 --- a/src/decoder.c +++ b/src/decoder.c @@ -85,8 +85,8 @@ dst_char = tmp[0];\ } -#define DECODE_UNICODE_OR_BAIL(dst_obj, raw, length, item_str) {\ - if (NULL == ((dst_obj) = PyUnicode_FromStringAndSize(raw, length))) {\ +#define DECODE_UNICODE_OR_BAIL(dst_obj, raw, length, item_str, errors) {\ + if (NULL == ((dst_obj) = PyUnicode_DecodeUTF8(raw, length, errors))) {\ RAISE_DECODER_EXCEPTION(("Failed to decode utf8: " item_str));\ }\ }\ @@ -567,7 +567,7 @@ static PyObject* _decode_high_prec(_ubjson_decoder_buffer_t *buffer) { DECODE_LENGTH_OR_BAIL(length); READ_OR_BAIL((Py_ssize_t)length, raw, "highprec"); - DECODE_UNICODE_OR_BAIL(num_str, raw, (Py_ssize_t)length, "highprec"); + DECODE_UNICODE_OR_BAIL(num_str, raw, (Py_ssize_t)length, "highprec", buffer->prefs.errors); BAIL_ON_NULL(decimal = PyObject_CallFunctionObjArgs((PyObject*)PyDec_Type, num_str, NULL)); Py_XDECREF(num_str); @@ -583,7 +583,7 @@ static PyObject* _decode_char(_ubjson_decoder_buffer_t *buffer) { PyObject *obj = NULL; READ_CHAR_OR_BAIL(value, "char"); - DECODE_UNICODE_OR_BAIL(obj, &value, 1, "char"); + DECODE_UNICODE_OR_BAIL(obj, &value, 1, "char", buffer->prefs.errors); return obj; bail: @@ -600,7 +600,7 @@ static PyObject* _decode_string(_ubjson_decoder_buffer_t *buffer) { if (length > 0) { READ_OR_BAIL((Py_ssize_t)length, raw, "string"); - DECODE_UNICODE_OR_BAIL(obj, raw, (Py_ssize_t)length, "string"); + DECODE_UNICODE_OR_BAIL(obj, raw, (Py_ssize_t)length, "string", buffer->prefs.errors); } else { BAIL_ON_NULL(obj = PyUnicode_FromStringAndSize(NULL, 0)); } From ac30555a38221a2f15e820ec87f57aa09b6682f9 Mon Sep 17 00:00:00 2001 From: Davis C Date: Sat, 22 Mar 2025 14:12:19 -0500 Subject: [PATCH 6/7] Revert "Disable recursion test for now" This reverts commit 8ea185273df2d713481d8fad3deec68a4a41fecd. --- test/test.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/test/test.py b/test/test.py index cf3e3a5..56f0607 100644 --- a/test/test.py +++ b/test/test.py @@ -472,24 +472,24 @@ def assert_raises_regex(self, *args, **kwargs): # pylint: disable=deprecated-method,no-member return (self.assertRaisesRegexp if PY2 else self.assertRaisesRegex)(*args, **kwargs) - #def test_recursion(self): - # old_limit = getrecursionlimit() - # setrecursionlimit(200) - # try: - # obj = current = [] - # for _ in range(getrecursionlimit() * 2): - # new_list = [] - # current.append(new_list) - # current = new_list - - # with self.assert_raises_regex(RuntimeError, 'recursion'): - # self.ubjdumpb(obj) - - # raw = ARRAY_START * (getrecursionlimit() * 2) - # with self.assert_raises_regex(RuntimeError, 'recursion'): - # self.ubjloadb(raw) - # finally: - # setrecursionlimit(old_limit) + def test_recursion(self): + old_limit = getrecursionlimit() + setrecursionlimit(200) + try: + obj = current = [] + for _ in range(getrecursionlimit() * 2): + new_list = [] + current.append(new_list) + current = new_list + + with self.assert_raises_regex(RuntimeError, 'recursion'): + self.ubjdumpb(obj) + + raw = ARRAY_START * (getrecursionlimit() * 2) + with self.assert_raises_regex(RuntimeError, 'recursion'): + self.ubjloadb(raw) + finally: + setrecursionlimit(old_limit) def test_encode_default(self): def default(obj): From d173a8e739a70876c642d56bb9da2e56131e7296 Mon Sep 17 00:00:00 2001 From: Davis C Date: Sat, 22 Mar 2025 14:34:27 -0500 Subject: [PATCH 7/7] Bump version --- ubjson/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ubjson/__init__.py b/ubjson/__init__.py index a97e496..e9d4dda 100644 --- a/ubjson/__init__.py +++ b/ubjson/__init__.py @@ -37,6 +37,6 @@ from .encoder import EncoderException from .decoder import DecoderException -__version__ = '0.16.1' +__version__ = '0.16.2' __all__ = ('EXTENSION_ENABLED', 'dump', 'dumpb', 'EncoderException', 'load', 'loadb', 'DecoderException')