python / python/cpython

IPv6Address ordering is scope-id-blind, breaking trichotomy and making sorted()/min()/max() non-deterministic for scoped addresses

Offen
#151,769 2 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

3.14 3.15 3.16 stdlib type-bug
Vorherrschende Sprache
Python
Sterne
77.2k
Forks
35.9k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

Bug report

Bug description:

COMPONENT: Library (Lib/ipaddress.py)

AFFECTED VERSIONS: main (3.16.0a0) and 3.14 (reproduced on 3.14.6). The defect dates to when scope_id was added to IPv6Address.__eq__/__hash__/__str__ but not to ordering.

SUMMARY

IPv6Address.__eq__ and __hash__ fold in the interface scope_id (so fe80::1 and fe80::1%eth0 are unequal and hash differently), but IPv6Address does not override ordering — it inherits _BaseAddress.__lt__, which compares only the integer address and ignores _scope_id. _BaseAddress is decorated with @functools.total_ordering, which synthesizes __gt__/__le__/__ge__ from __lt__ and __eq__. The synthesized __gt__ is effectively not (a < b) and a != b. For two addresses with the same integer but different scope, a < b is False (scope-blind __lt__) while a != b is True (scope-aware __eq__), so a > b is True — and symmetrically b > a is also True. The relation is not a strict order: trichotomy and antisymmetry both fail, and sorting is non-deterministic.

MINIMAL REPRODUCTION

>>> import ipaddress
>>> a = ipaddress.ip_address('fe80::1')
>>> b = ipaddress.ip_address('fe80::1%eth0')
>>> a == b
False
>>> a > b
True
>>> b > a            # antisymmetry violated: both directions are "greater"
True

>>> from itertools import permutations
>>> items = [ipaddress.ip_address(s)
...          for s in ('fe80::1', 'fe80::1%eth0', 'fe80::1%eth1')]
>>> {tuple(str(x) for x in sorted(p)) for p in permutations(items)}
# 6 DISTINCT outputs — sorted() just returns the input order

ROOT CAUSE

scope_id was added to IPv6Address.__eq__/__hash__/__str__ but not to ordering, and IPv6Address defines no __lt__. The @functools.total_ordering operators on _BaseAddress dispatch through type(self).__lt__, which (absent an override) resolves to the scope-blind _BaseAddress.__lt__ while total_ordering also consults the scope-aware __eq__ — so they disagree. The functools docs note total_ordering "makes no attempt to override methods declared in the class or its superclasses," which is exactly why the subclass inherits a __lt__ inconsistent with its own __eq__.

The minimal fix is a scope-aware IPv6Address.__lt__ (tie-break on scope_id only when the integer address is equal; unscoped sorts before scoped; scope ids compare lexicographically). Because the derived operators dispatch through type(self).__lt__, overriding only __lt__ on the subclass routes all four comparisons through the scope-aware path. This also transitively repairs scoped IPv6Interface and IPv6Network ordering, which delegate to address comparison. _BaseAddress.__lt__ is untouched, so IPv4Address is unaffected.

RELATION TO CONCURRENT WORK (#141647 / PR #141842) — please don't close as a dup

There is concurrent activity in gh-141647 / PR gh-141842 touching the comparison dunders, but it is orthogonal: that PR's rewritten _BaseAddress.__lt__ stays scope-blind (return self._ip < other._ip) and adds no IPv6Address override, and its test_mixed_type_ordering skips same-type pairs, so this same-type scoped trichotomy failure survives that PR untested. This fix adds a scope-aware override on the IPv6Address subclass only and does not modify _BaseAddress.__lt__, so it composes cleanly regardless of merge order (or could be folded into #141842).

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs
  • gh-151772
  • gh-151864
  • gh-152320

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginnen Sie in Lib/ipaddress.py mit den Vergleichsmethoden von IPv6Address und reproduzieren Sie die Beispiele für scoped addresses aus dem Issue. Bestätigen Sie, dass die Sortierung für scoped addresses deterministisch und konsistent ist, während die Vergleiche von IPv4Address sowie die zugehörigen Vergleiche von IPv6Interface und IPv6Network unverändert bleiben.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
networking
Issue-Typ
Bug
Schwierigkeit
3/5
Geschätzter Aufwand
1-2 Tage
Aktivitätsstatus
Veraltet
Klarheit
Klar beschrieben
Anfängerfreundlichkeit
35/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.