Skip to content

OrderedDict.pop() can leave OrderedDict internally inconsistent when key equality changes between lookups #154835

Description

@BHUVANSH855

Bug report

Bug description:

What happened?

collections.OrderedDict.pop() can leave an OrderedDict in an internally inconsistent state if a key's __eq__ implementation returns different results across successive comparisons.

Unlike dict.pop(), OrderedDict.pop() performs multiple independent lookups internally. If those lookups disagree, the linked list and the underlying dictionary can become desynchronized.

Minimal reproducer:

from collections import OrderedDict

class K:
    def __init__(self):
        self.calls = 0

    def __hash__(self):
        return 12345

    def __eq__(self, other):
        self.calls += 1
        return self.calls <= 2

k1 = K()
k2 = K()

od = OrderedDict()
od[k1] = "value"

sentinel = object()
found = od.pop(k2, sentinel)

print(found is sentinel)
print(len(od))
print(list(od))
print(list(od.keys()))
print(k1 in od)
print(od[k1])

try:
    print(od.popitem())
except Exception as e:
    print(type(e).__name__, e)

try:
    od.move_to_end(k1)
except Exception as e:
    print(type(e).__name__, e)

Observed output:

True
1
[]
[]
True
value
KeyError 'dictionary is empty'
KeyError <__main__.K object at ...>

The container reports a length of 1 and the key is still retrievable by lookup, but iteration reports no keys and operations such as popitem() and move_to_end() behave as though the container is empty.

For comparison, performing the equivalent operation on a plain dict leaves the container in a consistent state.

Expected behavior:

  • OrderedDict should remain internally consistent after pop().
  • Even if stateful __eq__ implementations violate normal expectations, the container should not end up in a split state where the dictionary and linked list disagree.

This appears to be distinct from the recent crash in OrderedDict.pop() when no default value is supplied. Supplying a default avoids the NULL dereference path while still reproducing this container desynchronization.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions