python / python/cpython

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

Ouverte
#151,769 2 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

3.14 3.15 3.16 stdlib type-bug
Langage dominant
Python
Étoiles
77.2k
Forks
36k
Métriques de merge des PR
Métriques de PR en attente

Description

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

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez dans Lib/ipaddress.py avec les méthodes de comparaison de IPv6Address et reproduisez les exemples de scoped addresses de l’issue. Confirmez que l’ordre est déterministe et cohérent pour les scoped addresses, tandis que IPv4Address et les comparaisons associées de IPv6Interface et IPv6Network restent inchangées.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
python
Domaine
networking
Type d'issue
Bug
Difficulté
3/5
Temps estimé
1-2 jours
Activité
À l'abandon
Clarté
Clairement spécifiée
Accessibilité débutants
35/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.