Repository: numpy/numpy
File: numpy/_core/src/multiarray/arrayobject.c (C)

Code before:
    int ret = PyErr_WarnEx(warning, msg, 1);
    if (ret == 0 || !unraisable) {
        return ret;
    }
    PyObject *s = PyUnicode_FromString("array_dealloc");
    PyErr_WriteUnraisable(s);  // OK if s = NULL
    Py_XDECREF(s);
    return 0;
}

/* array object functions */

/*
 * Much of the actual work for dealloc, split off for use in __setstate__
 * via dealloc_all_but_self function defined below.
 * If not unraisable, will return -1 on error, 0 on success.
 * If unraisable, always succeeds.
 */
static int
_dealloc_all_but_self(PyArrayObject *self, npy_bool unraisable)
{
    PyArrayObject_fields *fa = (PyArrayObject_fields *)self;
    int retval;

    if (fa->_buffer_info) {
        if (_buffer_info_free(fa->_buffer_info, (PyObject *)self) < 0) {
            if (!unraisable) {
                return -1;
            }
            PyErr_WriteUnraisable(NULL);
        }
        fa->_buffer_info = NULL;
    }

    if (fa->base) {
        if (PyArray_FLAGS(self) & NPY_ARRAY_WRITEBACKIFCOPY)
        {
            char const * msg = "WRITEBACKIFCOPY detected in array_dealloc. "
                " Required call to PyArray_ResolveWritebackIfCopy or "
                "PyArray_DiscardWritebackIfCopy is missing.";
            if (WARN_IN_DEALLOC(PyExc_RuntimeWarning, msg, unraisable) < 0) {
                return -1;  // Can only happen if !unraisable, so pass on.
            }
            retval = PyArray_ResolveWritebackIfCopy(self);
            if (retval < 0)
            {
                if (!unraisable) {
                    return -1;
                }
                PyErr_Print();
                PyErr_Clear();

Review comment (suggestion):
This part shows both the advantage and disadvantage of combining the deallocation from two different parts:
- advantage: `_buffer_info_free` was not called in the `__setstate__` path. Perhaps it can never be set? Or just introduced later? Better safe than sorry!
- disadvantage: bit spaghetti-codey to signal an error return only on one path. Also, we now have to make sure we clear any member that we deallocated (the assignment to NULL just below).

Code after:
write_and_clear_error_if_unraisable(int status, npy_bool unraisable)
{
    if (status < 0 && unraisable) {
        PyErr_WriteUnraisable(npy_interned_str.array_dealloc);
        return 0;
    }
    return status;
}

/* array object functions */

/*
 * Much of the actual work for dealloc, split off for use in __setstate__
 * via clear_array_attributes function defined below.
 * If not unraisable, will return -1 on error, 0 on success.
 * If unraisable, always succeeds, though may print errors and warnings.
 */
static int
_clear_array_attributes(PyArrayObject *self, npy_bool unraisable)
{
    PyArrayObject_fields *fa = (PyArrayObject_fields *)self;

    if (_buffer_info_free(fa->_buffer_info, (PyObject *)self) < 0) {
        if (write_and_clear_error_if_unraisable(-1, unraisable) < 0) {
            return -1;
        }
    }
    fa->_buffer_info = NULL;

    if (fa->base) {
        if (PyArray_FLAGS(self) & NPY_ARRAY_WRITEBACKIFCOPY)
        {
            char const * msg = "WRITEBACKIFCOPY detected in clearing of array. "
                " Required call to PyArray_ResolveWritebackIfCopy or "
                "PyArray_DiscardWritebackIfCopy is missing.";
            int retval = PyErr_WarnEx(PyExc_RuntimeWarning, msg, 1);
            if (write_and_clear_error_if_unraisable(retval, unraisable) < 0) {
                return -1;
            }
            /*
             * prevent reaching 0 twice and thus recursing into dealloc.
             * Increasing sys.gettotalrefcount, but path should not be taken.
             */
            Py_INCREF(self);
            retval = PyArray_ResolveWritebackIfCopy(self);
            if (write_and_clear_error_if_unraisable(retval, unraisable) < 0) {
                return -1;
            }
        }
        /*
         * If fa->base is non-NULL, it is something