Skip to content

Commit b5f5ffb

Browse files
committed
fix: add linter for thread-unsafe C API uses
1 parent 851ba15 commit b5f5ffb

33 files changed

+208
-88
lines changed

.github/workflows/mypy.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,21 @@ jobs:
7272
- name: Run Mypy
7373
run: |
7474
spin mypy
75+
# This job checks for suspicious C API usage that may break nogil compatibility.
76+
# It scans PR diffs for dangerous borrowed-referenced calls and fails if any are found.
77+
lint-capi:
78+
# To enable this workflow on a fork, comment out:
79+
if: github.repository == 'numpy/numpy'
80+
name: "C API Borrowed Ref Lint"
81+
runs-on: ubuntu-latest
82+
steps:
83+
- uses: actions/checkout@v3
84+
with:
85+
fetch-depth: 0
86+
submodules: recursive
87+
- name: Run API Borrowed Ref Linter
88+
run: |
89+
pip install -r requirements/build_requirements.txt
90+
pip install -r requirements/test_requirements.txt
91+
pip install ruff
92+
spin lint

numpy/_core/include/numpy/npy_3kcompat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ static inline PyObject*
242242
npy_PyFile_OpenFile(PyObject *filename, const char *mode)
243243
{
244244
PyObject *open;
245-
open = PyDict_GetItemString(PyEval_GetBuiltins(), "open");
245+
open = PyDict_GetItemString(PyEval_GetBuiltins(), "open"); // noqa: borrowed-ref OK
246246
if (open == NULL) {
247247
return NULL;
248248
}

numpy/_core/src/common/npy_cpu_dispatch.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ NPY_VISIBILITY_HIDDEN void
3333
npy_cpu_dispatch_trace(const char *fname, const char *signature,
3434
const char **dispatch_info)
3535
{
36-
PyObject *func_dict = PyDict_GetItemString(npy_static_pydata.cpu_dispatch_registry, fname);
36+
PyObject *func_dict = PyDict_GetItemString(npy_static_pydata.cpu_dispatch_registry, fname); // noqa: borrowed-ref OK
3737
if (func_dict == NULL) {
3838
func_dict = PyDict_New();
3939
if (func_dict == NULL) {

numpy/_core/src/common/ufunc_override.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ PyUFuncOverride_GetOutObjects(PyObject *kwds, PyObject **out_kwd_obj, PyObject *
9393
*out_kwd_obj = NULL;
9494
return -1;
9595
}
96-
int result = PyDict_GetItemStringRef(kwds, "out", out_kwd_obj);
96+
int result = PyDict_GetItemStringRef(kwds, "out", out_kwd_obj); // noqa: borrowed-ref OK
9797
if (result == -1) {
9898
return -1;
9999
}
@@ -108,7 +108,7 @@ PyUFuncOverride_GetOutObjects(PyObject *kwds, PyObject **out_kwd_obj, PyObject *
108108
* PySequence_Fast* functions. This is required for PyPy
109109
*/
110110
PyObject *seq;
111-
seq = PySequence_Fast(*out_kwd_obj,
111+
seq = PySequence_Fast(*out_kwd_obj, // noqa: borrowed-ref OK
112112
"Could not convert object to sequence");
113113
if (seq == NULL) {
114114
Py_CLEAR(*out_kwd_obj);

numpy/_core/src/multiarray/_multiarray_tests.c.src

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ incref_elide_l(PyObject *dummy, PyObject *args)
644644
}
645645
/* get item without increasing refcount, item may still be on the python
646646
* stack but above the inaccessible top */
647-
r = PyList_GetItem(arg, 4);
647+
r = PyList_GetItem(arg, 4); // noqa: borrowed-ref OK
648648
res = PyNumber_Add(r, r);
649649

650650
return res;
@@ -863,7 +863,7 @@ get_all_cast_information(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(args))
863863
if (classes == NULL) {
864864
goto fail;
865865
}
866-
Py_SETREF(classes, PySequence_Fast(classes, NULL));
866+
Py_SETREF(classes, PySequence_Fast(classes, NULL)); // noqa: borrowed-ref OK
867867
if (classes == NULL) {
868868
goto fail;
869869
}
@@ -883,7 +883,7 @@ get_all_cast_information(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(args))
883883
PyObject *to_dtype, *cast_obj;
884884
Py_ssize_t pos = 0;
885885

886-
while (PyDict_Next(NPY_DT_SLOTS(from_dtype)->castingimpls,
886+
while (PyDict_Next(NPY_DT_SLOTS(from_dtype)->castingimpls, // noqa: borrowed-ref OK
887887
&pos, &to_dtype, &cast_obj)) {
888888
if (cast_obj == Py_None) {
889889
continue;
@@ -965,7 +965,7 @@ identityhash_tester(PyObject *NPY_UNUSED(mod),
965965
}
966966

967967
/* Replace the sequence with a guaranteed fast-sequence */
968-
sequence = PySequence_Fast(sequence, "converting sequence.");
968+
sequence = PySequence_Fast(sequence, "converting sequence."); // noqa: borrowed-ref OK
969969
if (sequence == NULL) {
970970
goto finish;
971971
}

numpy/_core/src/multiarray/array_coercion.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1148,7 +1148,7 @@ PyArray_DiscoverDTypeAndShape_Recursive(
11481148
force_sequence_due_to_char_dtype:
11491149

11501150
/* Ensure we have a sequence (required for PyPy) */
1151-
seq = PySequence_Fast(obj, "Could not convert object to sequence");
1151+
seq = PySequence_Fast(obj, "Could not convert object to sequence"); // noqa: borrowed-ref OK
11521152
if (seq == NULL) {
11531153
/*
11541154
* Specifically do not fail on things that look like a dictionary,

numpy/_core/src/multiarray/arrayfunction_override.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ array__get_implementing_args(
371371
return NULL;
372372
}
373373

374-
relevant_args = PySequence_Fast(
374+
relevant_args = PySequence_Fast( // noqa: borrowed-ref OK
375375
relevant_args,
376376
"dispatcher for __array_function__ did not return an iterable");
377377
if (relevant_args == NULL) {
@@ -518,7 +518,7 @@ dispatcher_vectorcall(PyArray_ArrayFunctionDispatcherObject *self,
518518
fix_name_if_typeerror(self);
519519
return NULL;
520520
}
521-
Py_SETREF(relevant_args, PySequence_Fast(relevant_args,
521+
Py_SETREF(relevant_args, PySequence_Fast(relevant_args, // noqa: borrowed-ref OK
522522
"dispatcher for __array_function__ did not return an iterable"));
523523
if (relevant_args == NULL) {
524524
return NULL;

numpy/_core/src/multiarray/arraytypes.c.src

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,7 @@ VOID_getitem(void *input, void *vap)
881881
npy_intp offset;
882882
PyArray_Descr *new;
883883
key = PyTuple_GET_ITEM(names, i);
884-
tup = PyDict_GetItem(descr->fields, key);
884+
tup = PyDict_GetItem(descr->fields, key); // noqa: borrowed-ref OK
885885
if (_unpack_field(tup, &new, &offset) < 0) {
886886
Py_DECREF(ret);
887887
return NULL;
@@ -973,7 +973,7 @@ _setup_field(int i, _PyArray_LegacyDescr *descr, PyArrayObject *arr,
973973
npy_intp offset;
974974

975975
key = PyTuple_GET_ITEM(descr->names, i);
976-
tup = PyDict_GetItem(descr->fields, key);
976+
tup = PyDict_GetItem(descr->fields, key); // noqa: borrowed-ref OK
977977
if (_unpack_field(tup, &new, &offset) < 0) {
978978
return -1;
979979
}
@@ -2274,7 +2274,7 @@ VOID_copyswapn (char *dst, npy_intp dstride, char *src, npy_intp sstride,
22742274
PyArrayObject_fields dummy_fields = get_dummy_stack_array(arr);
22752275
PyArrayObject *dummy_arr = (PyArrayObject *)&dummy_fields;
22762276

2277-
while (PyDict_Next(descr->fields, &pos, &key, &value)) {
2277+
while (PyDict_Next(descr->fields, &pos, &key, &value)) { // noqa: borrowed-ref OK
22782278
npy_intp offset;
22792279
PyArray_Descr *new;
22802280
if (NPY_TITLE_KEY(key, value)) {
@@ -2359,7 +2359,7 @@ VOID_copyswap (char *dst, char *src, int swap, PyArrayObject *arr)
23592359
PyArrayObject_fields dummy_fields = get_dummy_stack_array(arr);
23602360
PyArrayObject *dummy_arr = (PyArrayObject *)&dummy_fields;
23612361

2362-
while (PyDict_Next(descr->fields, &pos, &key, &value)) {
2362+
while (PyDict_Next(descr->fields, &pos, &key, &value)) { // noqa: borrowed-ref OK
23632363
npy_intp offset;
23642364

23652365
PyArray_Descr * new;
@@ -2643,7 +2643,7 @@ VOID_nonzero (char *ip, PyArrayObject *ap)
26432643
PyArrayObject *dummy_arr = (PyArrayObject *)&dummy_fields;
26442644

26452645
_PyArray_LegacyDescr *descr = (_PyArray_LegacyDescr *)PyArray_DESCR(ap);
2646-
while (PyDict_Next(descr->fields, &pos, &key, &value)) {
2646+
while (PyDict_Next(descr->fields, &pos, &key, &value)) { // noqa: borrowed-ref OK
26472647
PyArray_Descr * new;
26482648
npy_intp offset;
26492649
if (NPY_TITLE_KEY(key, value)) {
@@ -3005,7 +3005,7 @@ VOID_compare(char *ip1, char *ip2, PyArrayObject *ap)
30053005
PyArray_Descr *new;
30063006
npy_intp offset;
30073007
key = PyTuple_GET_ITEM(names, i);
3008-
tup = PyDict_GetItem(PyDataType_FIELDS(descr), key);
3008+
tup = PyDict_GetItem(PyDataType_FIELDS(descr), key); // noqa: borrowed-ref OK
30093009
if (_unpack_field(tup, &new, &offset) < 0) {
30103010
goto finish;
30113011
}

numpy/_core/src/multiarray/buffer.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ _buffer_format_string(PyArray_Descr *descr, _tmp_string_t *str,
268268
int ret;
269269

270270
name = PyTuple_GET_ITEM(ldescr->names, k);
271-
item = PyDict_GetItem(ldescr->fields, name);
271+
item = PyDict_GetItem(ldescr->fields, name); // noqa: borrowed-ref OK
272272

273273
child = (PyArray_Descr*)PyTuple_GetItem(item, 0);
274274
offset_obj = PyTuple_GetItem(item, 1);

numpy/_core/src/multiarray/compiled_base.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1522,7 +1522,7 @@ arr_add_docstring(PyObject *NPY_UNUSED(dummy), PyObject *const *args, Py_ssize_t
15221522
PyTypeObject *new = (PyTypeObject *)obj;
15231523
_ADDDOC(new->tp_doc, new->tp_name);
15241524
if (new->tp_dict != NULL && PyDict_CheckExact(new->tp_dict) &&
1525-
PyDict_GetItemString(new->tp_dict, "__doc__") == Py_None) {
1525+
PyDict_GetItemString(new->tp_dict, "__doc__") == Py_None) { // noqa: borrowed-ref OK
15261526
/* Warning: Modifying `tp_dict` is not generally safe! */
15271527
if (PyDict_SetItemString(new->tp_dict, "__doc__", str) < 0) {
15281528
return NULL;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy