Skip to content

BUG: out reference leak in PyUFunc_Accumulate and PyUFunc_Reduceat on type resolution failure #31691

Closed

Description

@Fuyugithub

Bug Description

In both PyUFunc_Accumulate and PyUFunc_Reduceat, the out argument is INCREF'd early in the function, but when reducelike_promote_and_resolve fails immediately after, the code does return NULL directly — bypassing the fail label where Py_XDECREF(out) is located. This leaks the reference to out.

Location

  1. numpy/_core/src/umath/ufunc_object.c, PyUFunc_Accumulate, lines ~2745-2753
  2. numpy/_core/src/umath/ufunc_object.c, PyUFunc_Reduceat, lines ~3168-3176

Code

Both functions follow the same pattern:

/* Take a reference to out for later returning */
Py_XINCREF(out);                          // line ~2745 / ~3168

PyArray_Descr *descrs[3];
PyArrayMethodObject *ufuncimpl = reducelike_promote_and_resolve(ufunc,
        arr, out, signature, NPY_TRUE, descrs, NPY_UNSAFE_CASTING,
        "accumulate");                     // or "reduceat"
if (ufuncimpl == NULL) {
    return NULL;                           // BUG: out's INCREF never balanced
}

Further down the function, the fail: label correctly does Py_XDECREF(out), but the early return NULL bypasses it.

Impact

Every time accumulate() or reduceat() is called with an out argument and type resolution fails (e.g., incompatible dtypes), the out array leaks a reference. Over repeated calls, this prevents the array from being garbage collected.

Suggested Fix

Change return NULL to goto fail in both locations:

if (ufuncimpl == NULL) {
    goto fail;
}

This ensures the fail label's cleanup runs, which includes Py_XDECREF(out).

Environment

  • NumPy: current main branch
  • Affected: numpy.ufunc.accumulate() and numpy.ufunc.reduceat() with out argument on type resolution failure

Activity

  1. mattip commented on Jun 19, 2026

    @mattip
    Member

    Is this theoretical or is there a reproducer?

  2. ikrommyd commented on Jun 19, 2026

    @ikrommyd
    Member

    I was reading the whole reducer code paths in the last few days to understand it and I also thought about this issue myself.
    @mattip I think failures to resolve the dtype when out is provided will hit this so something like this will make the ref count of out be 10K because it can't resolve the output being U5.

    >>> import sys, numpy as np
    ... arr = np.arange(3)
    ... out = np.empty(3, dtype="U5")
    ...
    ... before = sys.getrefcount(out)
    ... for _ in range(10000):
    ...     try:
    ...         np.add.accumulate(arr, out=out)
    ...     except Exception:
    ...         pass
    ... print(sys.getrefcount(out) - before)
    ...
    10000

    but goto fail is not right solution, it does cleanup of uninitialized state and leads to a bus error crash. So either Py_XDECREF(out) before return NULL or moving the uninitialized state to initialized to NULL so that Py_XDECREF in the fail block is safe is the solution I think.

    I think it's low-priority anyways cause hopefully nobody tries so hard to fail the dtype resolution like my snippet does 🤣 so the leak is not important in the real world most likely.

  3. changed the title [-][BUG] out reference leak in PyUFunc_Accumulate and PyUFunc_Reduceat on type resolution failure[/-] [+]BUG: out reference leak in PyUFunc_Accumulate and PyUFunc_Reduceat on type resolution failure[/+] on Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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