FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

BUG: Avoid errors on NULL during deepcopy by jcusick13 · Pull Request #21996 · numpy/numpy · GitHub

/ numpy Public

BUG: Avoid errors on NULL during deepcopy - #21996

Merged
seberg merged 7 commits into
numpy:mainfrom
jcusick13:21883/deepcopy-exception
Jul 17, 2022
Merged

seberg merged 7 commits into
numpy:mainfrom
jcusick13:21883/deepcopy-exception

Conversation

jcusick13 commented Jul 16, 2022
edited
Loading

Copy link
Copy Markdown
Contributor

Closes #8706, #21883

Addresses concerns raised in the two linked issues by:

  • Casting NULL objects to Py_None within _deepcopy_call to avoid issues within CPython's deepcopy
  • Setting _deepcopy_call to return an int code for error/success, which is then checked in array_deepcopy

HaoZeke left a comment

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Will let @seberg be the last word but this seems solid. Thanks!

Comment thread numpy/core/tests/test_multiarray.py Outdated
Comment thread numpy/core/src/multiarray/methods.c Outdated

Copy link
Copy Markdown
Contributor Author

@seberg I realized when I sat back down to work on this, I wasn't exactly clear of how to setup a test for the fail case. I'm stumbling a little bit on how to create an object that will fail when copy.deepcopy is called specifically from within PyObject_CallFunctionObjArgs. I originally tried making a dummy class within the test but I was only able to make the high level .__deepcopy__ behavior fail, which isn't exactly what we're looking for.

def test__deepcopy__catches_failure(self):
    class MyArray(np.ndarray):
         def __deepcopy__(self, *args, **kwargs):
             raise Exception

    a = MyArray([1, 2, 3], dtype='O')
    a.__deepcopy__({})  # Raises an `Exception` defined above, not one from within `_deepcopy_call` C code

I also tried mocking out deepcopy itself but also didn't seem to get all the way there. It wasn't clear to me what to assert for in the case when the underlying deepcopy fails.

def test__deepcopy__catches_failure(self):
    a = np.array([1, 2, 3], dtype='O')

    with patch.object(copy, 'deepcopy', side_effect=None):
        b = a.__deepcopy__({})
      
        # What would a valid assertion be here?
        assert (b == None).all()

Do you have any suggestions or examples from other places in the code of how to do this?

seberg commented Jul 17, 2022

Copy link
Copy Markdown
Member

@jcusick13 oh, you have to put hte bad object into the array:

    class MyObj(np.ndarray):
         def __deepcopy__(self, *args, **kwargs):
             raise Exception

    arr = np.array([MyObj()])
    arr.__deepcopy__({})  # or copy.deepcopy(arr), I guess

Comment thread numpy/core/tests/test_multiarray.py Outdated

Copy link
Copy Markdown
Contributor Author

Thanks @seberg. I added the suggested changes but am still a little bit confused. From looking at the docs for PyObject_CallFunctionObjArgs, I'm expecting errors to be caught within that function and then returned as NULL

Return the result of the call on success, or raise an exception and return NULL on failure.

though it seems like the Exception raised from the bad object just bubbles all the way back up to the surface rather than being caught within the error handling of _deepcopy_call:

/* call deepcopy on this argument */
res = PyObject_CallFunctionObjArgs(deepcopy, itemp, visit, NULL);
Py_DECREF(itemp);
if (res == NULL) {
    return -1;
}

seberg commented Jul 17, 2022

Copy link
Copy Markdown
Member

How exceptions work (typically), is that you return NULL indicating that an error occurred (somewhere). Python then sets the global error state that you could check for example with PyErr_Occured(). So NULL result means "error", and the error itself is stored elsewhere. Was that your question?

Copy link
Copy Markdown
Contributor Author

Ah thanks for the explanation @seberg. I think my final point of confusion is if we then expect a failure within this function

res = PyObject_CallFunctionObjArgs(deepcopy, itemp, visit, NULL);

to raise a Python Exception or if it should return NULL, which would then be handled more gracefully but the error handling within _deepcopy_call?

Mainly, it's not clear to me what to check during the test for the fail case. What should I be looking to create an assert for in the end of the test for this behavior?

Comment thread numpy/core/tests/test_multiarray.py Outdated
raise Exception

arr = np.array([1, MyObj(), 3], dtype='O')
arr.__deepcopy__({})

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This should float up the exception, so you could not use Exception above maybe (maybe AssertionError), not that it matters much. What matters is wrapping this into:

with pytest.raises(Exception):  # (or a more exact Exception)
    arr.__deepcopy__({})

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Got it! I think I was just overthinking things a bit. Just made the changes now; appreciate the help 🙌

seberg commented Jul 17, 2022

Copy link
Copy Markdown
Member

Thanks also for that last error test! Hope to see you around :). Lets put this in, it looks nice to me.

seberg merged commit 0762342 into numpy:main Jul 17, 2022
jcusick13 deleted the 21883/deepcopy-exception branch July 18, 2022 00:05
charris added the 09 - Backport-Candidate PRs tagged should be backported label Jul 22, 2022
charris pushed a commit to charris/numpy that referenced this pull request Jul 24, 2022
Addresses concerns raised in the two linked issues by:

Casting NULL objects to Py_None within _deepcopy_call to avoid issues within CPython's deepcopy
Setting _deepcopy_call to return an int code for error/success, which is then checked in array_deepcopy

Closes numpygh-8706, numpygh-21883
charris removed the 09 - Backport-Candidate PRs tagged should be backported label Jul 24, 2022
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Second contribution

Development

Successfully merging this pull request may close these issues.

_deepcopy_call needs better error handling.

4 participants


Back | FazBrowse Home | New Git URL