Skip to content

bpo-29800: Fix crashes in partial.__repr__ if the keys of partial.keywords are not strings #649

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
Mar 15, 2017
Merged

bpo-29800: Fix crashes in partial.__repr__ if the keys of partial.keywords are not strings #649

merged 4 commits into from
Mar 15, 2017

Conversation

MSeifert04
Copy link
Contributor

This is my first contribution for CPython and I must admit I haven't read through all the developer guide.

I signed the CLA (probably still in review) but if you notice anything else that needs to be done please let me know.

@the-knights-who-say-ni
Copy link

Hello, and thanks for your contribution!

I'm a bot set up to make sure that the project can legally accept your contribution by verifying you have signed the PSF contributor agreement (CLA).

Unfortunately our records indicate you have not signed the CLA. For legal reasons we need you to sign this before we can look at your contribution. Please follow these steps to rectify the issue:

  1. Sign the PSF contributor agreement. The "bugs.python.org username" requested by the form is the "Login name" field in "Your Details" at b.p.o
  2. Wait at least one US business day and then check the "Contributor form received entry under "Your Details" on bugs.python.org to see if your account has been marked as having signed the CLA (the delay is due to a person having to manually check your signed CLA)
  3. Reply here saying you have completed the above steps

Thanks again to your contribution and we look forward to looking at it!

@mention-bot
Copy link

@MSeifert04, thanks for your PR! By analyzing the history of the files in this pull request, we identified @serhiy-storchaka, @rhettinger, @ncoghlan, @ambv and @abalkin to be potential reviewers.

# Adding a non-string/unicode keyword to partial kwargs
p.keywords[1] = 10
self.assertIn('1=10', repr(p))
try:
Copy link
Member

Choose a reason for hiding this comment

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

Just use assertRaises().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That failed because assertRaises needs the __name__ of the class/function. But _functools.partial doesn't have a __name__.

Copy link
Member

Choose a reason for hiding this comment

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

with self.assertRaises(TypeError):
    p()

p = self.partial(capture)
# Adding a non-string/unicode keyword to partial kwargs
p.keywords[1] = 10
self.assertIn('1=10', repr(p))
Copy link
Member

Choose a reason for hiding this comment

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

This test is fragile. Since 1=10 is not valid Python syntax, more comprehensive implementation can return partial(<foo>, **{1: 10}).

I would test that the repr() of the partial object contains either str() or repr() of the key and the repr() of the value.

p.keywords[123] = 'value'
r = repr(p)
self.assertIn('123', r)
self.assertIn("'value'", r)

else:
self.fail('partial object allowed passing non-string keywords')

# Deleting the key-value pair during key formatting shouldn't segfault.
Copy link
Member

Choose a reason for hiding this comment

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

This should be a separate test method.


# Deleting the key-value pair during key formatting shouldn't segfault.
class MutatesYourDict(object):
def __init__(self, dct):
Copy link
Member

Choose a reason for hiding this comment

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

The __init__ method and the dct attribute are not needed. Just use p.keywords instead of self.dct.

def __str__(self):
del self.dct[self]
return 'astr'
def __repr__(self):
Copy link
Member

Choose a reason for hiding this comment

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

This is not needed as well as wrapping the value with MutatesYourDict.


p = partial(capture)
p.keywords[MutatesYourDict(p.keywords)] = MutatesYourDict(p.keywords)
self.assertIn('astr=arepr', repr(p))
Copy link
Member

Choose a reason for hiding this comment

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

This test is fragile. See the comment above.

return 'arepr'

p = partial(capture)
p.keywords[MutatesYourDict(p.keywords)] = MutatesYourDict(p.keywords)
Copy link
Member

Choose a reason for hiding this comment

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

I think that wrapping the value with MutatesYourDict is not needed. Just use some unique value with predicable repr. For example ['value'].

Check that this test is failed when don't add increfs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I reorder the commits to add the tests first to show they are segfaulting before doing the commit that fixes them? Or do you mean I should check it locally?

Copy link
Member

