Skip to content

ENH: Build _reduction_loop_tests against the Limited API - #32256

Open
prathamhole14 wants to merge 4 commits into
numpy:mainfrom
prathamhole14:reduction_loop_tests_limited_api
Open

ENH: Build _reduction_loop_tests against the Limited API#32256
prathamhole14 wants to merge 4 commits into
numpy:mainfrom
prathamhole14:reduction_loop_tests_limited_api

Conversation

@prathamhole14

Copy link
Copy Markdown
Contributor

PR summary

Related to: #31913

AI Disclosure

Finding places where changes are required, code and logic is written by me.

@prathamhole14 prathamhole14 changed the title ENH: Build numpy._core._reduction_loop_tests against the Limited API ENH: Build _reduction_loop_tests against the Limited API Aug 12, 2026
@prathamhole14
prathamhole14 marked this pull request as ready for review August 12, 2026 12:09
Comment on lines -157 to +161
Py_XSETREF(*(PyObject **)out1, lo);
Py_XSETREF(*(PyObject **)out2, hi);
Py_XDECREF(*o1);
*o1 = lo;
Py_XDECREF(*o2);
*o2 = hi;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, here and below I don't think this is correct. Too bad we have to take special care here - these functions were never added to the limited API.

See the docs for Py_SETREF. This is exactly "the obvious ... deadly" code.

We could also add something like this to an internal compat header or maybe to pythoncapi-compat itself (suggested by an AI model, please double-check and validate it by comparing with CPython's implementation):

#ifndef Py_SETREF
#if defined(__GNUC__) || defined(__clang__)
#define NPY_SETREF_IMPL_(dst, src, decref) \
    do { \
        __typeof__(dst) *_tmp_dst_ptr = &(dst); \
        __typeof__(dst) _tmp_old_dst = (*_tmp_dst_ptr); \
        *_tmp_dst_ptr = (src); \
        decref(_tmp_old_dst); \
    } while (0)
#else
#define NPY_SETREF_IMPL_(dst, src, decref) \
    do { \
        PyObject **_tmp_dst_ptr = (PyObject **)&(dst); \
        PyObject *_tmp_old_dst = (*_tmp_dst_ptr); \
        PyObject *_tmp_src = (PyObject *)(src); \
        memcpy(_tmp_dst_ptr, &_tmp_src, sizeof(PyObject *)); \
        decref(_tmp_old_dst); \
    } while (0)
#endif
#define Py_SETREF(dst, src)  NPY_SETREF_IMPL_(dst, src, Py_DECREF)
#define Py_XSETREF(dst, src) NPY_SETREF_IMPL_(dst, src, Py_XDECREF)
#endif

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want a more minimal fix, this is also safe:

PyObject *tmp = dst;
dst = src;
Py_DECREF(tmp);

But doing it without storing a reference in the temporary before re-assigning the reference may cause a use-after-free.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened capi-workgroup/decisions#110 about this. But of course that will only impact Python 3.16 and newer, if at all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching the blunder you are right that is the unsafe ordering. I have gone with the minimal fix, using a temporary at each site:

PyObject *old1 = *o1, *old2 = *o2;
*o1 = lo;
*o2 = hi;
Py_XDECREF(old1);
Py_XDECREF(old2);

Both slots are published before either decref, so neither is stale while a __del__ runs.

I am also happy to do the shared-macro version instead if you had prefer but it felt like it belongs in a seperate PR rather than this one. And thanks for opening the capi-workgroup issue!

@prathamhole14
prathamhole14 force-pushed the reduction_loop_tests_limited_api branch from 1cd9ad8 to 923f8fc Compare August 13, 2026 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants