Skip to content

bpo-18533: Avoid RuntimeError from repr() of recursive dictview #4823

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 2 commits into from
Jan 26, 2018

Conversation

bennorth
Copy link
Contributor

@bennorth bennorth commented Dec 12, 2017

(Created as per recent comment on bpo-18533. See issue for further background and discussion.)

dictview_repr(): Use a Py_ReprEnter() / Py_ReprLeave() pair to check for recursion, and produce "..." if so.

test_recursive_repr(): Check for the correct string (containing "...") rather than a RuntimeError.

test_deeply_nested_repr(): Add new test, replacing the original test_recursive_repr(). It checks that a RuntimeError is raised in the case of a non-recursive but deeply nested structure. (Very similar to what test_repr_deep() in test/test_dict.py does for a normal dict.)

https://bugs.python.org/issue18533

@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 we couldn't find an account corresponding to your GitHub username on bugs.python.org (b.p.o) to verify you have signed the CLA (this might be simply due to a missing "GitHub Name" entry in your b.p.o account settings). This is necessary for legal reasons before we can look at your contribution. Please follow the steps outlined in the CPython devguide to rectify this issue.

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

@vstinner
Copy link
Member

Hum, this bug has already been fixed in https://bugs.python.org/issue32137 by the commit 1fb72d2, no?

cc @serhiy-storchaka

@bennorth
Copy link
Contributor Author

The fix for bpo-32137 changed a segfault into a RecursionError. This PR proposes changing that into a friendlier "..."-style representation, i.e., no exception is raised.

(I've just noticed that my commit message incorrectly refers to RuntimeError, sorry, as this is what it was last time I looked at this. I can force-push a commit with better message if this PR is approved in principle.)

@serhiy-storchaka serhiy-storchaka added the type-feature A feature request or enhancement label Dec 12, 2017
Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

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

LGTM. Just fix a style and add a news entry.

What about OrderedDict? Is it fixed by this change?

Py_ssize_t rc;

rc = Py_ReprEnter((PyObject *)dv);
if (rc != 0)
Copy link
Member

Choose a reason for hiding this comment

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

Add braces.


rc = Py_ReprEnter((PyObject *)dv);
if (rc != 0)
return rc > 0 ? PyUnicode_FromString("...") : NULL;

seq = PySequence_List((PyObject *)dv);
if (seq == NULL)
Copy link
Member

Choose a reason for hiding this comment

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

Add braces.

@@ -202,6 +203,18 @@ def test_items_set_operations(self):
def test_recursive_repr(self):
d = {}
d[42] = d.values()
r = repr(d)
self.assertIsInstance(r, str)
Copy link
Member

Choose a reason for hiding this comment

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

Is this needed?

@bennorth
Copy link
Contributor Author

@serhiy-storchaka --- I've implemented those style requests, and added a news entry.

Without this PR, OrderedDict raises a RecursionError:

>>> from collections import OrderedDict
>>> d = OrderedDict()
>>> d[42] = d.values()
>>> repr(d)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RecursionError: maximum recursion depth exceeded while getting the repr of an object

whereas with this PR:

>>> repr(d)
'OrderedDict([(42, odict_values([...]))])'

The PR now adds a test for the new behaviour of OrderedDict.

rc = Py_ReprEnter((PyObject *)dv);
if (rc != 0) {
return rc > 0 ? PyUnicode_FromString("...") : NULL;
}

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 empty lines are not needed after }. } itself adds enough vertical space.


class TestOrderedDict(unittest.TestCase):

def test_recursive_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.

Move this test into test_ordered_dict.py.

d = OrderedDict()
d[42] = d.values()
r = repr(d)
self.assertEqual(r, 'OrderedDict([(42, odict_values([...]))])')
Copy link
Member

Choose a reason for hiding this comment

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

The concrete implementation is implementation dependent. For example in PyPy3 it is OrderedDict([(42, _OrderedDictValuesView(...))]). I think it enough to test that repr() doesn't raise an exception in other implementations.

r = repr(d)
if test.support.check_impl_detail(cpython=True):
    self.assertEqual(r, 'OrderedDict([(42, odict_values([...]))])')

dictview_repr(): Use a Py_ReprEnter() / Py_ReprLeave() pair to check
for recursion, and produce "..." if so.

test_recursive_repr(): Check for the a string rather than a
RecursionError.  (Test cannot be any tighter as contents are
implementation-dependent.)