Choose a reason for hiding this comment

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

Check it locally. Remove increfs, check that the test is failed/crashed, add increfs, check that the same test is passed now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just to report back: It segfaulted (on Windows 10).

@@ -253,8 +253,13 @@ partial_repr(partialobject *pto)
/* Pack keyword arguments */
assert (PyDict_Check(pto->kw));
for (i = 0; PyDict_Next(pto->kw, &i, &key, &value);) {
Py_SETREF(arglist, PyUnicode_FromFormat("%U, %U=%R", arglist,
/* Prevent key.__str__ from deleting key or value during formatting. */
Py_INCREF(key);
Copy link
Member

Choose a reason for hiding this comment

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

I think that increfing the key is not needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, indeed. I have no problems without that INCREF and I will remove that. That part was copied from dict_repr because I thought what works there needs to work here as well.

def __init__(self, dct):
self.dct = dct
def __str__(self):
del self.dct[self]
Copy link
Member

Choose a reason for hiding this comment

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

It would be better to replace the value rather than delete the key. This wouldn't break conditions for dict iteration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replacing was kind of weird - I think because it's undefined behaviour I tried to test in this case and replacing could create an object that "fills" the memory location where the dangling value had been. I had some trouble to make that case fail in a predictable way without the INCREFs. It sometimes threw an AttributeError instead of segfaulting.

Copy link
Member

Choose a reason for hiding this comment

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

The predictability of failure is not required. It enough if the test failed or crashed sometimes.

If your replace the value with other value it can be catched since other value have different repr.

with self.assertRaises(TypeError):
p()

def test_manually_adding_non_string_keyword(self):
Copy link
Member

Choose a reason for hiding this comment

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

This is the same name as in the above test! This test hides the above test.

def test_manually_adding_non_string_keyword(self):
p = self.partial(capture)
# Adding a non-string/unicode keyword to partial kwargs
p.keywords[1234] = 4321
Copy link
Member

Choose a reason for hiding this comment

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

It would be better to test the value that have different repr and str. For example a string.

@@ -253,8 +253,11 @@ partial_repr(partialobject *pto)
/* Pack keyword arguments */
assert (PyDict_Check(pto->kw));
for (i = 0; PyDict_Next(pto->kw, &i, &key, &value);) {
Py_SETREF(arglist, PyUnicode_FromFormat("%U, %U=%R", arglist,
/* Prevent key.__str__ from deleting key or value during formatting. */
Copy link
Member

Choose a reason for hiding this comment

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

...from deleting value...

# value alive (at least long enough).
class MutatesYourDict(object):
def __str__(self):
p[self] = ['sth2']
Copy link
Member

Choose a reason for hiding this comment

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

p.keywords[self]

@serhiy-storchaka serhiy-storchaka added type-bug An unexpected behavior, bug, or error cherry-pick for 3.5 labels Mar 14, 2017
@MSeifert04
Copy link
Contributor Author

MSeifert04 commented Mar 14, 2017

@the-knights-who-say-ni Steps completed and CLA received.

@serhiy-storchaka serhiy-storchaka changed the title bpo-29800: Check if the keys of partial.keywords are unicodes in partial.__repr__ bpo-29800: Fix crashes in partial.__repr__ if the keys of partial.keywords are not strings Mar 14, 2017
@serhiy-storchaka
Copy link
Member

And the last thing is left @MSeifert04. Please add an entry in Misc/NEWS (at the start of the Library section). Don't forget to add "Patch by Michael Seifert."

@serhiy-storchaka serhiy-storchaka merged commit 6c3d527 into python:master Mar 15, 2017
@MSeifert04
Copy link
Contributor Author

@serhiy-storchaka Thank you for your helpful comments on the pull request and for merging it! 👍

@MSeifert04 MSeifert04 deleted the fix_segfault_partial_repr branch March 15, 2017 06:09
serhiy-storchaka pushed a commit that referenced this pull request Mar 15, 2017
serhiy-storchaka pushed a commit that referenced this pull request Mar 15, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants