Skip to content

Destructor called on unconstructed C++ object in Variable_new, Constraint_new, makecn #224

Description

@devdanzin

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.

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