-
-
Notifications
You must be signed in to change notification settings - Fork 31.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
gh-119004: fix a crash in equality testing between OrderedDict
#121329
Conversation
I don't see the need to change the pure Python version. Since it won't segfault, there is no need for this elaborate defense against something that doesn't arise in normal practice. |
Well.. without those changes, the tests would be broken (no exception would be raised actually). And the C implementation says:
|
You can either make the new tests for the C version only, or you can make the C version emulate what the Python version does (no exception).
I'm fine with that. We made that decision long ago and it has worked out fine in practice. Our only goal in this PR is make sure the C version won't segfault. There should be no other user visible API change. Nor should the pure python reference implementation change.
Add, "except where it is necessary to protect the C code from a segfault." |
With this approach, this means I need duplicate the items buffer the entire nodes in advance otherwise calling
Actually, the Python implementation does segfault (well it's a segfault in the form of an AttributeError but it's essentially the same issue I think). The Python implementation of def __delitem__(self, key, dict_delitem=dict.__delitem__):
'od.__delitem__(y) <==> del od[y]'
# Deleting an existing item uses self.__map to find the link which gets
# removed by updating the links in the predecessor and successor nodes.
dict_delitem(self, key)
link = self.__map.pop(key)
link_prev = link.prev
link_next = link.next
link_prev.next = link_next
link_next.prev = link_prev
link.prev = None # <- this has side-effects in __reversed__
link.next = None # <- this has side-effects in __iter__ And in return dict.__eq__(self, other) and all(map(_eq, self, other))) The following test: count = 0
class ChangeExternItem:
def __eq__(self, other):
nonlocal count
if count == 1:
del lhs[self]
count += 1
return True
def __hash__(self):
return -1
l1, r1 = ChangeExternItem(), ChangeExternItem()
lhs = self.OrderedDict(dict.fromkeys((0, l1)))
rhs = self.OrderedDict(dict.fromkeys((0, r1)))
# AttributeError: 'NoneType' object has no attribute 'key'
lhs == rhs Now, the first So I still think that the Python implementation should be protected the same the C implementation is. I didn't catch the exception in In summary the Python implementation has also a bug which can be prevented using the current guards. |
I was not able to reproduce this. The code in the referenced issue terminated normally, giving the output:
That is not as severe as a segfault. An exception is a normal way for functions and methods to die when fed bogus inputs. I'll look at this more when I get back from vacation. In the mean time, think about the least invasive, least behavior changing way to avoid the C code segfault. You've explored state tracking (an approach we intentionally decided not to do); now look for less impactful approaches. Keep in mind, |
Yes, that's what I thought as well. Actually, the
Maybe I should have phrased differently in the sense that the reason why such AttributeError arises is the same reason why it segfaults (at least for this specific test case I put).
I see that I forgot to put that in my previous comment but I've thought of copying the keys before calling I'll try to come up with other ideas (it's something I didn't thought of, but maybe I could try to see how dicts are compared in general as well and try to apply that logic here, or see how other languages deal with that issue).
Oh don't worry about it, I think it should be the focus. I can suggest fixing the C implementation but leaving the Python implementation untouched and add a comment saying that mutating the operands may result in undesired side-effects and unexpected exceptions (at least, people would know that we know about the behaviour). One thing I would like to know is whether you want the Python implementation to possibly behave differently than the C implementation (at least, in the handling of errors). |
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 agree with @rhettinger: no need to change the Python code.
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.
For now, I've left the messages since they were also used in other functions of this module. Changing them at one place does not feel right to me, so I think we should change them simultaneously if needed.
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 PR looks quite overcomplicated, especially tests. There are 10 tests for the C implementation, and complex helper class with many attributes and methods. Usually only 1-2 tests are added for similar bugfixes. I andrestand that you want to test many different cases, but are they all necessary? Would not test be simpler if you inline the code of setup()
and teardown()
?
@rhettinger Friendly ping in case you forgot about this PR |
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.
LGTM.
Misc/NEWS.d/next/Library/2024-07-03-14-23-04.gh-issue-119004.L5MoUu.rst
Outdated
Show resolved
Hide resolved
…5MoUu.rst Co-authored-by: Kirill Podoprigora <kirill.bast9@mail.ru>
At the sprint, I discussed this briefly with Eric Snow (the original author). He concurred that the pure python code should not change, and he had no objections to adding the state counter for the C version. |
Hum, do we backport it to 3.12 and 3.13? (AFAICT, this is a bug fix so there should be a backport) |
Thanks @picnixz for the PR, and @rhettinger for merging it 🌮🎉.. I'm working now to backport this PR to: 3.13. |
Thanks @picnixz for the PR, and @rhettinger for merging it 🌮🎉.. I'm working now to backport this PR to: 3.12. |
pythonGH-121329) (cherry picked from commit 38a887d) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
GH-124507 is a backport of this pull request to the 3.13 branch. |
pythonGH-121329) (cherry picked from commit 38a887d) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
GH-124508 is a backport of this pull request to the 3.12 branch. |
Related to python/cpython@c6c3d97 (python/cpython#121329), thanks to brianschubert for noticing
Related to python/cpython@c6c3d97 (python/cpython#121329), thanks to brianschubert for noticing
Related to python/cpython@c6c3d97 (python/cpython#121329), thanks to brianschubert for noticing
This patch is based on #119004 (comment) but some bits needed to be added here and there.
📚 Documentation preview 📚: https://cpython-previews--121329.org.readthedocs.build/