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.
Two related type-correctness issues, both primarily relevant under free-threaded Python:
1. Solver and strength missing
tp_traversevisitingPy_TYPE(self)Since Python 3.9, heap types (created via
PyType_FromSpec) must visitPy_TYPE(self)intp_traverseto prevent the type object from being prematurely collected by the GC. The other 4 types in kiwisolver (Variable,Term,Expression,Constraint) correctly do this.Solverandstrengthdo not havetp_traverseat all.Since neither type holds
PyObject*members, adding GC support requires:Py_TPFLAGS_HAVE_GCto the type spec flagstp_traversethat visitsPy_TYPE(self)tp_freetoPyObject_GC_DelPyObject_GC_UnTrackat the top oftp_dealloc2. Repr/accessor methods access possibly-cleared members under free-threading
Constraint_repr,Expression_repr/Expression_terms/Expression_value, andTerm_repr/Term_variable/Term_valueaccess theirPyObject*members (self->expression,self->terms,self->variable) without NULL checks. These members are set to NULL bytp_clearduring 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 forVariable.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.