Skip to content

BUG: Remove error-prone borrowed reference handling #13039

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

Merged
merged 4 commits into from
Feb 26, 2019

Conversation

mattip
Copy link
Member

@mattip mattip commented Feb 25, 2019

Fixes #9851. Previously we could have held on to a PyObject* with refcount 0.

@mattip mattip added the 09 - Backport-Candidate PRs tagged should be backported label Feb 25, 2019
Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

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

@mattip - nice! I slightly wonder about also initializing new and metadata to NULL and XDECREF them in the failure path, but it may be more work than it is worth it. Indeed, probably best to just go with this self-contained commit.

@mhvk
Copy link
Contributor

mhvk commented Feb 25, 2019

Hmm, @eric-wieser clearly looked much more carefully at the reference counting chain. Sorry. (Wish this was caught in the tests!)

names = Borrowed_PyMapping_GetItemString(obj, "names");
descrs = Borrowed_PyMapping_GetItemString(obj, "formats");
names = PyMapping_GetItemString(obj, "names");
descrs = PyMapping_GetItemString(obj, "formats");
Copy link
Member

Choose a reason for hiding this comment

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

Pretty sure it's not safe to do these both before checking if either errored. Consider a pathological type like:

class BadDict:
    def __getitem__(self, item):
        raise TypeError("Exception in flight here causes python to be unhappy")

class Foo:
    ___array_interface__ = BadDict

Note that we need a type overloading __getitem__ like this to make your patch worth it in the first place.

Copy link
Member Author

@mattip mattip Feb 26, 2019

Choose a reason for hiding this comment

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

Fixing, even though In this case I am pretty sure the code will work correctly, both name and descrs will be NULL so the following if will kick in.

The patch is to ensure we do not use an object with refcount 0, since it could be collected at any time.

Copy link
Member Author

Choose a reason for hiding this comment

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

Looking more carefully, the code clears any exceptions before calling _use_fields_dict so is there a problem with the test you propose?

I will add a pathological test that only errors on name lookup

Copy link
Member Author

Choose a reason for hiding this comment

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

For reference, the error code path is hit when a dict with no name, format. For instance when converting this valid spec

dt = np.dtype({'f0': ('i4', 0), 'f1':('u1', 4)}, align=True)

Copy link
Member

Choose a reason for hiding this comment

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

I've seen problems before where __getitem__("formats") would raise a SystemError because an exception was set by __getitem__("names"). I think in this case you silence both anyway, but in principle you could hit an assert or worse.

Copy link
Member Author

Choose a reason for hiding this comment

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

If that is something we should be checking for, we should only silence a KeyError. Would you like that to be part of this PR?

Copy link
Member

@eric-wieser eric-wieser Feb 26, 2019

Choose a reason for hiding this comment

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

Worrying about preserving exceptions is a fair point, but I think it's fine to leave for another PR.

Calling python code while PyErr_Occurred() != NULL is pretty dangerous though - grepping through the cpython source code for assert(!PyErr_Occurred()) finds a lot of matches.

Copy link
Member

Choose a reason for hiding this comment

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

So to clarify - I'd like to see this changed to call one, check for null, then call the other. That's in line with the purpose of this PR, which is to make this work for non-dict objects with __getitem__ (the only case when the refcount could drop to 0)

Catching KeyError specifically is a nice idea, but not one that really makes sense as part of this PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

got it, thanks for persevering. Fixed.

@@ -321,6 +320,11 @@ def test_fields_by_index(self):

assert_equal(dt[1], dt[np.int8(1)])

def test_partial_dict(self):
# 'name' is missing
Copy link
Member

Choose a reason for hiding this comment

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

nit: nameswith an s

Copy link
Member Author

Choose a reason for hiding this comment

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

heh, fixing

Copy link
Member

@eric-wieser eric-wieser left a comment

Choose a reason for hiding this comment

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

Looks good now, thanks

@mhvk
Copy link
Contributor

mhvk commented Feb 26, 2019

OK, merging!

@mhvk mhvk merged commit 9114038 into numpy:master Feb 26, 2019
@charris charris removed the 09 - Backport-Candidate PRs tagged should be backported label Mar 3, 2019
@charris charris modified the milestone: 1.16.3 release Mar 3, 2019
@charris charris changed the title BUG: remove error-prone borrowed reference handling BUG: Remove error-prone borrowed reference handling Mar 4, 2019
@mattip mattip deleted the remove-borrowed-refs branch May 20, 2019 13:44
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.

4 participants