-
-
Notifications
You must be signed in to change notification settings - Fork 9.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BUG: do not memcpy ptr to freed object #8862
Conversation
@@ -1908,6 +1908,8 @@ _check_object_rec(PyArray_Descr *descr) | |||
return 0; | |||
} | |||
|
|||
static PyObject * global_zero = NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could go within the function, if its not used elsewhere. Ditto for one
.
can you give more context on how scipy crashes? The way scipy uses these functions is looks wrong and this doesn't really fix it properly, it just avoids it by adding refcounts to the object. |
global_zero = PyInt_FromLong((long) 0); | ||
if (global_zero == NULL) | ||
{ | ||
PyErr_SetNone(PyExc_MemoryError); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the python function should already set the correct exception, so no special handling is necessary
Also our if brace style is:
if (cond) {
}
global_zero = PyInt_FromLong((long) 0); | ||
if (global_zero == NULL) | ||
{ | ||
PyErr_SetNone(PyExc_MemoryError); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it better just to pass on the internal error here?
I don't think these functions can be used correctly on object arrays right now anyway - they always return a pointer to an object that likely has refcount = 0. As @mattip mentions, we can get away with it on CPython because the object is cached. On PyPy, there's nothing useful you can ever do with this pointer - This isn't the right fix though - if you look in |
@juliantaylor I found this in Once I removed the |
@eric-wieser What would you suggest instead of removing the |
@eric-wieser What would you suggest instead of removing the Py_DECREFs? I'm claiming that there's no need to have the static global, but that removing those is the right call anyway. I think the only sane calling convention of I'm struggling to tell if this is what |
IMO Note that AFAICT, the code using |
This is the part that looked suspect to me. static void neighiter_dealloc(PyArrayNeighborhoodIterObject* iter)
{
if (iter->mode == NPY_NEIGHBORHOOD_ITER_CONSTANT_PADDING) {
if (PyArray_ISOBJECT(iter->_internal_iter->ao)) {
Py_DECREF(*(PyObject**)iter->constant);
}
}
PyDataMem_FREE(iter->constant);
...
} EDIT: Ah, that's not a path that's taken with |
The The approach taken in this PR looks... horrible and will have to be ripped out sooner or later when we rework dtypes. (In particular it hard-codes the assumption that Going forward IMO we need to deprecate |
Why are you screwed? As long as you
When combined with this patch, numpy looks alright to me, in the one place we use it. That doesn't mean this is a good api, but it does look like a usable one. |
memcpy(zeroval, &obj, sizeof(PyObject *)); | ||
Py_DECREF(obj); | ||
memcpy(zeroval, &global_zero, sizeof(PyObject *)); | ||
/*Py_DECREF(obj);*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens without this special case?
@eric-wieser: OK, I spent a while working out how the neighborhood iterator works, and I guess you're right inasmuch as the neighborhood iterator also has a kinda broken API for similar reasons :-). It's not at all clear to me how to make the neighborhood iterator work properly for arbitrary custom dtypes, and if you search google for neighborhood iterator examples then this code is the first thing that comes up, and it's totally wrong for object arrays because of how it screws up the refcounts: http://numpy-discussion.10968.n7.nabble.com/Example-Usage-of-Neighborhood-Iterator-in-Cython-td8757.html |
And yeah, technically I guess you can hard-code bits of the object dtype's internal semantics by checking for it and putting in manual |
Can you elaborate on these? Looks pretty reasonable to me
Do we even need the special case here? Doesn't |
I updated the pull requests according to the review comments, and tried to summarize the discussion into a warning in the code |
@@ -1916,7 +1916,7 @@ PyArray_Zero(PyArrayObject *arr) | |||
{ | |||
char *zeroval; | |||
int ret, storeflags; | |||
PyObject *obj; | |||
static PyObject * zero_obj = NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: I think I'd prefer the static variable to be first
if (zero_obj == NULL) { | ||
return NULL; | ||
} | ||
} | ||
if (PyArray_ISOBJECT(arr)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question as before - what goes wrong if we remove this special case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we ignore the fact that we could not allocate a PyIntOjbect? That seems dangerous, and bad practice. OTOH, if this even occurs we have bigger problems than just this allocation ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That comment was directed at if (PyArray_ISOBJECT(arr)) {
, not the lines above it. What's special about OBJECT_setitem
that means we don't want to call it below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I follow. AFAIK PyArray_DESCR(arr)->f->setitem(one_obj, oneval, arr)
will assume oneval
is a valid pointer to a block of memory large enough to hold sizeof(PyIntObject), but it is actually a void* NULL
at this point, with no valid content. So setitem
will effectively call memcpy(oneval, one_obj, sizeof(PyIntObject)
which will attempt to copy around 32 bytes into a NULL pointer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[zero]val
is a valid pointer to a block of memory large enough to holdsizeof(PyIntObject)
, but it is actually avoid* NULL
citation needed. I see a zeroval = PyDataMem_NEW(PyArray_DESCR(arr)->elsize);
on one of the lines above. Obviously it's not NULL, else the memcpy here would fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My analysis was wrong, yours was more correct. But in the end, we cannot use setitem
, when I tried I got a segfault in test_simple_object
from test_multiarray
.
Theoretically PyArray_DESCR(arr)->f->setitem(one_obj, oneval, arr)
, which ends up on OBJECT_setitem
, should work, although it would call Py_INCREF
on the PyObject*
so we would need a matching Py_DECREF
after the call, who would do it and when?
Unfortunately the current implementation crashes since OBJECT_setitem
calls Py_XDECREF
on oneval
(or zeroval
) and since the memory was allocated with malloc
via PyDataMem_New
it is not zeroed out, so the Py_XDECREF
crashes the interpreter. Perhaps this is why the original implementer did not call setitem
, in addition to the concern about the extra Py_INCREF
needing a matching call somewhere.
(Regarding the more general debate about the brokenness or lack thereof of |
I think the latest version of the pull request is the best I can do at this point. So bottom line - is this merge-able or should I close it? |
Nitpick about declaration order still stands, but this PR looks like an obvious improvement to me, even if there's still an underlying problem - so I'm +1 on merging this. |
it is ugly but it shouldn't make the situation worse, I guess it can go in. |
Running scipy tests with pypy 5.7.0 crashed in these functions when called with array([None]). The returned block of memory holds a pointer-to-Pyobject, but that object has been decref'ed and the pypy garbage collector is free to release the memory. Note that on CPython, small ints are immortal so this does not show up on that interpreter
This does cause permanent allocation of a static variable, but clears the segfault.
I left the decref commented out to make the change more clear, if the idea is acceptable I will amend the pull request to remove the commented out code