test_deeply_nested_repr(): Add new test, replacing the original
test_recursive_repr().  It checks that a RecursionError is raised in
the case of a non-recursive but deeply nested structure.  (Very
similar to what test_repr_deep() in test/test_dict.py does for a
normal dict.)

OrderedDictTests: Add new test case, to test behaviour on OrderedDict
instances containing their own values() or items().
@bennorth
Copy link
Contributor Author

Thanks for feedback. New version pushed addressing comments:

  • Remove vertical whitespace;
  • Avoid relying on particular implementation in tests;
  • Move OrderedDict test to more sensible location.

Also word 'news' entry more concisely.

@bennorth
Copy link
Contributor Author

Is there anything further I should be doing to help this along? The failing AppVeyor build looks unrelated as it's a failure in distutils.tests.test_bdist_wininst.BuildWinInstTestCase.

@bennorth
Copy link
Contributor Author

@terryjreedy — Thanks for bringing this up-to-date with master; good that the AppVeyor build is now OK.

@orsenthil orsenthil merged commit d7773d9 into python:master Jan 26, 2018
@orsenthil
Copy link
Member

I merged it, @bennorth. All the requirements were met and thanks for your contribution.

@miss-islington
Copy link
Contributor

Thanks @bennorth for the PR, and @orsenthil for merging it 🌮🎉.. I'm working now to backport this PR to: 3.6.
🐍🍒⛏🤖

@miss-islington
Copy link
Contributor

Thanks @bennorth for the PR, and @orsenthil for merging it 🌮🎉.. I'm working now to backport this PR to: 2.7.
🐍🍒⛏🤖

@bedevere-bot
Copy link

GH-5348 is a backport of this pull request to the 3.6 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jan 26, 2018
…thonGH-4823)

dictview_repr(): Use a Py_ReprEnter() / Py_ReprLeave() pair to check
for recursion, and produce "..." if so.

test_recursive_repr(): Check for the string rather than a
RecursionError.  (Test cannot be any tighter as contents are
implementation-dependent.)

test_deeply_nested_repr(): Add new test, replacing the original
test_recursive_repr().  It checks that a RecursionError is raised in
the case of a non-recursive but deeply nested structure.  (Very
similar to what test_repr_deep() in test/test_dict.py does for a
normal dict.)

OrderedDictTests: Add new test case, to test behavior on OrderedDict
instances containing their own values() or items().
(cherry picked from commit d7773d9)
@miss-islington
Copy link
Contributor

Sorry, @bennorth and @orsenthil, I could not cleanly backport this to 2.7 due to a conflict.
Please backport using cherry_picker on command line.
cherry_picker d7773d92bd11640a8c950d6c36a9cef1cee36f96 2.7

@bennorth
Copy link
Contributor Author

@orsenthil — thanks for merging.

As suggested in comment on issue, I've created #5357 to backport this to 2.7.

miss-islington added a commit that referenced this pull request Feb 26, 2018
…-4823)

dictview_repr(): Use a Py_ReprEnter() / Py_ReprLeave() pair to check
for recursion, and produce "..." if so.

test_recursive_repr(): Check for the string rather than a
RecursionError.  (Test cannot be any tighter as contents are
implementation-dependent.)

test_deeply_nested_repr(): Add new test, replacing the original
test_recursive_repr().  It checks that a RecursionError is raised in
the case of a non-recursive but deeply nested structure.  (Very
similar to what test_repr_deep() in test/test_dict.py does for a
normal dict.)

OrderedDictTests: Add new test case, to test behavior on OrderedDict
instances containing their own values() or items().
(cherry picked from commit d7773d9)
bennorth added a commit to bennorth/cpython that referenced this pull request Feb 26, 2018
…on#4823)

dictview_repr(): Use a Py_ReprEnter() / Py_ReprLeave() pair to check
for recursion, and produce "..." if so.

test_recursive_repr(): Check for a string rather than a
RuntimeError.  (Test cannot be any tighter as contents are
implementation-dependent.)

test_deeply_nested_repr(): Add new test, replacing the original
test_recursive_repr().  It checks that a RuntimeError is raised in
the case of a non-recursive but deeply nested structure.

OrderedDictTests: Add new test case, to test behavior on OrderedDict
instances containing their own viewvalues() or viewitems().  This test
passes without the patch to dictview_repr(), but it failed in (at
least some versions of) Python 3, so including it here for
completeness.

(cherry picked from commit d7773d9)
serhiy-storchaka pushed a commit that referenced this pull request Feb 26, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-feature A feature request or enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants