Variable_new, Constraint_new, and makecn (in symbolics.h) all allocate a Python object via PyType_GenericNew, then may return an error before the C++ member is placement-new'd. When the error return triggers cppy::ptr's destructor, it calls Py_DECREF on the object, which invokes the type's tp_dealloc. The dealloc calls the C++ destructor (~Variable(), ~Constraint()) on memory that was never constructed — undefined behavior.
Affected sites:
variable.cpp:40-48 — if !PyUnicode_Check(name) or convert_pystr_to_str fails, error return before new(&self->variable) kiwi::Variable(...) at line 50/56
constraint.cpp:40-53 — if reduce_expression fails at line 45, error return before new(&cn->constraint) kiwi::Constraint(...) at line 50
symbolics.h:575-587 (makecn) — same pattern as Constraint_new
Example (Variable_new flow):
PyType_GenericNew succeeds (zero-initialized memory)
→ self->context = cppy::xincref(context) // sets a field
→ !PyUnicode_Check(name) → return error
→ cppy::ptr destructor → Py_DECREF → Variable_dealloc
→ self->variable.~Variable() // UB: never constructed
Suggested fix: validate input before allocating, or do the placement-new immediately after allocation:
// Option A: validate first
if (name != 0) {
if (!PyUnicode_Check(name))
return cppy::type_error(name, "str");
if (!convert_pystr_to_str(name, c_name))
return 0;
}
cppy::ptr pyvar(PyType_GenericNew(type, args, kwargs));
if (!pyvar)
return 0;
// Now placement-new is always reached...
In practice, the UB may be benign on current platforms (kiwi destructors on zeroed memory happen to be no-ops), but it is formally undefined and could break with compiler optimizations or kiwi library changes.
Found by cext-review-toolkit.
Variable_new,Constraint_new, andmakecn(insymbolics.h) all allocate a Python object viaPyType_GenericNew, then may return an error before the C++ member is placement-new'd. When the error return triggerscppy::ptr's destructor, it callsPy_DECREFon the object, which invokes the type'stp_dealloc. The dealloc calls the C++ destructor (~Variable(),~Constraint()) on memory that was never constructed — undefined behavior.Affected sites:
variable.cpp:40-48— if!PyUnicode_Check(name)orconvert_pystr_to_strfails, error return beforenew(&self->variable) kiwi::Variable(...)at line 50/56constraint.cpp:40-53— ifreduce_expressionfails at line 45, error return beforenew(&cn->constraint) kiwi::Constraint(...)at line 50symbolics.h:575-587(makecn) — same pattern asConstraint_newExample (
Variable_newflow):Suggested fix: validate input before allocating, or do the placement-new immediately after allocation:
In practice, the UB may be benign on current platforms (kiwi destructors on zeroed memory happen to be no-ops), but it is formally undefined and could break with compiler optimizations or kiwi library changes.
Found by cext-review-toolkit.