Skip to content

Commit 54c8ed5

Browse files
Fixed reference/memory leaks in decode_definite_long_string (#290)
Two reference/memory leaks in the C extension's long string decoder: 1. Missing Py_DECREF(ret) before reassignment after PyUnicode_Concat, leaking all intermediate Unicode objects during chunked decoding. 2. Missing PyMem_Free(buffer) on the success path, leaking the scratch buffer used for UTF-8 boundary handling.
1 parent a8d92dc commit 54c8ed5

File tree

3 files changed

+9
-1
lines changed

3 files changed

+9
-1
lines changed

docs/versionhistory.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
2020
(`#254 <https://github.com/agronholm/cbor2/issues/254>`_)
2121
- Fixed a missed check for an exception in the C implementation of ``CBOREncoder.encode_shared()``
2222
(`#287 <https://github.com/agronholm/cbor2/issues/287>`_)
23+
- Fixed two reference/memory leaks in the C extension's long string decoder
24+
(`#290 <https://github.com/agronholm/cbor2/pull/290>`_ PR by @killiancowan82)
2325

2426
**5.8.0** (2025-12-30)
2527

scripts/ref_leak_test.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ class Module:
8585
),
8686
("tag", {}, c_cbor2.CBORTag(1, 1)),
8787
("nestedtag", {}, {c_cbor2.CBORTag(1, 1): 1}),
88+
("longstr_128k", {}, "x" * 131072),
89+
("longstr_multi_utf8", {}, ("a" * 65535 + "€") * 2),
8890
]
8991

9092
Leaks = namedtuple("Leaks", ("count", "comparison"))
@@ -105,7 +107,7 @@ def test_malloc(op):
105107
# NOTE: Filter pointing to the op() line in the loop below, because we're
106108
# only interested in memory allocated by that line. Naturally, if this file
107109
# is edited, the lineno parameter below must be adjusted!
108-
only_op = tracemalloc.Filter(True, __file__, lineno=102, all_frames=True)
110+
only_op = tracemalloc.Filter(True, __file__, lineno=119, all_frames=True)
109111
tracemalloc.start(10)
110112
try:
111113
# Perform a pre-run of op so that any one-time memory allocation

source/decoder.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,7 @@ decode_definite_long_string(CBORDecoderObject *self, Py_ssize_t length)
937937

938938
Py_DECREF(string);
939939
string = NULL;
940+
Py_DECREF(ret);
940941
ret = joined;
941942
} else {
942943
// Set the result to the decoded string
@@ -969,6 +970,9 @@ decode_definite_long_string(CBORDecoderObject *self, Py_ssize_t length)
969970
if (ret && string_namespace_add(self, ret, length) == -1)
970971
goto error;
971972

973+
if (buffer)
974+
PyMem_Free(buffer);
975+
972976
return ret;
973977
error:
974978
Py_XDECREF(ret);

0 commit comments

Comments
 (0)