Bug report
Bug description:
Summary
Modules/_zstd/decompressor.c is missing a Py_DECREF(value) before
the early return when PyLong_AsInt(key) fails. The identical code in
Modules/_zstd/compressor.c has the fix.
Likely easy to miss other than very long running services that slowly
leak.
decompressor.c (buggy):
Py_INCREF(key); // line 109 Py_INCREF(value); // line 110 int key_v = PyLong_AsInt(key); // line 111 Py_DECREF(key); // line 112 if (key_v == -1 && PyErr_Occurred()) { return -1; // line 114 -- leaks value }
compressor.c (correct):
Py_INCREF(key); // line 150 Py_INCREF(value); // line 151 int key_v = PyLong_AsInt(key); // line 152 Py_DECREF(key); // line 153 if (key_v == -1 && PyErr_Occurred()) { Py_DECREF(value); // line 158 -- present here return -1; }
Reproducer
import _zstd, sys, gc class Marker: pass val = Marker() baseline = sys.getrefcount(val) for _ in range(100): try: _zstd.ZstdDecompressor(options={"bad_key": val}) except Exception: pass gc.collect() after = sys.getrefcount(val) print(f"baseline refcount: {baseline}") print(f"after 100 error triggers: {after}") print(f"leaked refs: {after - baseline}")
Output:
baseline refcount: 2
after 100 error triggers: 102
leaked refs: 100
Each call leaks exactly one reference to value.
Related
- _zstd: Fix decompression options dict error message #146579 (cuiweixie, merged 2 weeks ago) fixed the error message
in the same decompressor options dict code path; this is the adjacent
refcount fix.
Proposed Fix
Just add Py_DECREF(value); before return -1 at decompressor.c line 114. Happy to submit that as a PR if you want.
CPython versions tested on:
3.14
Operating systems tested on:
Linux