Skip to content

Solver and strength types missing tp_traverse for heap type; repr methods vulnerable to GC-clear #228

Description

@devdanzin

Two related type-correctness issues, both primarily relevant under free-threaded Python:

1. Solver and strength missing tp_traverse visiting Py_TYPE(self)

Since Python 3.9, heap types (created via PyType_FromSpec) must visit Py_TYPE(self) in tp_traverse to prevent the type object from being prematurely collected by the GC. The other 4 types in kiwisolver (Variable, Term, Expression, Constraint) correctly do this. Solver and strength do not have tp_traverse at all.

Since neither type holds PyObject* members, adding GC support requires:

  • Adding Py_TPFLAGS_HAVE_GC to the type spec flags
  • Adding a tp_traverse that visits Py_TYPE(self)
  • Changing tp_free to PyObject_GC_Del
  • Adding PyObject_GC_UnTrack at the top of tp_dealloc

2. Repr/accessor methods access possibly-cleared members under free-threading

Constraint_repr, Expression_repr/Expression_terms/Expression_value, and Term_repr/Term_variable/Term_value access their PyObject* members (self->expression, self->terms, self->variable) without NULL checks. These members are set to NULL by tp_clear during GC cycle breaking.

Under the GIL this is safe (GC and method calls are serialized). Under free-threading, if GC clears the object while a repr/accessor is running concurrently, the NULL dereference would crash. Adding Py_BEGIN_CRITICAL_SECTION (already used for Variable.context) would protect these accesses.

Low priority — the GC-clear-during-method scenario is very unlikely even under free-threading, but worth noting for completeness.

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