Skip to content

All 6 heap type deallocs missing Py_DECREF(Py_TYPE(self)) #223

Description

@devdanzin

All 6 types in kiwisolver are created via PyType_FromSpec (heap types). Heap type instances hold a strong reference to their type object, which must be released in tp_dealloc. None of the 6 dealloc functions call Py_DECREF(Py_TYPE(self)), leaking one type reference per object destruction.

Reproducer:

import sys, gc
import kiwisolver as ks

for name, factory in [
    ("Variable", lambda: ks.Variable("x")),
    ("Solver", lambda: ks.Solver()),
    ("Constraint", lambda: (ks.Variable("x") + 1 >= 0)),
]:
    T = type(factory())
    rc_before = sys.getrefcount(T)
    for _ in range(1000):
        del (factory())
    gc.collect()
    delta = sys.getrefcount(T) - rc_before
    print(f"{name}: {delta} type refs leaked over 1000 cycles")
    assert delta == 1000
# Variable: 1000 type refs leaked over 1000 cycles
# Solver: 1000 type refs leaked over 1000 cycles
# Constraint: 1000 type refs leaked over 1000 cycles

Affected files (all follow the same pattern — save tp before tp_free, decref after):

  • variable.cpp:84-92
  • term.cpp:68-73
  • expression.cpp:74-79
  • constraint.cpp:71-79
  • solver.cpp:37-43
  • strength.cpp:31-34

Example fix (Variable, same pattern for all 6):

void Variable_dealloc(Variable* self)
{
    PyObject_GC_UnTrack(self);
    Variable_clear(self);
    ACQUIRE_GLOBAL_LOCK();
    self->variable.~Variable();
    RELEASE_GLOBAL_LOCK();
    PyTypeObject* tp = Py_TYPE(self);
    tp->tp_free(pyobject_cast(self));
    Py_DECREF(tp);
}

Note: the tp_traverse functions already correctly Py_VISIT(Py_TYPE(self)) on 3.9+. Only the dealloc side is missing.

Found by cext-review-toolkit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions